chore: 添加静态资源文件和版本配置文件

添加大量图片、字体、音频等静态资源文件,包括用户界面图片、模板文件和安装界面素材
添加系统版本配置文件(.version)和常量定义文件(.constant)
更新供应商依赖文件,包括Symfony VarDumper组件和相关许可证文件
添加验证码配置文件(captcha.php)和.gitignore规则
This commit is contained in:
2026-03-06 20:14:23 +08:00
commit c9a98f342c
4485 changed files with 490986 additions and 0 deletions

View File

@@ -0,0 +1,93 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\work;
use app\services\other\CategoryServices;
use crmeb\services\FormBuilder;
use think\exception\ValidateException;
/**
* 渠道码分类
* Class WorkChannelCategoryServices
* @package app\services\work
*/
class WorkChannelCategoryServices extends CategoryServices
{
const TYPE = 4;
/**
* @param array $where
* @param array|string[] $field
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getCateList(array $where = [], array $field = ['*'])
{
$where['group'] = self::TYPE;
$where['owner_id'] = 0;
return parent::getCateList($where, $field);
}
/**
* 获取标签全部分类
* @param int $type
* @param int $relation_id
* @return mixed
*/
public function getCateAll(int $type = self::TYPE, int $relation_id = 0)
{
return $this->dao->getCateList(['type' => 0, 'owner_id' => 0, 'relation_id' => $relation_id, 'group' => $type]);
}
/**
* 标签分类表单
* @param array $cataData
* @return mixed
*/
public function cateForm(array $cataData = [])
{
$f[] = FormBuilder::input('name', '分类名称', $cataData['name'] ?? '')->maxlength(20)->required();
$f[] = FormBuilder::number('sort', '排序', (int)($cataData['sort'] ?? 0))->min(0);
return $f;
}
/**
* 创建表单
* @return array
*/
public function createForm()
{
return create_form('添加渠道二维码分类', $this->cateForm(), $this->url('/work/channel/cate'), 'POST');
}
/**
* 修改分类标签表单
* @param int $id
* @return array
* @throws \FormBuilder\Exception\FormBuilderException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function updateForm(int $id)
{
$cate = $this->dao->get($id);
if (!$cate) {
throw new ValidateException('渠道二维码分类没有查到');
}
return create_form('编辑渠道二维码分类', $this->cateForm($cate->toArray()), $this->url('work/channel/cate/' . $id), 'PUT');
}
}

View File

@@ -0,0 +1,369 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\work;
use app\dao\work\WorkChannelCodeDao;
use app\services\BaseServices;
use app\services\user\label\UserLabelServices;
use crmeb\services\wechat\Work;
use crmeb\traits\service\ContactWayQrCode;
use think\annotation\Inject;
use think\exception\ValidateException;
/**
* 渠道码
* Class WorkChannelCodeServices
* @package app\services\work
*/
class WorkChannelCodeServices extends BaseServices
{
use ContactWayQrCode;
/**
* @var WorkChannelCodeDao
*/
#[Inject]
protected WorkChannelCodeDao $dao;
/**
* 获取渠道二维码列表
* @param array $where
* @return array
*/
public function getList(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getDataList($where, ['*'], $page, $limit, 'create_time', ['category' => function ($query) {
$query->field(['id', 'name']);
}]);
//获取标签
$labelIds = [];
foreach ($list as $item) {
$labelIds = array_merge($labelIds, $item['label_id']);
}
$labelIds = array_merge(array_unique(array_filter($labelIds)));
$labelList = [];
if ($labelIds) {
/** @var UserLabelServices $labelServices */
$labelServices = app()->make(UserLabelServices::class);
$labelList = $labelServices->getColumn([
['tag_id', 'in', $labelIds]
], 'label_name', 'tag_id');
}
$workClientFollowServices = app()->make(WorkClientFollowServices::class);
foreach ($list as &$item) {
foreach ($labelList as $key => $value) {
if (in_array($key, $item['label_id'])) {
$item['label_name'][] = $value;
}
}
$item["client_num"] =$workClientFollowServices->getChannelCodeClientCount($item["id"]);
}
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 获取渠道码详情
* @param int $id
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getChannelInfo(int $id)
{
$channelInfo = $this->dao->get($id, ['*'], ['cycle', 'useridLimit' => function ($query) {
$query->with(['member']);
}]);
if (!$channelInfo) {
throw new ValidateException('没有查询到渠道二维码信息');
}
/** @var WorkMemberServices $make */
$make = app()->make(WorkMemberServices::class);
//重组周期数据
if (!empty($channelInfo['cycle'])) {
$userIds = [];
foreach ($channelInfo['cycle'] as $item) {
$userIds = array_merge($userIds, $item['userids']);
}
if ($userIds) {
$userIds = array_merge(array_unique(array_filter($userIds)));
$userList = $make->getColumn([['userid', 'in', $userIds]], 'name', 'userid');
foreach ($channelInfo['cycle'] as &$item) {
$userItem = [];
foreach ($userList as $key => $value) {
if (in_array($key, $item['userids'])) {
$userItem[] = ['name' => $value, 'userid' => $key];
}
}
$item['userItem'] = $userItem;
}
}
}
$channelInfo = $channelInfo->toArray();
$channelInfo['presentUseUserIds'] = $this->getPresentUseUserIds((int)$channelInfo['type'], $channelInfo['cycle'] ?? [], $channelInfo['useridLimit'] ?? [], [
'reserve_userid' => $channelInfo['reserve_userid'],
'userids' => $channelInfo['userids'],
'code_id' => $channelInfo['id'],
'add_upper_limit' => $channelInfo['add_upper_limit'],
]);
$channelInfo['presentUseUserList'] = $make->getColumn([['userid', 'in', $channelInfo['presentUseUserIds']]], 'name');
$channelInfo['reserve_user_list'] = $make->getColumn([['userid', 'in', $channelInfo['reserve_userid']]], 'name');
$channelInfo['user_list'] = $make->getColumn([['userid', 'in', $channelInfo['userids']]], 'name,userid,avatar');
return $channelInfo;
}
/**
* 保存和修改渠道二维码
* @param array $data
* @param int $id
* @return mixed
*/
public function saveChanne(array $data, int $id = 0)
{
$data['type'] = (int)$data['type'];
$cycle = $data['cycle'];
$useridLimit = $data['useridLimit'];
unset($data['cycle'], $data['useridLimit']);
//验证数据
if (1 == $data['type']) {
if (!count($cycle)) {
throw new ValidateException('至少设置一个周期');
}
$this->checkEmployee($cycle);
} else {
if (!$data['userids']) {
throw new ValidateException('必须选择一个成员');
}
if (count($data['userids']) > 100) {
throw new ValidateException('选择的成员不能大于100个');
}
}
//验证欢迎语
$this->checkWelcome($data['welcome_words'], (int)$data['welcome_type']);
/** @var WorkChannelCycleServices $cycleService */
$cycleService = app()->make(WorkChannelCycleServices::class);
/** @var WorkChannelLimitServices $limitService */
$limitService = app()->make(WorkChannelLimitServices::class);
//保存数据
$id = $this->transaction(function () use ($cycle, $id, $data, $cycleService, $useridLimit, $limitService) {
$configId = null;
if ($id) {
$this->dao->update($id, $data);
$configId = $this->dao->value(['id' => $id], 'config_id');
} else {
$data['create_time'] = time();
$res = $this->dao->save($data);
if (!$res) {
throw new ValidateException('保存失败');
}
$id = $res->id;
}
$newCycle = $cycle;
foreach ($cycle as &$item) {
$item['channel_id'] = $id;
$item['userids'] = json_encode($item['userids']);
$item['wokr_time'] = json_encode($item['wokr_time']);
}
foreach ($useridLimit as &$item) {
$item['channel_id'] = $id;
}
$cycleService->delete(['channel_id' => $id]);
$cycleService->saveAll($cycle);
$limitService->delete(['channel_id' => $id]);
$limitService->saveAll($useridLimit);
//提取在线成员生成二维码
$userIds = $this->getPresentUseUserIds((int)$data['type'], $newCycle, $useridLimit, [
'reserve_userid' => $data['reserve_userid'],
'userids' => $data['userids'],
'code_id' => $id,
'add_upper_limit' => $data['add_upper_limit'],
]);
$this->handleQrCode((int)$id, $userIds, !!$data['skip_verify'], $configId);
return $id;
});
return $id;
}
/**
* 获取当前时间内值班的成员userid
* @param int $type
* @param array $cycle
* @param array $useridLimit
* @param array $option
* @return array|mixed
*/
public function getPresentUseUserIds(int $type, array $cycle, array $useridLimit, array $option = [])
{
$addUpperLimit = (int)($option['add_upper_limit'] ?? 0);
if (0 !== $type) {
$newUserIdLimit = [];
foreach ($useridLimit as $item) {
$newUserIdLimit[$item['userid']] = $item;
}
$userIds = $this->cycleByUserid($cycle, $newUserIdLimit, $option['code_id'], $addUpperLimit);
} else {
//限制添加成员
if (1 === $addUpperLimit) {
$userIds = $this->addClientLimit($option['userids'] ?? [], $useridLimit, $option['code_id']);
} else {
$userIds = $option['userids'] ?? [];
}
}
if (!$userIds) {
$userIds = $option['reserve_userid'] ?? [];
}
return $userIds;
}
/**
* 检测成员限制
* @param array $cycle
*/
public function checkEmployee(array $cycle)
{
$week = [];
foreach ($cycle as $item) {
foreach ($item['wokr_time'] as $num) {
$week[$num] = array_merge(($week[$num] ?? []), $item['userids']);
}
}
foreach ($week as $item) {
if (count($item) > 100) {
throw new ValidateException('周期内每个联系方式最多配置100个使用成员');
}
}
}
/**
* 根据周期规则获取当前值班成员userid
* @param array $cycle
* @param array $useridLimit
* @param int $codeId
* @param int $addUpperLimit
* @return array
*/
public function cycleByUserid(array $cycle, array $useridLimit = [], int $codeId = 0, int $addUpperLimit = 0)
{
$userids = [];
$weekNam = date('w');
$nowHon = date('H:i');
foreach ($cycle as $item) {
//查询出当前周期内工作的值班人员
if (in_array($weekNam, $item['wokr_time']) && $item['start_time'] <= $nowHon && $nowHon <= $item['end_time']) {
$userids = array_merge($userids, $item['userids']);
}
//24小时值班人员
if (in_array($weekNam, $item['wokr_time']) && $item['start_time'] == $item['end_time']) {
$userids = array_merge($userids, $item['userids']);
}
}
//每日值班人数大于100的需要使用备用人员
$userids = array_merge(array_unique($userids));
if ($userids && count($userids) > 100) {
return [];
}
//不限制添加客户上限
if (0 === $addUpperLimit) {
return $userids;
}
return $this->addClientLimit($userids, $useridLimit, $codeId);
}
/**
* 获取添加员工上限
* @param array $userids
* @param array $useridLimit
* @param int $codeId
* @return array
*/
protected function addClientLimit(array $userids, array $useridLimit, int $codeId)
{
/** @var WorkClientFollowServices $followService */
$followService = app()->make(WorkClientFollowServices::class);
$userCount = $followService->userIdByCilentCount($userids, $codeId);
if (!$userCount) {
return $userids;
}
$useridLimitNew = [];
foreach ($useridLimit as $value) {
$useridLimitNew[$value['userid']] = $value;
}
$returnUserId = [];
//限制员工每日添加客户个数
foreach ($userids as $userid) {
foreach ($userCount as $item) {
if ($userid == $item['userid'] && $useridLimitNew[$userid]['max'] > $item['sum']) {
$returnUserId[] = $userid;
}
}
}
return $returnUserId;
}
/**
* @param int $id
* @return mixed
*/
public function deleteChannel(int $id)
{
$configId = $this->dao->value(['id' => $id], 'config_id');
return $this->transaction(function () use ($id, $configId) {
if ($configId) {
Work::deleteQrCode($configId);
}
return $this->dao->destroy($id);
});
}
/**
* 执行生成渠道二维码
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
* @throws \GuzzleHttp\Exception\GuzzleException
*/
public function cronHandle()
{
$channelList = $this->dao->getDataList(['status' => 1, 'type' => 1], ['*'], 0, 0, null, ['cycle', 'useridLimit']);
foreach ($channelList as $item) {
//提取在线成员生成二维码
$userIds = $this->getPresentUseUserIds(
(int)$item['type'],
$item['cycle'] ?? [],
$item['useridLimit'] ?? [],
[
'reserve_userid' => $item['reserve_userid'],
'userids' => $item['userids'],
'code_id' => $item['id'],
'add_upper_limit' => $item['add_upper_limit'],
]
);
if ($userIds) {
$this->handleQrCode((int)$item['id'], $userIds, !!$item['skip_verify'], $item['config_id']);
}
}
}
}

View File

@@ -0,0 +1,34 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\work;
use app\dao\work\WorkChannelCycleDao;
use app\services\BaseServices;
use think\annotation\Inject;
/**
* 渠道码周期规则
* Class WorkChannelCycleServices
* @package app\services\work
* @mixin WorkChannelCycleDao
*/
class WorkChannelCycleServices extends BaseServices
{
/**
* @var WorkChannelCycleDao
*/
#[Inject]
protected WorkChannelCycleDao $dao;
}

View File

@@ -0,0 +1,33 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\work;
use app\dao\work\WorkChannelLimitDao;
use app\services\BaseServices;
use think\annotation\Inject;
/**
* Class WorkChannelLimitServices
* @package app\services\\work
* @mixin WorkChannelLimitDao
*/
class WorkChannelLimitServices extends BaseServices
{
/**
* @var WorkChannelLimitDao
*/
#[Inject]
protected WorkChannelLimitDao $dao;
}

View File

@@ -0,0 +1,61 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\work;
use app\dao\work\WorkClientFollowDao;
use app\services\BaseServices;
use think\annotation\Inject;
/**
* 企业微信客户跟踪
* Class WorkClientFollowServices
* @package app\services\work
* @mixin WorkClientFollowDao
*/
class WorkClientFollowServices extends BaseServices
{
/**
* @var WorkClientFollowDao
*/
#[Inject]
protected WorkClientFollowDao $dao;
/**
* 获取扫描渠道码添加的客户列表
* @param int $channelId
* @param string $name
* @return array
*/
public function getChannelCodeClientList(int $channelId, string $name = '')
{
[$page, $limit] = $this->getPageValue();
$where = ['state' => 'channelCode-' . $channelId, 'user_name' => $name, 'is_del_user' => 0];
$list = $this->dao->getDataList($where, ['createtime as create_time', 'client_id'], $page, $limit, 'create_time', ['client' => function ($query) {
$query->field(['id', 'name', 'avatar']);
}]);
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 获取渠道码客户数量
* @param int $channelId 渠道码ID
* @return int
*/
public function getChannelCodeClientCount($channelId)
{
return $this->dao->count(['state' => 'channelCode-' . $channelId, 'is_del_user' => 0]) ?? 0;
}
}

View File

@@ -0,0 +1,34 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\work;
use app\dao\work\WorkClientFollowTagsDao;
use app\services\BaseServices;
use think\annotation\Inject;
/**
* 企业微信客户跟踪标签
* Class WorkClientFollowTagsServices
* @package app\services\work
* @mixin WorkClientFollowTagsDao
*/
class WorkClientFollowTagsServices extends BaseServices
{
/**
* @var WorkClientFollowTagsDao
*/
#[Inject]
protected WorkClientFollowTagsDao $dao;
}

View File

@@ -0,0 +1,614 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\work;
use app\dao\work\WorkClientDao;
use app\jobs\work\WorkClientJob;
use app\services\BaseServices;
use app\services\user\label\UserLabelServices;
use app\services\user\UserServices;
use app\services\wechat\WechatUserServices;
use crmeb\services\wechat\config\WorkConfig;
use crmeb\services\wechat\WechatResponse;
use crmeb\services\wechat\Work;
use think\annotation\Inject;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\exception\ValidateException;
use think\facade\Log;
/**
* 企业微信客户
* Class WorkClientServices
* @package app\services\work
* @mixin WorkClientDao
*/
class WorkClientServices extends BaseServices
{
/**
* @var WorkClientDao
*/
#[Inject]
protected WorkClientDao $dao;
/**
* @param array $where
* @param array $field
* @param bool $isPage
* @return array
*/
public function getList(array $where, array $field = ['*'], bool $isPage = true)
{
$page = $limit = 0;
if ($isPage) {
[$page, $limit] = $this->getPageValue();
}
$list = $this->dao->getDataList($where, $field, $page, $limit, 'create_time', [
'followOne' => function ($query) {
$query->with([
'member' => function ($query) {
$query->field(['userid', 'id', 'name', 'main_department'])
->with(['mastareDepartment']);
}
])->field(['userid', 'client_id', 'state', 'id']);
},
'follow' => function ($query) {
$query->field(['id', 'client_id'])->with(['tags']);
},
]);
foreach ($list as &$item) {
if (!empty($item['follow'])) {
$tags = [];
foreach ($item['follow'] as $value) {
if (!empty($value['tags'])) {
$tags = array_merge($tags, $value['tags']);
}
}
$newTags = [];
foreach ($tags as $tag) {
if (!in_array($tag['tag_name'], array_column($newTags, 'tag_name'))) {
$newTags[] = $tag;
}
}
$item['followOne']['tags'] = $newTags;
}
}
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 自动同步客户
* @param int $page
* @param string $cursor
* @return bool
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function authGetExternalcontact(int $page = 1, string $cursor = '')
{
/** @var WorkConfig $config */
$config = app()->make(WorkConfig::class);
$corpId = $config->corpId;
if (!$corpId) {
return true;
}
/** @var WorkMemberServices $memberService */
$memberService = app()->make(WorkMemberServices::class);
$menmberList = $memberService->getDataList(['corp_id' => $corpId], ['userid'], $page, 1);
//没有数据就返回成功
if (!$menmberList) {
return true;
}
$tagRes = Work::getCorpTags();
$tagMap = [];
foreach ($tagRes["tag_group"]??[] as $tagGroupItem){
foreach ($tagGroupItem["tag"]??[] as $tagItem){
$tagMap[$tagItem["id"]] = $tagItem +["group_id"=>$tagGroupItem["group_id"],"group_name"=>$tagGroupItem["group_name"]];
}
}
$userids = array_column($menmberList, 'userid');
try {
$response = Work::getBatchClientList($userids, $cursor);
$externalContactList = $response['external_contact_list'] ?? [];
}catch (\Throwable $e){
WorkClientJob::dispatchDo('authClient', [$page + 1, '']);
return true;
}
$external = [];
$followUser = [];//内部人员跟踪
$externalUserids = [];//客户信息
$this->transaction(function () use ($externalContactList, $corpId, $externalUserids, $followUser, $external, $tagMap) {
/** @var WechatUserServices $wechatUserService */
$wechatUserService = app()->make(WechatUserServices::class);
foreach ($externalContactList as $item) {
$externalContact = $item['external_contact'];
$unionid = $externalContact['unionid'] ?? '';
if (isset($externalContact['unionid'])) {
unset($externalContact['unionid']);
}
$corpName = $corpFullName = $position = '';
if (isset($externalContact['corp_name'])) {
$corpName = $externalContact['corp_name'];
unset($externalContact['corp_name']);
}
if (isset($externalContact['corp_full_name'])) {
$corpFullName = $externalContact['corp_full_name'];
unset($externalContact['corp_full_name']);
}
if (isset($externalContact['position'])) {
$position = $externalContact['position'];
unset($externalContact['position']);
}
$externalContact['position'] = $position;
$externalContact['external_profile'] = json_encode($externalContact['external_profile'] ?? [],JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$followUserData = [
'userid' => $item['follow_info']['userid'],
'remark' => $item['follow_info']['remark'] ?? '',
'description' => $item['follow_info']['description'] ?? '',
'createtime' => $item['follow_info']['createtime'] ?? '',
'remark_corp_name' => $item['follow_info']['remark_corp_name'] ?? '',
'remark_mobiles' => json_encode($item['follow_info']['remark_mobiles'] ?? ''),
'add_way' => $item['follow_info']['add_way'] ?? '',
'state' => $item['follow_info']['state'] ?? '',
'oper_userid' => $item['follow_info']['oper_userid'] ?? '',
'create_time' => time(),
'tags' => [],
];
foreach ($item['follow_info']['tag_id']??[] as $tagId){
$tag = $tagMap[$tagId]??[];
$followUserData['tags'][] = [
'group_name' => $tag['group_name'] ?? '',
'tag_name' => $tag['name'] ?? '',
'type' => $tag['type'] ?? 1,
'tag_id' => $tag['id'],
'create_time' => time()
];
}
$followUser[$externalContact['external_userid']] = $followUserData;
$externalUserids[] = $externalContact['external_userid'];
$externalUserid = $externalContact['external_userid'];
$externalContact['uid'] = $wechatUserService->getOne(["unionid"=>$unionid],"uid")["uid"]??0;
$externalContact['corp_id'] = $corpId;
$externalContact['unionid'] = $unionid;
$externalContact['corp_name'] = $corpName;
$externalContact['corp_full_name'] = $corpFullName;
if ($this->dao->count(['external_userid' => $externalUserid, 'corp_id' => $corpId])) {
unset($externalContact['external_userid']);
$this->dao->update(['external_userid' => $externalUserid], $externalContact);
} else {
$externalContact['create_time'] = $item['follow_info']['createtime'] ??time();
$externalContact['update_time'] = time();
$external[] = $externalContact;
}
}
if ($external) {
$this->dao->saveAll($external);
}
$clientList = $this->dao->getColumn([['external_userid', 'in', $externalUserids], ['corp_id', '=', $corpId]], 'id', 'external_userid');
/** @var WorkClientFollowServices $followService */
$followService = app()->make(WorkClientFollowServices::class);
if ($followUser) {
/** @var WorkClientFollowTagsServices $tagService */
$tagService = app()->make(WorkClientFollowTagsServices::class);
foreach ($followUser as $userid => $items) {
if(!isset($clientList[$userid])){
continue;
}
$items['client_id'] = $clientList[$userid];
if (($id = $followService->value(['client_id' => $clientList[$userid], 'userid' => $items['userid']], 'id'))) {
$followService->update($id, [
'remark' => $items['remark'],
'description' => $items['description'],
'createtime' => $items['createtime'],
'remark_corp_name' => $items['remark_corp_name'],
'remark_mobiles' => $items['remark_mobiles'],
'add_way' => $items['add_way'],
'oper_userid' => $items['oper_userid'],
]);
} else {
$res = $followService->save($items);
$id = $res->id;
}
if (!empty($items['tags'])) {
$tagService->delete(['follow_id' => $id]);
foreach ($items['tags'] as &$tag) {
$tag['follow_id'] = $id;
}
$tagService->saveAll($items['tags']);
}
}
}
});
if (isset($response['next_cursor']) && $response['next_cursor']) {
WorkClientJob::dispatchDo('authClient', [$page, $response['next_cursor']]);
} else if (empty($response['next_cursor'])) {
WorkClientJob::dispatchDo('authClient', [$page + 1, '']);
}
return true;
}
/**
* 保存客户标签
* @param array $tagGroup 标签组数据
* @return void
*/
public function saveClientTags(array $tagGroup)
{
}
/**
* 创建客户
* @param array $payload
* @return mixed
*/
public function createClient(array $payload)
{
$corpId = $payload['ToUserName'];//企业id
$externalUserID = $payload['ExternalUserID'];//外部企业userid
$state = $payload['State'] ?? '';//扫码值
$userId = $payload['UserID'];//成员userid
//保存客户
$clientId = $this->saveOrUpdateClient($corpId, $externalUserID, $userId);
//发送欢迎语
try {
event('work.welcome', [$payload['WelcomeCode'] ?? '', $state, $clientId, $userId]);
} catch (\Throwable $e) {
Log::error("发送欢迎语失败:" . json_encode([
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
],JSON_UNESCAPED_UNICODE));
}
//设置欢客户标签
try {
event('work.label', [$state, $userId, $externalUserID]);
} catch (\Throwable $e) {
Log::error("设置欢客户标签失败:" . json_encode([
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
],JSON_UNESCAPED_UNICODE));
}
//关联客户与商城用户
try {
event('work.user', [$clientId]);
} catch (\Throwable $e) {
Log::error("关联客户与商城用户失败:" . json_encode([
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
],JSON_UNESCAPED_UNICODE));
}
return $clientId;
}
/**
* 更新客户信息
* @param array $payload
* @return mixed
*/
public function updateClient(array $payload)
{
$corpId = $payload['ToUserName'];
$externalUserID = $payload['ExternalUserID'];
$userId = $payload['UserID'];//成员serid
$clientId = $this->saveOrUpdateClient($corpId, $externalUserID, $userId);
//关联客户与商城用户
try {
event('work.user', [$clientId]);
} catch (\Throwable $e) {
Log::error("关联客户与商城用户失败:" . json_encode([
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
],JSON_UNESCAPED_UNICODE));
}
return $clientId;
}
/**
* 企业成员删除客户
* @param array $payload
* @return bool
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function deleteClient(array $payload)
{
$corpId = $payload['ToUserName'];
$externalUserID = $payload['ExternalUserID'];
$userId = $payload['UserID'];//成员serid
$clientInfo = $this->dao->get(['external_userid' => $externalUserID, 'corp_id' => $corpId], ['id']);
if ($clientInfo) {
$this->transaction(function () use ($clientInfo, $userId) {
$this->dao->destroy($clientInfo->id);
/** @var WorkClientFollowServices $followService */
$followService = app()->make(WorkClientFollowServices::class);
$followService->update(['client_id' => $clientInfo->id, 'userid' => $userId], ['is_del_user' => 1]);
});
}
return true;
}
/**
* 客户删除企业微信成员
* @param array $payload
* @return bool
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function deleteFollowClient(array $payload)
{
$corpId = $payload['ToUserName'];
$externalUserID = $payload['ExternalUserID'];
$userId = $payload['UserID'];//成员serid
$clientInfo = $this->dao->get(['external_userid' => $externalUserID, 'corp_id' => $corpId], ['id']);
/** @var WorkClientFollowServices $followService */
$followService = app()->make(WorkClientFollowServices::class);
if ($clientInfo) {
$followService->update(['client_id' => $clientInfo->id, 'userid' => $userId], ['is_del_user' => 1]);
}
return true;
}
/**
* 更新或者添加客户信息
* @param string $corpId
* @param string $externalUserID
* @param string $userId
* @return mixed
*/
public function saveOrUpdateClient(string $corpId, string $externalUserID, string $userId)
{
$response = Work::getClientInfo($externalUserID);
$externalContact = $response['external_contact'] ?? [];
$followUser = $response['follow_user'] ?? [];
$res = true;
$externalContact['corp_id'] = $corpId;
$externalContact['external_profile'] = json_encode($externalContact['external_profile'] ?? []);
$clientId = $this->dao->value(['external_userid' => $externalContact['external_userid'], 'corp_id' => $corpId], 'id');
try {
$clientId = $this->transaction(function () use ($userId, $res, $clientId, $externalContact, $followUser) {
if ($clientId) {
$this->dao->update($clientId, $externalContact);
} else {
$res = $this->dao->save($externalContact);
$clientId = $res->id;
}
$userids = [];
$res1 = false;
foreach ($followUser as &$item) {
$item['create_time'] = time();
if ($userId === $item['userid']) {
$res1 = true;
}
$userids[] = $item['userid'];
$item['client_id'] = $clientId;
if (isset($item['wechat_channels'])) {
unset($item['wechat_channels']);
}
}
if (!$res1 && $userId) {
$followUser[] = [
'client_id' => $clientId,
'userid' => $userId,
'state' => $item["state"] ?? '',
'createtime' => time(),
'tags' => []
];
}
//添加了此外部联系人的企业成员
if ($followUser) {
/** @var WorkClientFollowServices $followService */
$followService = app()->make(WorkClientFollowServices::class);
/** @var WorkClientFollowTagsServices $tagService */
$tagService = app()->make(WorkClientFollowTagsServices::class);
foreach ($followUser as $item) {
if (($id = $followService->value(['client_id' => $clientId, 'userid' => $item['userid']], 'id'))) {
$followService->update($id, [
'remark' => $item['remark'],
'description' => $item['description'],
'remark_corp_name' => $item['remark_corp_name'] ?? '',
'state' => $item["state"] ?? '',
'add_way' => $item['add_way'] ?? '',
'oper_userid' => $item['oper_userid'] ?? '',
]);
} else {
$res = $followService->save($item);
$id = $res->id;
}
$tagService->delete(['follow_id' => $id]);
if (!empty($item['tags'])) {
$tagsNews = [];
foreach ($item['tags'] as $tag) {
$tag['follow_id'] = $id;
$tagsNews[] = $tag;
}
$tagService->saveAll($tagsNews);
}
}
}
if (!$res) {
throw new ValidateException('保存失败');
}
return $clientId;
});
} catch (\Throwable $e) {
Log::error(json_encode([
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
],JSON_UNESCAPED_UNICODE));
}
return $clientId;
}
/**
* @param string $userid
* @param array $clientInfo
* @return array
*/
public function getClientInfo(string $userid, array $clientInfo)
{
$clientInfo['userInfo'] = [];
if ($clientInfo['uid']) {
/** @var UserServices $make */
$make = app()->make(UserServices::class);
$userInfo = $make->get($clientInfo['uid'], ['*'], ['label', 'userGroup', 'spreadUser']);
if ($userInfo) {
$clientInfo['userInfo'] = $userInfo->toArray();
// $clientInfo['userInfo']['birthday'] = $clientInfo['userInfo']['birthday'] ? date('Y-m-d', $clientInfo['userInfo']['birthday']) : '';
}
}
return $clientInfo;
}
/**
* 异步批量设置标签
* @param array $addTag
* @param array $removeTag
* @param array $userId
* @param array $where
* @param int $isAll
* @return bool
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function synchBatchLabel(array $addTag, array $removeTag, array $userId, array $where, int $isAll = 0)
{
if ($isAll) {
$clientList = $this->dao->getDataList($where, ['external_userid', 'id', 'unionid', 'uid'], 0, 0, null, ['followOne']);
} else {
$clientList = $this->dao->getDataList(['external_userid' => $userId], ['external_userid', 'id', 'unionid', 'uid'], 0, 0, null, ['followOne']);
}
$batchClient = [];
foreach ($clientList as $item) {
if (!empty($item['followOne'])) {
$batchClient[] = [
'external_userid' => $item['external_userid'],
'userid' => $item['followOne']['userid'],
'add_tag' => $addTag,
'remove_tag' => $removeTag,
];
}
}
if ($batchClient) {
foreach ($batchClient as $item) {
WorkClientJob::dispatchDo('setLabel', [$item]);
}
}
return true;
}
/**
* 设置客户标签
* @param array $markTag
* @return WechatResponse|false
*/
public function setClientMarkTag(array $markTag)
{
try {
$res = Work::markTags($markTag['userid'], $markTag['external_userid'], $markTag['add_tag'], $markTag['remove_tag']);
$res = new WechatResponse($res);
//同步标签后同步用户信息
/** @var WorkConfig $config */
$config = app()->make(WorkConfig::class);
WorkClientJob::dispatchSece(2, 'saveClientInfo', [$config->corpId, $markTag['external_userid'], $markTag['userid']]);
return $res;
} catch (\Throwable $e) {
Log::error("修改客户标签发生错误:" . json_encode([
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
],JSON_UNESCAPED_UNICODE));
return false;
}
}
/**
* 查找成员下附带的客户人数
* @param array $where
* @return int
*/
public function getUserIdsByCount(array $where)
{
if ($where['is_all']) {
unset($where['time'], $where['label'], $where['notLabel']);
}
$where['timeKey'] = 'create_time';
if (!empty($where['label'])) {
/** @var UserLabelServices $service */
$service = app()->make(UserLabelServices::class);
$tagId = $service->getColumn([
['id', 'in', $where['label']],
], 'tag_id');
$where['label'] = array_unique($tagId);
}
if (!empty($where['notLabel'])) {
/** @var UserLabelServices $service */
$service = app()->make(UserLabelServices::class);
$tagId = $service->getColumn([
['id', 'in', $where['notLabel']],
], 'tag_id');
$where['notLabel'] = array_unique($tagId);
}
return $this->dao->getClientCount($where);
}
/**
* 解绑用户
* @param int $uid
*/
public function unboundUser(int $uid)
{
try {
$this->dao->update(['uid' => $uid], ['uid' => 0]);
} catch (\Throwable $e) {
Log::error("解绑用户失败:" . json_encode([
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
],JSON_UNESCAPED_UNICODE));
}
}
}

View File

@@ -0,0 +1,56 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\work;
use app\services\BaseServices;
use crmeb\services\wechat\config\WorkConfig;
use crmeb\services\wechat\contract\WorkAppConfigHandlerInterface;
/**
* 企业微信配置
* Class WorkConfigServices
* @package app\services
*/
class WorkConfigServices extends BaseServices implements WorkAppConfigHandlerInterface
{
/**
* 获取应用配置
* @param string $corpId
* @param string $type
* @return array
*/
public function getAppConfig(string $corpId, string $type): array
{
$config = [];
switch ($type) {
case WorkConfig::TYPE_USER:
$config = [
'secret' => sys_config('wechat_work_user_secret')
];
break;
case WorkConfig::TYPE_ADDRESS:
$config = [
'secret' => sys_config('wechat_work_address_secret'),
];
break;
case WorkConfig::TYPE_USER_APP:
$config = [
'agent_id' => sys_config('wechat_work_build_agent_id'),
'secret' => sys_config('wechat_work_build_secret'),
];
break;
}
return $config;
}
}

View File

@@ -0,0 +1,210 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\work;
use app\dao\work\WorkDepartmentDao;
use app\jobs\work\WorkMemberJob;
use app\services\BaseServices;
use crmeb\services\wechat\config\WorkConfig;
use crmeb\services\wechat\Work;
use think\annotation\Inject;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\exception\ValidateException;
/**
* 企业微信部门
* Class WorkDepartmentServices
* @package app\services\work
* @mixin WorkDepartmentDao
*/
class WorkDepartmentServices extends BaseServices
{
/**
* @var WorkDepartmentDao
*/
#[Inject]
protected WorkDepartmentDao $dao;
/**
* 获取组织架构
* @return mixed
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function getDepartmentList()
{
/** @var WorkConfig $config */
$config = app()->make(WorkConfig::class);
$data = $this->dao->getDataList(['corp_id' => $config->corpId], ['department_id', 'parentid', 'name', 'name_en']);
$departmentId = array_column($data, 'department_id');
if ($departmentId) {
/** @var WorkMemberRelationServices $memberRelationService */
$memberRelationService = app()->make(WorkMemberRelationServices::class);
$memberList = $memberRelationService->getMemberRelationList([
['department', 'in', $departmentId]
], ['member_id', 'department']);
foreach ($data as &$item) {
$memberId = [];
foreach ($memberList as $value) {
if ($value['department'] == $item['department_id']) {
$memberId[] = $value['member_id'];
}
}
$item['count'] = count($memberId);
}
}
return get_tree_children($data, 'children', 'department_id', 'parentid');
}
/**
* 同步企业微信部门和成员信息
*/
public function authDepartment()
{
$res = Work::getDepartment();
$department = $res['department'] ?? [];
$data = [];
$ids = [];
/** @var WorkConfig $config */
$config = app()->make(WorkConfig::class);
$corpId = $config->corpId;
if (!$corpId) {
throw new ValidateException('请先配置企业微信ID');
}
foreach ($department as $item) {
$item['srot'] = $item['order'] ?? '';
$item['name_en'] = $item['name_en'] ?? '';
$item['department_leader'] = json_encode($item['department_leader'] ?? []);
$item['department_id'] = $item['id'] ?? '';
unset($item['order'], $item['id']);
if ($this->dao->count(['department_id' => $item['department_id'], 'corp_id' => $corpId])) {
$this->dao->update(['department_id' => $item['department_id'], 'corp_id' => $corpId], [
'name' => $item['name'] ?? '',
'srot' => $item['srot'],
'department_leader' => $item['department_leader'],
'parentid' => $item['parentid']
]);
} else {
$item['create_time'] = time();
$item['corp_id'] = $corpId;
$data[] = $item;
}
$ids[] = $item['department_id'];
}
if ($data) {
$this->dao->saveAll($data);
}
if ($ids) {
foreach ($ids as $id) {
WorkMemberJob::dispatchDo('run', [$id]);
}
}
}
/**
* 获取部门+成员tree型数据
* @param string $corpId
* @param array $mobile
* @return array
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function getMailChildren(string $corpId)
{
$list = $this->dao->getDataList(['corp_id' => $corpId], ['department_id', 'parentid', 'name', 'name_en'], 0, 0, 'srot', [
'member' => fn($query) => $query->where('corp_id', $corpId)
->field(['userid', 'id', 'mobile', 'avatar', 'thumb_avatar', 'name'])
->with('departmentRelation')
]);
$userList = [];
foreach ($list as $item) {
if ($item['member'] && is_array($item['member'])) {
$userList = array_merge($userList, $item['member']);
}
}
foreach ($list as &$item) {
$item['member'] = [];
$user = [];
foreach ($userList as $value) {
$frameIds = $value['departmentRelation'] ? array_column($value['departmentRelation'], 'department') : [];
unset($value['departmentRelation']);
if (in_array($item['department_id'], $frameIds)) {
$user[] = $value;
}
}
$item['member'] = $user;
$item['member_count'] = count($user);
}
return get_tree_children($list, 'children', 'department_id', 'parentid');
}
/**
* 创建部门
* @param array $payload
* @return \crmeb\basic\BaseModel|mixed|\think\Model
*/
public function createDepartment(array $payload)
{
$corpId = $payload['ToUserName'];
$where = ['corp_id' => $corpId, 'department_id' => $payload['Id']];
$departmentInfo = Work::getDepartmentInfo($payload['Id']);
if ($this->dao->count($where)) {
return $this->updateDepartment($corpId, (int)$payload['Id'], $departmentInfo['department']['name']);
} else {
return $this->dao->save([
'corp_id' => $corpId,
'department_id' => $payload['Id'] ?? '',
'name' => $departmentInfo['department']['name'] ?? '',
'parentid' => $departmentInfo['department']['parentid'] ?? '',
'sort' => $payload['order'] ?? '',
'create_time' => time()
]);
}
}
/**
* 更新部门
* @param string $corpId
* @param int $department_id
* @param string $name
* @return mixed
*/
public function updateDepartment(string $corpId, int $departmentId, string $name)
{
if (!$name) {
$departmentInfo = Work::getDepartmentInfo($departmentId);
$name = $departmentInfo['department']['name'] ?? '';
}
return $this->dao->update(['corp_id' => $corpId, 'department_id' => $departmentId], ['name' => $name]);
}
/**
* 删除部门
* @param string $corpId
* @param int $departmentId
* @return mixed
*/
public function deleteDepartment(string $corpId, int $departmentId)
{
return $this->dao->delete(['corp_id' => $corpId, 'department_id' => $departmentId]);
}
}

View File

@@ -0,0 +1,224 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\work;
use app\dao\work\WorkGroupChatAuthDao;
use app\services\BaseServices;
use app\services\user\label\UserLabelServices;
use crmeb\services\wechat\Work;
use crmeb\traits\service\ContactWayQrCode;
use think\annotation\Inject;
use think\exception\ValidateException;
/**
* 企业微信自动拉群
* Class WorkGroupChatAuthServices
* @package app\services\work
*/
class WorkGroupChatAuthServices extends BaseServices
{
use ContactWayQrCode;
/**
* @var WorkGroupChatAuthDao
*/
#[Inject]
protected WorkGroupChatAuthDao $dao;
/**
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getList(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getDataList($where, ['*'], $page, $limit, 'create_time');
$chatIds = [];
$labels = [];
foreach ($list as $item) {
$chatIds = array_merge($chatIds, $item['chat_id']);
$labels = array_merge($labels, $item['label'] ?? []);
}
$chatIds = array_merge(array_unique(array_filter($chatIds)));
$labels = array_merge(array_unique(array_filter($labels)));
/** @var UserLabelServices $userLabelService */
$userLabelService = app()->make(UserLabelServices::class);
$labelList = $userLabelService->getColumn([
['tag_id', 'in', $labels]
], 'label_name', 'tag_id');
/** @var WorkGroupChatServices $service */
$service = app()->make(WorkGroupChatServices::class);
$chatList = $service->getColumn([
['chat_id', 'in', $chatIds]
], 'name', 'chat_id');
foreach ($list as &$item) {
$chatNewList = $labelNewList = [];
foreach ($chatList as $key => $val) {
if (in_array($key, $item['chat_id'])) {
$chatNewList[] = ['name' => $val, 'chat_id' => $key];
}
}
if ($item['label']) {
foreach ($labelList as $k => $v) {
if (in_array($k, $item['label'])) {
$labelNewList[] = ['name' => $v, 'label_id' => $k];
}
}
}
$item['chat_list'] = $chatNewList;
$item['label_list'] = $labelNewList;
}
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 保存或者修改自动拉群
* @param array $data
* @param int $id
* @return mixed
*/
public function saveGroupChatAuth(array $data, int $id = 0)
{
return $this->transaction(function () use ($data, $id) {
if ($id) {
$authInfo = $this->dao->get(['id' => $id], ['config_id']);
$this->dao->update($id, $data);
$this->handleGroupChat($data, $id, $authInfo->config_id);
} else {
$res = $this->dao->save($data);
$id = $res->id;
$this->handleGroupChat($data, $id);
}
return $id;
});
}
/**
* 配置加入群聊并获取二维码
* @param string $configId
* @param array $data
* @param int $id
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
* @throws \GuzzleHttp\Exception\GuzzleException
*/
protected function handleGroupChat(array $data, int $id, string $configId = '')
{
//设置进群配置
if ($configId) {
$qrCode = Work::instance()->groupChat()->updateJoinWay($configId, $data['group_name'], $data['chat_id'], 'groupChat-' . $id, (int)$data['auth_group_chat'], (int)$data['group_num']);
} else {
$qrCode = Work::instance()->groupChat()->addJoinWay($data['group_name'], $data['chat_id'], 'groupChat-' . $id, (int)$data['auth_group_chat'], (int)$data['group_num']);
}
if (0 !== $qrCode['errcode']) {
throw new ValidateException($qrCode['errmsg']);
}
if (!$configId) {
$this->dao->update($id, ['config_id' => $qrCode['config_id']]);
$configId = $qrCode['config_id'];
}
//获取群二维码
$qrCodeInfo = Work::instance()->groupChat()->getJoinWay($configId);
if (0 !== $qrCodeInfo['errcode']) {
throw new ValidateException($qrCodeInfo['errmsg']);
}
$this->dao->update($id, ['qr_code' => $qrCodeInfo['join_way']['qr_code']]);
}
/**
* 删除客户进群配置
* @param int $id
* @return bool
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
* @throws \GuzzleHttp\Exception\GuzzleException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function deleteGroupChatAuth(int $id)
{
$groupChatAuthInfo = $this->dao->get($id);
if (!$groupChatAuthInfo) {
throw new ValidateException('删除的群聊配置不存在');
}
//删除入群配置
if ($groupChatAuthInfo->config_id) {
$qrCode = Work::instance()->groupChat()->deleteJoinWay($groupChatAuthInfo->config_id);
if (0 !== $qrCode['errcode']) {
throw new ValidateException($qrCode['errmsg']);
}
}
return $this->dao->destroy($id);
}
/**
* 获取群配置
* @param int $id
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getGrouChatAuthInfo(int $id)
{
$groupChatAuthInfo = $this->dao->get($id);
if (!$groupChatAuthInfo) {
throw new ValidateException('删除的群聊配置不存在');
}
$groupChatAuthInfo['labelList'] = $groupChatAuthInfo['chatList'] = [];
if ($groupChatAuthInfo->label) {
/** @var UserLabelServices $userLabelService */
$userLabelService = app()->make(UserLabelServices::class);
$groupChatAuthInfo['labelList'] = $userLabelService->getColumn([
['tag_id', 'in', $groupChatAuthInfo->label]
], 'label_name,tag_id');
}
if ($groupChatAuthInfo->chat_id) {
/** @var WorkGroupChatServices $service */
$service = app()->make(WorkGroupChatServices::class);
$groupChatAuthInfo['chatList'] = $service->getColumn([
['chat_id', 'in', $groupChatAuthInfo->chat_id]
], 'name,chat_id');
}
return $groupChatAuthInfo->toArray();
}
/**
*
* @param int $groupAuthId
* @param string $userid
* @param string $externalUserID
*/
public function clientAddLabel(int $groupAuthId, string $userid, string $externalUserID)
{
$label = $this->dao->value(['id' => $groupAuthId], 'label');
$resTage = Work::markTags($userid, $externalUserID, $label);
if (0 !== $resTage['errcode']) {
throw new ValidateException($resTage['errmsg']);
}
}
}

View File

@@ -0,0 +1,102 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\work;
use app\dao\work\WorkGroupChatMemberDao;
use app\services\BaseServices;
use app\services\user\UserServices;
use think\annotation\Inject;
/**
* 企业微信群成员
* Class WorkGroupChatMemberServices
* @package app\services\work
* @mixin WorkGroupChatMemberDao
*/
class WorkGroupChatMemberServices extends BaseServices
{
/**
* @var WorkGroupChatMemberDao
*/
#[Inject]
protected WorkGroupChatMemberDao $dao;
/**
* 获取群成员
* @param int $chatId
* @param string $name
* @return array
*/
public function getChatMemberList(int $chatId, string $name = '')
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getDataList(['group_id' => $chatId, 'name_like' => $name, 'status' => 1], ['*'], $page, $limit, 'create_time', [
'member' => function ($query) {
$query->field(['name', 'userid', 'gender', 'avatar', 'id', 'mobile']);
},
'client' => function ($query) {
$query->field(['name', 'external_userid', 'gender', 'avatar', 'id']);
}
]);
$clientId = $mobile = [];
foreach ($list as &$item) {
$item['group_chat_num'] = $this->dao->getChatSum($item['userid']);
if ($item['group_chat_num'] > 0) {
$item['group_chat_num']--;
}
if (isset($item['client']['id'])) {
$clientId[] = $item['client']['id'];
}
if (isset($item['member'])) {
$mobile[] = $item['member']['mobile'];
}
}
/** @var WorkClientFollowServices $followService */
$followService = app()->make(WorkClientFollowServices::class);
$followList = $followService->getDataList(['client_id' => $clientId], ['id', 'client_id',], 0, 0, 'create_time', ['tags']);
if ($followList) {
foreach ($list as &$item) {
$newTag = [];
if (isset($item['client']['id'])) {
foreach ($followList as $value) {
if ($item['client']['id'] == $value['client_id'] && !empty($value['tags'])) {
$newTag = array_column($value['tags'], 'tag_name');
}
}
$item['tags'] = $newTag;
}
}
}
if ($mobile) {
/** @var UserServices $userService */
$userService = app()->make(UserServices::class);
$userList = $userService->getList(['phone' => $mobile], 'phone,uid', 0, 0);
if ($userList) {
foreach ($list as &$item) {
$newTag = [];
if (isset($item['member'])) {
foreach ($userList as $value) {
if ($item['member']['mobile'] == $value['phone'] && !empty($value['label'])) {
$newTag = array_column($value['label'], 'label_name');
}
}
$item['tags'] = $newTag;
}
}
}
}
$count = $this->dao->count(['group_id' => $chatId, 'name_like' => $name, 'status' => 1]);
return compact('list', 'count');
}
}

View File

@@ -0,0 +1,435 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\work;
use app\dao\work\WorkGroupChatDao;
use app\jobs\work\WorkClientJob;
use app\jobs\work\WorkGroupChatJob;
use app\services\BaseServices;
use crmeb\services\wechat\config\WorkConfig;
use crmeb\services\wechat\Work;
use think\annotation\Inject;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\exception\ValidateException;
/**
* Class WorkGroupChatServices
* @package app\services\work
* @mixin WorkGroupChatDao
*/
class WorkGroupChatServices extends BaseServices
{
/**
* @var WorkGroupChatDao
*/
#[Inject]
protected WorkGroupChatDao $dao;
/**
* 获取客户群群发列表
* @param array $where
* @param array $with
* @return array
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function groupChatList(array $where, array $with = [])
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->groupChat($where)->with($with)->page($page, $limit)->select()->toArray();
$count = $this->dao->groupChat($where)->count();
return compact('list', 'count');
}
/**
* 群发详情列表
* @param array $chatIds
* @return array
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function getOwnerChatList(array $chatIds, $status = null)
{
return $this->groupChatList(['chat_id' => $chatIds, 'status' => $status], [
'sendResult' => function ($query) {
$query->field(['chat_id', 'status']);
}, 'chatMember' => function ($query) {
$query->field(['count(*) as sun', 'group_id']);
}
]);
}
/**
* 同步企业微信客户群
* @param string|null $nextCursor
* @return bool
* @author 等风来
* @email 136327134@qq.com
* @date 2022/10/10
*/
public function authGroupChat(string $nextCursor = null)
{
$res = Work::getGroupChats([], 100, $nextCursor);
if (0 !== $res['errcode']) {
throw new ValidateException($res['errmsg']);
}
$groupChatList = $res['group_chat_list'] ?? [];
/** @var WorkConfig $confg */
$config = app()->make(WorkConfig::class);
$corpId = $config->corpId;
if (!$corpId) {
throw new ValidateException('请先配置企业微信ID');
}
if ($groupChatList) {
$groupChat = [];
foreach ($groupChatList as $item) {
$item['corp_id'] = $corpId;
if (($id = $this->dao->value(['chat_id' => $item['chat_id'], 'corp_id' => $corpId], 'id'))) {
$this->dao->update($id, $item);
} else {
$item['create_time'] = time();
$groupChat[] = $item;
}
}
if ($groupChat) {
$this->dao->saveAll($groupChat);
}
foreach ($groupChatList as $item) {
WorkGroupChatJob::dispatchDo('authChat', [$corpId, $item['chat_id']]);
}
//如果有下一页继续执行
if (!empty($res['next_cursor'])) {
WorkGroupChatJob::dispatchDo('authGroupChat', [$res['next_cursor']]);
}
}
return true;
}
/**
* 保存群详情
* @param string $corpId
* @param string $chatId
* @return bool
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function saveWorkGroupChat(string $corpId, string $chatId)
{
$response = Work::getGroupChat($chatId);
if (0 !== $response['errcode']) {
throw new ValidateException($response['errmsg']);
}
$groupInfo = $response['group_chat'] ?? [];
$groupInfo['admin_list'] = json_encode(array_column($groupInfo['admin_list'], 'userid'));
$memberList = $groupInfo['member_list'] ?? [];
unset($groupInfo['member_list']);
$group = $this->dao->get(['corp_id' => $corpId, 'chat_id' => $chatId]);
return $this->transaction(function () use ($chatId, $corpId, $group, $groupInfo, $memberList) {
if ($group) {
$group->name = $groupInfo['name'];
$group->owner = $groupInfo['owner'];
$group->notice = $groupInfo['notice'] ?? '';
$group->group_create_time = $groupInfo['create_time'];
$group->member_num = count($memberList);
$group->save();
} else {
$group = $this->dao->save([
'corp_id' => $corpId,
'chat_id' => $chatId,
'name' => $groupInfo['name'],
'owner' => $groupInfo['owner'],
'notice' => $groupInfo['notice'] ?? '',
'member_num' => count($memberList),
'group_create_time' => $groupInfo['create_time'],
'status' => $groupInfo['status'] ?? 0,
]);
}
$this->saveMember($memberList, $group->id, $group->member_num);
return $group->id;
});
}
/**
* @param array $where
* @return array
*/
public function getList(array $where)
{
[$page, $limit] = $this->getPageValue();
$where['timeKey'] = 'group_create_time';
$where['status'] = [0, 2, 3];
$list = $this->dao->getDataList($where, ['*'], $page, $limit, 'group_create_time', [
'ownerInfo' => function ($query) {
$query->field(['userid', 'name']);
},
]);
//提取管理员数据
$adminUserId = [];
foreach ($list as $item) {
$adminUserId = array_merge($adminUserId, $item['admin_list'] ?? []);
}
$adminUserId = array_merge(array_unique(array_filter($adminUserId)));
if ($adminUserId) {
/** @var WorkMemberServices $memberService */
$memberService = app()->make(WorkMemberServices::class);
$adminUserList = $memberService->getColumn([
['userid', 'in', $adminUserId],
], 'name', 'userid');
foreach ($list as &$item) {
$newAdminUser = [];
if (!empty($item['admin_list'])) {
foreach ($adminUserList as $key => $value) {
if (in_array($key, $item['admin_list'])) {
$newAdminUser[] = ['name' => $value, 'userid' => $key];
}
}
}
$item['admin_user_list'] = $newAdminUser;
}
}
$count = $this->dao->count();
return compact('list', 'count');
}
/**
* 企业微信客户群变动
* @param array $payload
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function updateGroupChat(array $payload)
{
$corpId = $payload['ToUserName'];
$chatId = $payload['ChatId'];
$groupInfo = $this->dao->get(['corp_id' => $corpId, 'chat_id' => $chatId]);
if (!$groupInfo) {
$groupId = $this->saveWorkGroupChat($corpId, $chatId);
$groupInfo = $this->dao->get($groupId);
}
$response = Work::getGroupChat($chatId);
if (0 !== $response['errcode']) {
throw new ValidateException($response['errmsg'] ?? '企业微信查询群详情失败');
}
$groupChat = $response['group_chat'] ?? [];
$memberList = $groupChat['member_list'];
/** @var WorkGroupChatStatisticServices $statisticService */
$statisticService = app()->make(WorkGroupChatStatisticServices::class);
$this->transaction(function () use ($payload, $groupInfo, $groupChat, $memberList, $statisticService) {
switch ($payload['UpdateDetail']) {
case 'add_member':
$groupInfo->member_num++;
$this->saveMember($memberList, $groupInfo->id, $groupInfo->member_num, true);
$statisticService->saveOrUpdate($groupInfo->id, true, false, $groupInfo->member_num, $groupInfo->retreat_group_num);
break;
case 'del_member':
$groupInfo->member_num--;
$groupInfo->retreat_group_num++;
$this->saveMember($memberList, $groupInfo->id, $groupInfo->member_num, false);
$statisticService->saveOrUpdate($groupInfo->id, false, true, $groupInfo->member_num, $groupInfo->retreat_group_num);
break;
case 'change_owner':
$groupInfo->owner = $groupChat['owner'];
break;
case 'change_name':
$groupInfo->name = $groupChat['name'];
break;
case 'change_notice':
$groupInfo->notice = $groupChat['notice'];
break;
}
if (!empty($groupChat['admin_list'])) {
$groupInfo->admin_list = json_encode(array_column($groupChat['admin_list'], 'userid'));
}
$groupInfo->save();
});
}
/**
* 保存群成员
* @param array $memberList
* @param int $groupId
* @param int $sum
* @param bool $plus
* @return mixed
*/
public function saveMember(array $memberList, int $groupId, int $sum = 0, bool $plus = false)
{
$data = [];
/** @var WorkGroupChatMemberServices $chatMemberService */
$chatMemberService = app()->make(WorkGroupChatMemberServices::class);
$newUserIds = array_column($memberList, 'userid');
$userids = $chatMemberService->getColumn(['group_id' => $groupId], 'userid');
$unUserIds = array_diff($userids, $newUserIds);
$labelList = [];
foreach ($memberList as $item) {
$item['group_id'] = $groupId;
$state = $item['state'] ?? '';
if (isset($item['state'])) {
unset($item['state']);
}
$item['invitor_userid'] = $item['invitor']['userid'] ?? '';
$unionid = $item['unionid'] ?? '';
unset($item['invitor'], $item['unionid']);
if ($chatMemberService->count(['group_id' => $groupId, 'userid' => $item['userid']])) {
$chatMemberService->update(['group_id' => $groupId, 'userid' => $item['userid']], [
'type' => $item['type'],
'unionid' => $unionid,
'chat_sum' => $sum,
'status' => 1,
'join_time' => $item['join_time'],
'join_scene' => $item['join_scene'],
'invitor_userid' => $item['invitor_userid'],
'group_nickname' => $item['group_nickname'],
]);
} else {
if ($state) {
$labelList[] = ['userid' => $item['userid'], 'type' => $item['type'], 'state' => $state];
}
$item['unionid'] = $unionid;
$item['chat_sum'] = $sum;
$item['state'] = $state;
$item['create_time'] = time();
$data[] = $item;
}
}
if ($data) {
$chatMemberService->saveAll($data);
//如果没有客户信息同步客户信息
/** @var WorkClientServices $clientService */
$clientService = app()->make(WorkClientServices::class);
$corpId = app()->make(WorkConfig::class)->corpId;
foreach ($data as $item) {
if (2 == $item['type'] && $clientService->count(['external_userid' => $item['userid']])) {
WorkClientJob::dispatchDo('saveClientInfo', [$corpId, $item['userid'], '']);
}
}
}
if ($unUserIds) {
$chatMemberService->update([
['userid', 'in', $unUserIds]
], ['status' => 0]);
}
return true;
}
/**
* 解散客户群
* @param string $corpId
* @param string $chatId
* @return mixed
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function dismissGroupChat(string $corpId, string $chatId)
{
$groupChat = $this->dao->get(['corp_id' => $corpId, 'chat_id' => $chatId], ['id']);
if (!$groupChat) {
throw new ValidateException('没有查询到群');
}
return $this->transaction(function () use ($groupChat) {
/** @var WorkGroupChatMemberServices $chatMemberService */
$chatMemberService = app()->make(WorkGroupChatMemberServices::class);
$chatMemberService->delete(['group_id' => $groupChat->id]);
return $groupChat->delete();
});
}
/**
* 群统计
* @param int $id
* @param string $time
* @return array
*/
public function getChatStatistics(int $id, string $time)
{
/** @var WorkGroupChatMemberServices $chatMemberService */
$chatMemberService = app()->make(WorkGroupChatMemberServices::class);
$data = [
'toDaySum' => $chatMemberService->getToDaySum($id),
'toDayReturn' => $chatMemberService->getToDayReturn($id),
'groupChatSum' => $this->dao->value(['id' => $id], 'member_num'),
'groupChatReturnSum' => $this->dao->value(['id' => $id], 'retreat_group_num')
];
$data['groupChatList'] = $chatMemberService->getChatMemberStatistics($id, 'join_time', ['count(*) as sum'], 1, $time);
$data['groupChatReturnList'] = $chatMemberService->getChatMemberStatistics($id, 'join_time', ['count(*) as sum'], 0, $time);
return $data;
}
/**
* 群成员统计
* @param int $id
* @param string $time
* @return array
*/
public function getChatStatisticsList(int $id, string $time)
{
[$page, $limit] = $this->getPageValue();
/** @var WorkGroupChatMemberServices $chatMemberService */
$chatMemberService = app()->make(WorkGroupChatMemberServices::class);
$newCount = $chatMemberService->getChatMemberStatisticsCount($id, 1, $time);
$returnCount = $chatMemberService->getChatMemberStatisticsCount($id, 0, $time);
$SumCount = $newCount > $returnCount ? $newCount : $returnCount;
$groupChatList = $chatMemberService->getChatMemberStatistics($id, 'join_time', ['count(*) as sum', 'chat_sum', 'retreat_chat_num'], 1, $time, $page, $limit);
$groupChatReturnList = $chatMemberService->getChatMemberStatistics($id, 'join_time', ['count(*) as retreat_sum'], 0, $time, $page, $limit);
$count = ($rCount = count($groupChatReturnList)) > ($count = count($groupChatList)) ? $rCount : $count;
$data = [];
for ($i = 0; $i < $count; $i++) {
$data[] = [
'time' => $groupChatList[$i]['time'] ?? $groupChatReturnList[$i]['time'] ?? '',
'sum' => $groupChatList[$i]['sum'] ?? 0,
'retreat_chat_num' => $groupChatList[$i]['retreat_chat_num'] ?? 0,
'chat_sum' => $groupChatList[$i]['chat_sum'] ?? 0,
'retreat_sum' => $groupChatReturnList[$i]['retreat_sum'] ?? 0,
];
}
return ['list' => $data, 'count' => $SumCount];
}
/**
* @param string $chatId
* @param string $corpId
* @return array
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function getGroupInfo(string $chatId, string $corpId)
{
$groupInfo = $this->dao->get(['chat_id' => $chatId, 'corp_id' => $corpId]);
if (!$groupInfo) {
throw new ValidateException('客户群未查到');
}
/** @var WorkGroupChatMemberServices $service */
$service = app()->make(WorkGroupChatMemberServices::class);
$groupInfo['todaySum'] = $service->getToDaySum($groupInfo->id);
$groupInfo['todayReturnSum'] = $service->getToDayReturn($groupInfo->id);
return $groupInfo->toArray();
}
}

View File

@@ -0,0 +1,83 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\work;
use app\dao\work\WorkGroupChatStatisticDao;
use app\services\BaseServices;
use think\annotation\Inject;
/**
* 群聊统计
* Class WorkGroupChatStatisticServices
* @package app\services\work
* @mixin WorkGroupChatStatisticDao
*/
class WorkGroupChatStatisticServices extends BaseServices
{
/**
* @var WorkGroupChatStatisticDao
*/
#[Inject]
protected WorkGroupChatStatisticDao $dao;
/**
* 保存或者修改
* @param int $chatId
* @param bool $todaySum
* @param bool $todayReturnSum
* @param int $chatSum
* @param int $chatReturnSum
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function saveOrUpdate(int $chatId, bool $todaySum, bool $todayReturnSum, int $chatSum, int $chatReturnSum)
{
$info = $this->dao->getToDayInfo($chatId);
if ($info) {
if ($todaySum) {
$info->today_sum++;
}
if ($todayReturnSum) {
$info->today_return_sum++;
}
$info->chat_sum = $chatSum;
$info->chat_return_sum = $chatReturnSum;
$info->save();
} else {
$this->dao->save([
'group_id' => $chatId,
'today_sum' => $todaySum ? 1 : 0,
'today_return_sum' => $todayReturnSum ? 1 : 0,
'chat_sum' => $chatSum,
'chat_return_sum' => $chatReturnSum,
]);
}
}
/**
* 群统计列表
* @param int $id
* @param string $time
* @return array
*/
public function getChatStatisticsList(int $id, string $time)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getDataList(['time' => $time, 'group_id' => $id], ['*'], $page, $limit);
$count = $this->dao->count(['time' => $time, 'group_id' => $id]);
return compact('list', 'count');
}
}

View File

@@ -0,0 +1,34 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\work;
use app\dao\work\WorkGroupMsgRelationDao;
use app\services\BaseServices;
use think\annotation\Inject;
/**
* 企业微信群发关联表
* Class WorkGroupMsgRelationServices
* @package app\services\work
* @mixin WorkGroupMsgRelationDao
*/
class WorkGroupMsgRelationServices extends BaseServices
{
/**
* @var WorkGroupMsgRelationDao
*/
#[Inject]
protected WorkGroupMsgRelationDao $dao;
}

View File

@@ -0,0 +1,48 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\work;
use app\dao\work\WorkGroupMsgSendResultGroupChatDao;
use app\services\BaseServices;
use think\annotation\Inject;
/**
* Class WorkGroupMsgSendResultGroupChatServices
* @package app\services\work
* @mixin WorkGroupMsgSendResultGroupChatDao
*/
class WorkGroupMsgSendResultGroupChatServices extends BaseServices
{
/**
* @var WorkGroupMsgSendResultGroupChatDao
*/
#[Inject]
protected WorkGroupMsgSendResultGroupChatDao $dao;
/**
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getList(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->groupChat($where)->page($page, $limit)->select()->toArray();
$count = $this->dao->groupChat($where)->count();
return compact('list', 'count');
}
}

View File

@@ -0,0 +1,123 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\work;
use app\dao\work\WorkGroupMsgSendResultDao;
use app\jobs\work\WorkGroupMsgJob;
use app\services\BaseServices;
use crmeb\services\wechat\Work;
use think\annotation\Inject;
use think\facade\Log;
/**
* 企业微信群发消息客户发送详情
* Class WorkGroupMsgSendResultServices
* @package app\services\work
* @mixin WorkGroupMsgSendResultDao
*/
class WorkGroupMsgSendResultServices extends BaseServices
{
/**
* @var WorkGroupMsgSendResultDao
*/
#[Inject]
protected WorkGroupMsgSendResultDao $dao;
/**
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getClientList(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getDataList($where, ['*'], $page, $limit, 'create_time', ['client']);
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 获取群主发送详情
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getGroupChatMsgList(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getGroupChatMsg($where)->whereNotNull('chat_id')->page($page, $limit)->select()->toArray();
$count = $this->dao->getGroupChatMsg($where)->whereNotNull('chat_id')->count();
return compact('list', 'count');
}
/**
* 获取企业群发成员执行结果
* @param int $type
* @param string $userid
* @param string $msgid
* @param string|null $cursor
* @return bool
*/
public function getSendResult(int $type, string $userid, string $msgid, string $cursor = null)
{
try {
$response = Work::getGroupmsgSendResult($msgid, $userid, 500, $cursor);
$sendList = $response['send_list'] ?? [];
foreach ($sendList as $item) {
$where = ['msg_id' => $msgid, 'userid' => $userid];
if ($type) {
$where['chat_id'] = $item['chat_id'];
} else {
$where['external_userid'] = $item['external_userid'];
}
$info = $this->dao->get($where);
if (!$info) {
$this->dao->save([
'msg_id' => $msgid,
'userid' => $item['userid'],
'chat_id' => $item['chat_id'] ?? null,
'status' => $item['status'],
'send_time' => $item['send_time'] ?? 0,
'external_userid' => $item['external_userid'] ?? null,
]);
} else {
$info->status = $item['status'];
$info->send_time = $item['send_time'] ?? 0;
$info->save();
}
}
if ($response['next_cursor']) {
WorkGroupMsgJob::dispatchDo('getSendResult', [$type, $userid, $msgid, $response['next_cursor']]);
}
return true;
} catch (\Throwable $e) {
Log::error(
json_encode([
'message' => '获取企业群发成员执行结果失败:' . $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)
);
return false;
}
}
}

View File

@@ -0,0 +1,116 @@
<?php
namespace app\services\work;
use app\dao\work\WorkGroupMsgTaskDao;
use app\jobs\work\WorkGroupMsgJob;
use app\services\BaseServices;
use crmeb\services\wechat\Work;
use think\annotation\Inject;
use think\facade\Log;
/**
* 企业微信群发消息任务
* Class WorkGroupMsgTaskServices
* @package app\services\work
* @mixin WorkGroupMsgTaskDao
*/
class WorkGroupMsgTaskServices extends BaseServices
{
/**
* @var WorkGroupMsgTaskDao
*/
#[Inject]
protected WorkGroupMsgTaskDao $dao;
/**
* 获取成员列表
* @param array $where
* @return array
*/
public function getTaksList(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getDataList($where, ['*'], $page, $limit, 'send_time', [
'member', 'sendResult' => function ($query) use ($where) {
$query->whereIn('msg_id', $where['msg_id'])->field(['userid', 'count(*) as num_count'])->group('userid');
}
]);
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 获取群发成员发送任务列表
* @param int $type
* @param string $msgid
* @param string|null $cursor
* @return bool
*/
public function getTaks(int $type, string $msgid, string $cursor = null)
{
try {
$response = Work::getGroupmsgTask($msgid, 500, $cursor);
$taskList = $response['task_list'] ?? [];
foreach ($taskList as $item) {
$info = $this->dao->get(['msg_id' => $msgid, 'userid' => $item['userid']]);
if (!$info) {
$info = $this->dao->save([
'msg_id' => $msgid,
'userid' => $item['userid'],
'status' => $item['status'],
'create_time' => time(),
'send_time' => $item['send_time'] ?? 0
]);
} else {
$info->status = $item['status'];
$info->send_time = $item['send_time'] ?? 0;
$info->save();
}
WorkGroupMsgJob::dispatchDo('getSendResult', [$type, $item['userid'], $msgid, null]);
}
if ($response['next_cursor']) {
WorkGroupMsgJob::dispatchDo('getTaks', [$type, $msgid, $cursor]);
}
return true;
} catch (\Throwable $e) {
Log::error(
json_encode([
'message' => '获取群发成员发送任务列表失败:' . $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)
);
return false;
}
}
/**
* 统计群发模板数据
* @param array $msgIds
* @return array
*/
public function getSendMsgStatistics(array $msgIds, int $type = 0)
{
$data = [];
$data['user_count'] = $data['unuser_count'] = $data['external_user_count'] = $data['external_unuser_count'] = 0;
$data['user_count'] = $this->dao->count(['msg_id' => $msgIds, 'status' => 2]);
$data['unuser_count'] = $this->dao->count(['msg_id' => $msgIds, 'status' => 0]);
/** @var WorkGroupMsgSendResultServices $service */
$service = app()->make(WorkGroupMsgSendResultServices::class);
if ($type) {
$data['external_user_count'] = $service->count(['msg_id' => $msgIds, 'notChatId' => true, 'status' => 1]);
$data['external_unuser_count'] = $service->count(['msg_id' => $msgIds, 'notChatId' => true, 'status' => [0, 2, 3]]);
} else {
$data['external_user_count'] = $service->count(['msg_id' => $msgIds, 'status' => 1]);
$data['external_unuser_count'] = $service->count(['msg_id' => $msgIds, 'status' => [0, 2, 3]]);
}
return $data;
}
}

View File

@@ -0,0 +1,334 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\work;
use app\dao\work\WorkGroupTemplateDao;
use app\jobs\work\WorkGroupMsgJob;
use app\services\BaseServices;
use crmeb\services\wechat\WechatResponse;
use crmeb\traits\service\ContactWayQrCode;
use think\annotation\Inject;
use think\exception\ValidateException;
use think\facade\Event;
use think\facade\Log;
/**
* 企业微信群发模板
* Class WorkGroupTemplateServices
* @package app\services\work
* @mixin WorkGroupTemplateDao
*/
class WorkGroupTemplateServices extends BaseServices
{
use ContactWayQrCode;
/**
* @var WorkGroupTemplateDao
*/
#[Inject]
protected WorkGroupTemplateDao $dao;
/**
* 获取群聊模板列表
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getGroupTemplate(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getDataList($where, ['*'], $page, $limit, 'create_time', ['msgIds']);
/** @var WorkGroupMsgTaskServices $taskService */
$taskService = app()->make(WorkGroupMsgTaskServices::class);
foreach ($list as &$item) {
$item['user_count'] = $item['unuser_count'] = $item['external_user_count'] = $item['external_unuser_count'] = 0;
if (!empty($item['msgIds'])) {
$msgIds = [];
foreach ($item['msgIds'] as $value) {
$msgIds[] = $value['msg_id'];
if ($value['msg_id']) {
WorkGroupMsgJob::dispatchDo('getTaks', [(int)$item['type'], $value['msg_id'], null]);
}
}
$item = array_merge($item, $taskService->getSendMsgStatistics($msgIds, (int)$item['type']));
}
}
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 创建或者修改
* @param array $data
*/
public function saveGroupTemplate(array $data)
{
$this->checkWelcome($data['welcome_words'], 0);
$this->transaction(function () use ($data) {
$res = $this->dao->save($data);
//立即发送或者选择的时间小于当前时间
if (!$data['template_type'] || ($data['template_type'] == 1 && $data['send_time'] < time())) {
if ($data['type']) {
WorkGroupMsgJob::dispatchDo('batch', [$res->id, '', 0]);
} else {
foreach ($data['userids'] as $key => $userid) {
WorkGroupMsgJob::dispatchDo('batch', [$res->id, $userid, $key + 1]);
}
}
}
});
}
/**
* 批量发送
* @param int $id
* @param string $userId
* @param int $count
* @return bool
*/
public function batch(int $id, string $userId, int $count)
{
try {
$groupTempInfo = $this->dao->get($id);
if (!$groupTempInfo) {
return true;
}
if ($groupTempInfo->send_type == 1) {
return true;
}
$groupTempInfo = $groupTempInfo->toArray();
$externalUserid = [];
if (!$groupTempInfo['type']) {
/** @var WorkClientServices $service */
$service = app()->make(WorkClientServices::class);
$where = ['userid' => $userId];
if ($groupTempInfo['client_type']) {
//条件筛选
if ($groupTempInfo['where_time']) {
$where['time'] = $groupTempInfo['where_time'];
$where['timeKey'] = 'create_time';
}
if ($groupTempInfo['where_label']) {
$where['label'] = $groupTempInfo['where_label'];
}
if ($groupTempInfo['notLabel']) {
$where['notLabel'] = $groupTempInfo['where_not_label'];
}
}
$externalUserid = $service->getClientUserIds($where);
}
$this->sendTask($id, $externalUserid, $groupTempInfo, $count);
return true;
} catch (\Throwable $e) {
Log::error(
json_encode([
'message' => '创建群发任务失败:' . $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)
);
//记录发送失败原因
try {
$this->dao->update($id, ['send_type' => -1, 'fail_message' => '创建群发任务失败:' . $e->getMessage()]);
} catch (\Throwable $e) {
}
return true;
}
}
/**
* @param int $id
* @param array $externalUserid
* @param array $data
* @param int $count
*/
public function sendTask(int $id, array $externalUserid, array $data, int $count)
{
$update = [];
/** @var WorkGroupMsgRelationServices $msgRelationService */
$msgRelationService = app()->make(WorkGroupMsgRelationServices::class);
if ($data['type']) {
//群主群发
$failList = [];
$msgIdData = [];
foreach ($data['userids'] as $item) {
$res = $this->sendMsgTemplate([], $data['welcome_words'], 'group', $item);
$failList = array_merge($failList, $res['fail_list'] ?? []);
if (isset($res['msgid'])) {
$msgIdData[] = $res['msgid'];
}
}
$update['send_type'] = 1;
if ($failList) {
$update['fail_external_userid'] = json_encode($failList);
}
if ($update) {
$this->dao->update($id, $update);
}
$msgRelation = [];
foreach ($msgIdData as $msgId) {
$msgRelation[] = ['template_id' => $id, 'msg_id' => $msgId];
WorkGroupMsgJob::dispatchSece(60, 'getTaks', [(int)$data['type'], $msgId, null]);
}
$msgRelationService->saveAll($msgRelation);
} else {
//成员群发
$sendTemplateWelcome = $this->sendMsgTemplate($externalUserid, $data['welcome_words']);
$failList = $sendTemplateWelcome['fail_list'] ?? [];
if ($failList) {
$update['fail_external_userid'] = json_encode($failList);
}
if ($count == count($data['userids'])) {
$update['send_type'] = 1;
} else {
$update['send_type'] = 2;
}
if ($update) {
$this->dao->update($id, $update);
}
$msgRelationService->save(['template_id' => $id, 'msg_id' => $sendTemplateWelcome['msgid']]);
WorkGroupMsgJob::dispatchSece(60, 'getTaks', [(int)$data['type'], $sendTemplateWelcome['msgid'], null]);
}
}
/**
* 群发任务详情
* @param int $id
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getGroupTemplateInfo(int $id)
{
$info = $this->dao->get($id, ['*'], ['msgIds']);
if (!$info) {
throw new ValidateException('没有查到此数据');
}
$msgIds = [];
$info = $info->toArray();
if (!empty($info['msgIds'])) {
$msgIds = array_column($info['msgIds'], 'msg_id');
}
$info['user_count'] = $info['unuser_count'] = $info['external_user_count'] = $info['external_unuser_count'] = 0;
$info['user_list'] = [];
if ($info['userids']) {
/** @var WorkMemberServices $service */
$service = app()->make(WorkMemberServices::class);
$info['user_list'] = $service->getColumn([
['userid', 'in', $info['userids']]
], 'userid,name');
}
if ($msgIds) {
/** @var WorkGroupMsgTaskServices $taskService */
$taskService = app()->make(WorkGroupMsgTaskServices::class);
$info = array_merge($info, $taskService->getSendMsgStatistics($msgIds, (int)$info['type']));
}
return $info;
}
/**
* 删除群发模板
* @param int $id
* @return bool
*/
public function deleteGroupTemplate(int $id)
{
/** @var WorkGroupMsgTaskServices $taskService */
$taskService = app()->make(WorkGroupMsgTaskServices::class);
/** @var WorkGroupMsgRelationServices $msgRelationService */
$msgRelationService = app()->make(WorkGroupMsgRelationServices::class);
/** @var WorkGroupMsgSendResultServices $sendResultService */
$sendResultService = app()->make(WorkGroupMsgSendResultServices::class);
$msgIds = $msgRelationService->getColumn(['template_id' => $id], 'msg_id');
$this->transaction(function () use ($id, $msgRelationService, $msgIds, $taskService, $sendResultService) {
$this->dao->delete($id);
if ($msgIds) {
$taskService->delete(['msg_id' => $msgIds]);
$sendResultService->delete(['msg_id' => $msgIds]);
}
});
return true;
}
/**
* 执行定时发送群发内容
*/
public function cornHandle()
{
$time = time();
$list = $this->dao->getDataList(['send_time' => $time, 'send_type' => 0], ['*'], 0, 0, null, ['msgIds']);
foreach ($list as $item) {
if ($item['type']) {
WorkGroupMsgJob::dispatchDo('batch', [$item['id'], '', 0]);
} else {
foreach ($item['userids'] as $count => $userid) {
WorkGroupMsgJob::dispatchDo('batch', [$item['id'], $userid, $count + 1]);
}
}
}
}
/**
* 发送应用消息
* @param int $id
* @param string $userid
* @param string $sendTime
* @return WechatResponse
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function sendMessage(int $id, string $userid, string $sendTime)
{
if ($id) {
$template = $this->dao->get(['id' => $id], ['userids', 'type', 'name', 'create_time']);
if (!$template) {
throw new ValidateException('没有查到群发模板');
}
$template = $template->toArray();
if (isset($template[0]['userid'])) {
$template['userids'] = array_column($template['userids'], 'userid');
}
$userids = $template['userids'];
$task = $template['type'] ? '客户群群发任务' : '客户群发任务';
$text = "【任务提醒】有新的任务啦!\n" .
"任务类型:{$task}\n" .
"任务名称:{$template['name']}\n" .
"创建时间:{$template['create_time']}\n" .
"可前往【群发助手】中确认发送,记得及时完成哦\n";
} else {
$userids = [$userid];
$text = "【任务提醒】有新的任务啦!\n" .
"任务类型:客户群发任务\n" .
"创建时间:{$sendTime}\n" .
"可前往【群发助手】中确认发送,记得及时完成哦\n";
}
$res = Event::until('work.message', [
'text', $text, ['toUser' => $userids], []
]);
if ($res === false) {
throw new ValidateException('发送消息失败');
}
}
}

View File

@@ -0,0 +1,34 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\work;
use app\dao\work\WorkLabelDao;
use app\services\BaseServices;
use think\annotation\Inject;
/**
* 企业微信标签
* Class WorkLabelServices
* @package app\services\\work
* @mixin WorkLabelDao
*/
class WorkLabelServices extends BaseServices
{
/**
* @var WorkLabelDao
*/
#[Inject]
protected WorkLabelDao $dao;
}

View File

@@ -0,0 +1,206 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\work;
use app\dao\work\WorkMediaDao;
use app\services\BaseServices;
use crmeb\basic\BaseModel;
use crmeb\services\FileService;
use crmeb\services\wechat\Work;
use EasyWeChat\Kernel\Exceptions\InvalidConfigException;
use GuzzleHttp\Exception\GuzzleException;
use think\annotation\Inject;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\exception\ValidateException;
use think\Model;
/**
* 企业微信素材
* Class WorkMediaServices
* @package app\services\work
* @mixin WorkMediaDao
*/
class WorkMediaServices extends BaseServices
{
/**
* @var WorkMediaDao
*/
#[Inject]
protected WorkMediaDao $dao;
/**
* 获取附件资源
* @param string $url
* @param string $type
* @param int $uploadType
* @return array|bool
* @throws DataNotFoundException
* @throws DbException
* @throws GuzzleException
* @throws InvalidConfigException
* @throws ModelNotFoundException
*/
public function getPathMediaInfo(string $url, string $type, int $uploadType = 0)
{
$pathInfo = parse_url($url);
$path = $pathInfo['path'];
$mediaInfo = [];
$md5Path = md5($path);
$info = $this->dao->get(['md5_path' => $md5Path, 'type' => $type, 'upload_type' => $uploadType], ['media_id', 'url', 'temporary']);
if ($info) {
if ($info->temporary && $info->media_id) {
$mediaInfo = $info->toArray();
}
if (((int)$info->temporary) === 0) {
$mediaInfo = $info->toArray();
}
}
if (!$mediaInfo) {
if (str_starts_with($path, '/')) {
$path = substr($path, 1);
}
$pathUrl = public_path() . $path;
if (is_file($pathUrl)) {
$uploadInfo = $this->mediaUpload($uploadType, $pathUrl, $type, $md5Path, $info);
$mediaInfo['media_id'] = $uploadInfo['media_id'];
} else {
//获取文件内容
$stream = file_get_contents($url);
//创建文件路径
$dir = public_path() . 'uploads' . DS . 'temp';
try {
FileService::mkDir($dir);
} catch (\Throwable $e) {
throw new ValidateException($e->getMessage());
}
//把文件流保存到本地
$pathUrl = $dir . DS . basename($url);
file_put_contents($pathUrl, $stream);
//上传到素材附件
$uploadInfo = $this->mediaUpload($uploadType, $pathUrl, $type, $md5Path, $info);
unlink($pathUrl);
$mediaInfo['media_id'] = $uploadInfo['media_id'];
}
}
return $mediaInfo;
}
/**
* 上传临时素材
* @param int $uploadType
* @param string $pathUrl
* @param string $type
* @param string $md5Path
* @param $info
* @return BaseModel|Model
* @throws GuzzleException
* @throws InvalidConfigException
*/
public function mediaUpload(int $uploadType, string $pathUrl, string $type, string $md5Path, $info)
{
if ($uploadType) {
$resMedia = Work::mediaUploadAttachment($pathUrl, $type);
} else {
$resMedia = Work::mediaUpload($pathUrl, $type);
}
if ($info) {
$info->media_id = $resMedia['media_id'];
$info->valid_time = (int)$resMedia['created_at'] + 60 * 60 * 24 * 3 - 60;//3天有效期
$info->save();
return $info;
} else {
return $this->dao->save([
'path' => $pathUrl,
'md5_path' => $md5Path,
'type' => $type,
'upload_type' => $uploadType,
'media_id' => $resMedia['media_id'],
'valid_time' => (int)$resMedia['created_at'] + 60 * 60 * 24 * 3 - 60,//3天有效期
'create_time' => time(),
'temporary' => 1
]);
}
}
/**
* 获取欢迎语
* @param array $welcome
* @return array
* @throws DataNotFoundException
* @throws DbException
* @throws GuzzleException
* @throws InvalidConfigException
* @throws ModelNotFoundException
*/
public function resolvingWelcome(array $welcome, int $uploadType = 0)
{
//清除过期的附件
$this->dao->deleteValidFile();
$attachments = [];
foreach ($welcome['attachments'] as $item) {
switch ($item['msgtype']) {
case 'image':
$mediaInfo = $this->getPathMediaInfo($item['image']['pic_url'], 'image', $uploadType);
if (!empty($mediaInfo['media_id'])) {
$item['image']['media_id'] = $mediaInfo['media_id'];
unset($item['image']['pic_url']);
$attachments[] = $item;
}
break;
case 'link':
break;
case 'miniprogram':
$mediaInfo = $this->getPathMediaInfo($item['miniprogram']['pic_url'], 'image', $uploadType);
if (!empty($mediaInfo['media_id'])) {
$item['miniprogram']['pic_media_id'] = $mediaInfo['media_id'];
unset($item['miniprogram']['pic_url']);
$attachments[] = $item;
}
break;
case 'video':
$mediaInfo = $this->getPathMediaInfo($item['video']['url'], 'video', $uploadType);
if (!empty($mediaInfo['media_id'])) {
$item['video']['media_id'] = $mediaInfo['media_id'];
unset($item['video']['url']);
$attachments[] = $item;
}
break;
case 'file':
$mediaInfo = $this->getPathMediaInfo($item['file']['url'], 'file', $uploadType);
if (!empty($mediaInfo['media_id'])) {
$item['file']['media_id'] = $mediaInfo['media_id'];
unset($item['file']['url']);
$attachments[] = $item;
}
break;
}
}
return [
'text' => [
'content' => $welcome['text']['content'],
],
'attachments' => $attachments
];
}
}

View File

@@ -0,0 +1,33 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\work;
use app\dao\work\WorkMemberOtherDao;
use app\services\BaseServices;
use think\annotation\Inject;
/**
* Class WorkMemberOtherServices
* @package app\services\\work
* @mixin WorkMemberOtherDao
*/
class WorkMemberOtherServices extends BaseServices
{
/**
* @var WorkMemberOtherDao
*/
#[Inject]
protected WorkMemberOtherDao $dao;
}

View File

@@ -0,0 +1,62 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\work;
use app\dao\work\WorkMemberRelationDao;
use app\services\BaseServices;
use think\annotation\Inject;
/**
* 企业微信成员关联表
* Class WorkMemberRelationServices
* @package app\services\\work
* @mixin WorkMemberRelationDao
*/
class WorkMemberRelationServices extends BaseServices
{
/**
* @var WorkMemberRelationDao
*/
#[Inject]
protected WorkMemberRelationDao $dao;
/**
* 添加成员部门
* @param int $memberId
* @param string $isLeaderInDept
* @param string $department
* @return mixed
*/
public function saveMemberDepartment(int $memberId, string $isLeaderInDept, string $department)
{
$res = true;
if ($department) {
$department = explode(',', $department);
$isLeaderInDept = $isLeaderInDept ? explode(',', $isLeaderInDept) : [];
$departmentData = [];
for ($i = 0; $i < count($department); $i++) {
$departmentData[] = [
'department' => $department[$i],
'is_leader_in_dept' => $isLeaderInDept[$i] ?? 0,
'member_id' => $memberId,
'create_time' => time()
];
}
$this->dao->delete(['member_id' => $memberId]);
$res = $this->dao->saveAll($departmentData);
}
return $res;
}
}

View File

@@ -0,0 +1,428 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\work;
use app\dao\work\WorkMemberDao;
use app\jobs\work\WorkMemberJob;
use app\services\BaseServices;
use crmeb\services\wechat\config\WorkConfig;
use crmeb\services\wechat\Work;
use think\annotation\Inject;
use think\exception\ValidateException;
use think\facade\Log;
use think\helper\Str;
/**
* 企业微信成员
* Class WorkMemberServices
* @package app\services\work
* @mixin WorkMemberDao
*/
class WorkMemberServices extends BaseServices
{
const TABLE_FIELD = ['Name', 'MainDepartment', 'DirectLeader',
'Mobile', 'Position', 'Gender', 'Email', 'BizMail', 'Status', 'Avatar', 'Alias',
'Telephone', 'Address'];
/**
* @var WorkMemberDao
*/
#[Inject]
protected WorkMemberDao $dao;
/**
* 员工列表
* @param array $where
* @return array
*/
public function getList(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getDataList($where, ['*'], $page, $limit, 'create_time', [
'departmentRelation',
'clientFollow' => function ($query) {
$query->group('userid')->where('is_del_user', 0)->field(['count(*) as sum_follow', 'userid']);
},
'chat' => function ($query) {
$query->group('userid')->field(['count(*) as sum_chat', 'userid']);
},
]);
$departmentIds = [];
foreach ($list as &$item) {
if (!empty($item['departmentRelation'])) {
$item['departmentId'] = array_column($item['departmentRelation'], 'department');
} else {
$item['departmentId'] = [];
}
$departmentIds = array_merge($departmentIds, $item['departmentId']);
}
$departmentIds = array_merge(array_unique(array_filter($departmentIds)));
if ($departmentIds) {
/** @var WorkDepartmentServices $services */
$services = app()->make(WorkDepartmentServices::class);
$departmentList = $services->getColumn([
['department_id', 'in', $departmentIds],
], 'name', 'department_id');
foreach ($list as &$item) {
$department = [];
foreach ($departmentList as $k => $v) {
if (in_array($k, $item['departmentId'])) {
$department[] = ['name' => $v, 'department' => $k];
}
}
$item['department_list'] = $department;
}
}
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 保存成员数据
* @param array $members
* @return bool
*/
public function saveMember(array $members)
{
/** @var WorkConfig $config */
$config = app()->make(WorkConfig::class);
$corpId = $config->corpId;
if (!$corpId) {
return true;
}
/** @var WorkDepartmentServices $departmentService */
$departmentService = app()->make(WorkDepartmentServices::class);
$defaultDepartment = $departmentService->value(['corp_id' => $corpId, 'parentid' => 0], 'department_id');
$this->transaction(function () use ($members, $corpId, $defaultDepartment) {
$data = [];
$relation = [];
$other = [];
$userids = array_column($members, 'userid');
foreach ($members as $member) {
if (isset($member['english_name'])) {
unset($member['english_name']);
}
$address = $bizMail = '';
if (isset($member['address'])) {
$address = $member['address'];
unset($member['address']);
}
if (isset($member['biz_mail'])) {
$bizMail = $member['biz_mail'];
unset($member['biz_mail']);
}
$member['address'] = $address;
$member['biz_mail'] = $bizMail;
if (isset($member['extattr']) && $member['extattr']) {
$other[$member['userid']] = [
'extattr' => json_encode($member['extattr'] ?? []),
'external_profile' => json_encode($member['external_profile'] ?? []),
];
}
if (!empty($member['department'])) {
foreach ($member['department'] as $i => $department) {
$relation[$member['userid']][] = [
'department' => $member['department'][$i] ?? 0,
'srot' => $member['order'][$i] ?? 0,
'is_leader_in_dept' => $member['is_leader_in_dept'][$i] ?? 0
];
}
} else {
//写入默认部门
$relation[$member['userid']][] = ['department' => $defaultDepartment, 'srot' => 0, 'is_leader_in_dept' => 0];
}
$externalPosition = '';
if (isset($member['external_position'])) {
$externalPosition = $member['external_position'];
unset($member['external_position']);
}
$member['external_position'] = $externalPosition;
$member['direct_leader'] = json_encode($member['direct_leader']);
$member['is_leader'] = $member['isleader'];
$member['corp_id'] = $corpId;
if (isset($member['external_profile'])) {
unset($member['external_profile']);
}
unset($member['isleader'], $member['is_leader_in_dept'], $member['order'], $member['department'], $member['extattr']);
if ($this->dao->count(['userid' => $member['userid'], 'corp_id' => $corpId])) {
$this->dao->update(['userid' => $member['userid']], $member);
} else {
$member['create_time'] = time();
$data[] = $member;
}
}
//写入成员数据
if ($data) {
$this->dao->saveAll($data);
}
$userList = $this->dao->getColumn([['userid', 'in', $userids], ['corp_id', '=', $corpId]], 'id', 'userid');
$userValueAll = array_values($userList);
//写入关联数据
if (count($relation)) {
/** @var WorkMemberRelationServices $relationService */
$relationService = app()->make(WorkMemberRelationServices::class);
$relationService->delete([['member_id', 'in', $userValueAll]]);
$saveRelation = [];
foreach ($relation as $userid => $item) {
$memberId = $userList[$userid];
foreach ($item as $value) {
$saveRelation[] = [
'member_id' => $memberId,
'create_time' => time(),
'department' => $value['department'],
'srot' => $value['srot'],
'is_leader_in_dept' => $value['is_leader_in_dept'],
];
}
}
$relationService->saveAll($saveRelation);
}
//写入其他数据
if (count($other)) {
/** @var WorkMemberOtherServices $otherService */
$otherService = app()->make(WorkMemberOtherServices::class);
$otherService->delete([['member_id', 'in', $userValueAll]]);
foreach ($other as $userid => &$item) {
$memberId = $userList[$userid];
$item['member_id'] = $memberId;
}
$otherService->saveAll($other);
}
});
return true;
}
/**
* 自动更新企业成员
* @param int $departmentId
*/
public function authUpdataMember(int $departmentId)
{
$res = Work::getDetailedDepartmentUsers($departmentId);
$members = $res['userlist'] ?? [];
$maxCount = 500;
$sumCount = count($members);
if ($sumCount > $maxCount) {
$page = ceil($maxCount / $sumCount);
for ($i = 1; $i < $page; $i++) {
$res = collect($members)->slice($maxCount * $i, $maxCount)->toArray();
WorkMemberJob::dispatchDo('save', [$res]);
}
} else {
$this->saveMember($members);
}
}
/**
* 获取提交字段
* @param array $payload
* @return array
*/
protected function getTableField(array $payload)
{
$data = [];
foreach (self::TABLE_FIELD as $key) {
$strKey = Str::snake($key);
if (isset($payload[$strKey])) {
$data[$strKey] = $payload[$strKey];
}
}
return $data;
}
/**
* 更新企业成员
* @param array $payload
* @return mixed
*/
public function updateMember(array $payload)
{
$corpId = $payload['ToUserName'] ?? '';
$userId = $payload['UserID'] ?? '';
$updateData = $this->getTableField($payload);
if (!empty($payload['NewUserID'])) {
$updateData['userid'] = $payload['NewUserID'];
}
$memberInfo = Work::getMemberInfo($userId);
if (0 !== $memberInfo['errcode']) {
throw new ValidateException($memberInfo['errmsg']);
}
$extattr = $memberInfo['extattr'] ?? [];
$externalProfile = $memberInfo['external_profile'] ?? [];
unset($memberInfo['errcode'], $memberInfo['errmsg'], $memberInfo['department'],
$memberInfo['order'], $memberInfo['is_leader_in_dept'], $memberInfo['extattr'],
$memberInfo['external_profile']);
$updateData = array_merge($updateData, $memberInfo);
$memberId = $this->dao->value(['userid' => $userId], 'id');
if ($memberId) {
if ($updateData) {
$dbCorpId = $this->dao->value(['userid' => $userId], 'corp_id');
if (!$dbCorpId) {
$updateData['corp_id'] = $corpId;
}
$this->dao->update(['corp_id' => $corpId, 'userid' => $userId], $updateData);
}
} else {
if (!empty($payload['NewUserID'])) {
$updateData['userid'] = $payload['NewUserID'];
}
$res = $this->dao->save($updateData);
$memberId = $res->id;
}
/** @var WorkMemberRelationServices $relationServices */
$relationServices = app()->make(WorkMemberRelationServices::class);
$relationServices->saveMemberDepartment($memberId, $payload['IsLeaderInDept'] ?? '', $payload['Department'] ?? '');
//写入其他数据
if (!empty($extattr['attrs']) || !empty($externalProfile)) {
/** @var WorkMemberOtherServices $otherService */
$otherService = app()->make(WorkMemberOtherServices::class);
$otherInfo = $otherService->get(['member_id' => $memberId]);
if ($otherInfo) {
$otherInfo->extattr = json_encode($extattr);
$otherInfo->external_profile = json_encode($externalProfile);
$otherInfo->save();
} else {
$otherService->save([
'member_id' => $memberId,
'extattr' => json_encode($extattr),
'external_profile' => json_encode($externalProfile),
]);
}
}
return $memberId;
}
/**
* 创建企业微信成员
* @param array $payload
* @return mixed
*/
public function createMember(array $payload)
{
$corpId = $payload['ToUserName'] ?? '';
if (!$corpId) {
/** @var WorkConfig $config */
$config = app()->make(WorkConfig::class);
$corpId = $config->corpId;
}
$userId = $payload['UserID'] ?? '';
$data = $this->getTableField($payload);
$memberInfo = Work::getMemberInfo($userId);
if (0 !== $memberInfo['errcode']) {
throw new ValidateException($memberInfo['errmsg']);
}
$extattr = $memberInfo['extattr'] ?? [];
$externalProfile = $memberInfo['external_profile'] ?? [];
unset($memberInfo['errcode'], $memberInfo['errmsg'], $memberInfo['department'],
$memberInfo['order'], $memberInfo['is_leader_in_dept'], $memberInfo['extattr'],
$memberInfo['external_profile']);
$data = array_merge($data, $memberInfo);
$memberId = $this->dao->value(['userid' => $userId], 'id');
if ($memberId) {
if ($data) {
$dbCorpId = $this->dao->value(['userid' => $userId], 'corp_id');
if (!$dbCorpId) {
$data['corp_id'] = $corpId;
}
$this->dao->update(['userid' => $userId], $data);
}
} else {
$data['corp_id'] = $corpId;
$res = $this->dao->save($data);
$memberId = $res->id;
}
//记录
$isLeaderInDept = $payload['IsLeaderInDept'] ?? '';
$department = $payload['Department'] ?? '';
if (!$department && !$isLeaderInDept) {
//写入主部门
/** @var WorkDepartmentServices $departmentService */
$departmentService = app()->make(WorkDepartmentServices::class);
$id = $departmentService->value(['corp_id' => $corpId, 'parentid' => 0], 'department_id');
if ($id) {
$department = (string)$id;
$isLeaderInDept = '0';
}
}
/** @var WorkMemberRelationServices $relationServices */
$relationServices = app()->make(WorkMemberRelationServices::class);
$relationServices->saveMemberDepartment($memberId, $isLeaderInDept, $department);
//写入其他数据
if (!empty($extattr['attrs']) || !empty($externalProfile)) {
/** @var WorkMemberOtherServices $otherService */
$otherService = app()->make(WorkMemberOtherServices::class);
$otherInfo = $otherService->get(['member_id' => $memberId]);
if ($otherInfo) {
$otherInfo->extattr = json_encode($extattr);
$otherInfo->external_profile = json_encode($externalProfile);
$otherInfo->save();
} else {
$otherService->save([
'member_id' => $memberId,
'extattr' => json_encode($extattr),
'external_profile' => json_encode($externalProfile),
]);
}
}
return $memberId;
}
/**
* 删除企业微信成员
* @param string $corpId
* @param string $userid
*/
public function deleteMember(string $corpId, string $userid)
{
$memberId = $this->dao->value(['corp_id' => $corpId, 'userid' => $userid], 'id');
if ($memberId) {
$this->transaction(function () use ($memberId) {
/** @var WorkMemberRelationServices $relationServices */
$relationServices = app()->make(WorkMemberRelationServices::class);
$relationServices->delete(['member_id' => $memberId]);
/** @var WorkMemberOtherServices $otherServices */
$otherServices = app()->make(WorkMemberOtherServices::class);
$otherServices->delete(['member_id' => $memberId]);
$this->dao->delete($memberId);
});
}
}
/**
* 用户注销解绑用户
* @param int $uid
*/
public function unboundUser(int $uid)
{
try {
$this->dao->update(['uid' => $uid], ['uid' => 0]);
} catch (\Throwable $e) {
Log::error(
json_encode([
'message' => '解绑用户失败:' . $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)
);
}
}
}

View File

@@ -0,0 +1,149 @@
<?php
namespace app\services\work;
use app\dao\work\WorkMomentSendResultDao;
use app\jobs\work\WorkMomentJob;
use app\services\BaseServices;
use crmeb\services\wechat\Work;
use think\annotation\Inject;
use think\facade\Log;
/**
* Class WorkMomentSendResultServices
* @package app\services\work
* @mixin WorkMomentSendResultDao
*/
class WorkMomentSendResultServices extends BaseServices
{
/**
* @var WorkMomentSendResultDao
*/
#[Inject]
protected WorkMomentSendResultDao $dao;
/**
* @param array $where
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getList(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getDataList($where, ['*'], $page, $limit, 'create_time', ['member']);
$externalUserid = [];
foreach ($list as $item) {
$externalUserid = array_merge($externalUserid, $item['external_userid']);
}
if ($externalUserid) {
$externalUserid = array_merge(array_unique(array_filter($externalUserid)));
/** @var WorkClientServices $clientService */
$clientService = app()->make(WorkClientServices::class);
$externalUserList = $clientService->getColumn([
['external_userid', 'in', $externalUserid]
], 'name', 'external_userid');
foreach ($list as &$item) {
$item['external_user_list'] = [];
foreach ($externalUserList as $k => $v) {
if (in_array($k, $item['external_userid'])) {
$item['external_user_list'][] = $v;
}
}
}
}
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 获取任务执行详情
* @param string $momentId
* @param string $cursor
* @return bool
*/
public function getTaskInfo(string $momentId, string $cursor = '')
{
try {
$resInfo = Work::getMomentTaskInfo($momentId, $cursor, 100);
$taskList = $resInfo['task_list'] ?? [];
foreach ($taskList as $item) {
$res = $this->dao->get(['user_id' => $item['userid'], 'moment_id' => $momentId]);
if (!$res) {
$res = $this->dao->save([
'user_id' => $item['userid'],
'status' => $item['publish_status'],
'moment_id' => $momentId,
'create_time' => time()
]);
} else {
if ($res->status != $item['publish_status']) {
$res->status = $item['publish_status'];
$res->save();
}
}
WorkMomentJob::dispatchDo('getCustomerPage', [$res->id, $momentId, $item['userid'], '']);
}
if ($resInfo['next_cursor']) {
WorkMomentJob::dispatchDo('getTaskPage', [$momentId, $resInfo['next_cursor']]);
}
} catch (\Throwable $e) {
Log::error(
json_encode([
'message' => '获取任务执行详情失败:' . $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)
);
return false;
}
return true;
}
/**
* 获取某个人发送朋友圈详情
* @param int $id
* @param string $momentId
* @param string $userId
* @param string $cursor
* @return bool
*/
public function getCustomerList(int $id, string $momentId, string $userId, string $cursor = '')
{
try {
$resInfo = Work::getMomentCustomerList($momentId, $userId, $cursor, 1000);
$externalUserids = array_column($resInfo['customer_list'], 'external_userid');
if ($externalUserids) {
$value = $this->dao->value(['id' => $id], 'external_userid');
$value = $value ? (is_array($value) ? $value : json_decode($value, true)) : [];
$value = array_merge($value, $externalUserids);
$this->dao->update($id, ['external_userid' => $value]);
}
if ($resInfo['next_cursor']) {
WorkMomentJob::dispatchDo('getCustomerPage', [$id, $momentId, $userId, $resInfo['next_cursor']]);
}
} catch (\Throwable $e) {
Log::error(
json_encode([
'message' => '获取某个人发送朋友圈详情失败:' . $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)
);
return false;
}
return true;
}
}

View File

@@ -0,0 +1,228 @@
<?php
namespace app\services\work;
use app\dao\work\WorkMomentDao;
use app\jobs\work\WorkMomentJob;
use app\services\BaseServices;
use crmeb\services\wechat\Work;
use crmeb\traits\service\ContactWayQrCode;
use think\annotation\Inject;
use think\exception\ValidateException;
use think\facade\Log;
/**
* 创建发布朋友圈
* Class WorkMomentServices
* @package app\services\work
* @mixin WorkMomentDao
*/
class WorkMomentServices extends BaseServices
{
use ContactWayQrCode;
/**
* @var WorkMomentDao
*/
#[Inject]
protected WorkMomentDao $dao;
/**
* @param array $where
* @return array
*/
public function getList(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getDataList($where, ['*'], $page, $limit, 'create_time', [
'sendResult' => function ($query) {
$query->field(['status', 'moment_id']);
}
]);
$userIds = $userList = [];
foreach ($list as $v) {
$userIds = array_merge($userIds, $v['user_ids']);
}
if ($userIds) {
$userIds = array_merge(array_unique(array_filter($userIds)));
/** @var WorkMemberServices $service */
$service = app()->make(WorkMemberServices::class);
$userList = $service->getColumn([
['userid', 'in', $userIds]
], 'name', 'userid');
}
foreach ($list as &$item) {
$item['user_count'] = $item['unuser_count'] = $userCount = $unuserCount = 0;
if (!empty($item['sendResult'])) {
foreach ($item['sendResult'] as $value) {
if ($value['status']) {
$userCount++;
} else {
$unuserCount++;
}
}
}
$item['user_count'] = $userCount;
$item['unuser_count'] = $unuserCount;
$item['user_list'] = [];
foreach ($userList as $k => $v) {
if (in_array($k, $item['user_ids'])) {
$item['user_list'][] = $v;
}
}
}
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 添加发送朋友圈任务
* @param array $data
* @return mixed
*/
public function createMomentTask(array $data)
{
$this->checkWelcome($data['welcome_words'], 0);
return $this->transaction(function () use ($data) {
$res = $this->dao->save($data);
//立即发送或者过了发送时间直接发送
if (!$data['send_type'] || ($data['send_type'] == 1 && $data['send_time'] < time())) {
$this->sendMomentTask($res->id, $data['welcome_words'], $data['type'] ? $data['user_ids'] : [], $data['type'] ? $data['client_tag_list'] : []);
}
return $res;
});
}
/**
* 发送
* @param int $id
* @param array $content
* @param array $userIds
* @param array $tags
*/
public function sendMomentTask(int $id, array $content, array $userIds, array $tags = [])
{
$resTask = $this->addMomentTask($content, $userIds, $tags);
$this->dao->update($id, ['jobid' => $resTask['jobid']]);
WorkMomentJob::dispatchSece(60 * 3, 'task', [$resTask['jobid']]);
}
/**
* 获取异步任务执行结果
* @param string $jobId
* @return bool
*/
public function getTaskInfo(string $jobId)
{
try {
$res = Work::getMomentTask($jobId);
if ($res['status'] !== 3) {
WorkMomentJob::dispatchSece(60 * 3, 'task', [$jobId]);
return true;
}
$this->dao->update(['jobid' => $jobId], [
'moment_id' => $res['result']['moment_id'],
'invalid_sender_list' => isset($res['result']['invalid_sender_list']) ? json_encode($res['result']['invalid_sender_list']) : null,
'invalid_external_contact_list' => isset($res['result']['invalid_external_contact_list']) ? json_encode($res['result']['invalid_external_contact_list']) : null,
]);
//执行获取任务详情
WorkMomentJob::dispatchDo('getTaskPage', [$res['result']['moment_id'], '']);
return true;
} catch (\Throwable $e) {
Log::error(
json_encode([
'message' => '获取异步任务结果失败:' . $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)
);
return false;
}
}
/**
* 获取发送朋友圈详情
* @param int $id
* @return array
*/
public function getMomentInfo(int $id)
{
$info = $this->dao->get($id);
if (!$info) {
throw new ValidateException('没有查询到此朋友圈信息');
}
$userCount = $sendUserCount = $unSendUserCount = $externalUserCount = 0;
if ($info->moment_id) {
/** @var WorkMomentSendResultServices $service */
$service = app()->make(WorkMomentSendResultServices::class);
$userCount = $service->count(['moment_id' => $info->moment_id]);
$sendUserCount = $service->count(['moment_id' => $info->moment_id, 'status' => 1]);
$unSendUserCount = $service->count(['moment_id' => $info->moment_id, 'status' => 0]);
$list = $service->getMomentList(['moment_id' => $info->moment_id, 'status' => 1], ['external_userid']);
$externalUserid = [];
foreach ($list as $item) {
$externalUserid = array_merge($externalUserid, $item['external_userid']);
}
$externalUserCount = count($externalUserid);
if ($info->moment_id) {
//执行获取任务详情
WorkMomentJob::dispatchDo('getTaskPage', [$info->moment_id, '']);
}
}
$info = $info->toArray();
$info['info'] = compact('userCount', 'sendUserCount', 'unSendUserCount', 'externalUserCount');
return $info;
}
/**
* @param int $id
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function deleteMoment(int $id)
{
$info = $this->dao->get($id);
if (!$info) {
throw new ValidateException('没有查询到此朋友圈信息');
}
/** @var WorkMomentSendResultServices $service */
$service = app()->make(WorkMomentSendResultServices::class);
return $this->transaction(function () use ($info, $id, $service) {
if ($info->moment_id) {
$service->delete(['moment_id' => $info->moment_id]);
}
return $info->delete();
});
}
/**
* 执行定时任务发送朋友圈
*/
public function cronHandle()
{
$time = time();
$list = $this->dao->getDataList(['send_time' => $time, 'send_type' => 1, 'jobid_null' => true]);
foreach ($list as $item) {
$this->sendMomentTask($item['id'], $item['welcome_words'], $item['type'] ? $item['user_ids'] : [], $item['type'] ? $item['client_tag_list'] : []);
}
}
}

View File

@@ -0,0 +1,33 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\work;
use app\dao\work\WorkWelcomeRelationDao;
use app\services\BaseServices;
use think\annotation\Inject;
/**
* Class WorkWelcomeRelationServices
* @package app\services\work
* @mixin WorkWelcomeRelationDao
*/
class WorkWelcomeRelationServices extends BaseServices
{
/**
* @var WorkWelcomeRelationDao
*/
#[Inject]
protected WorkWelcomeRelationDao $dao;
}

View File

@@ -0,0 +1,180 @@
<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
namespace app\services\work;
use app\dao\work\WorkWelcomeDao;
use app\services\BaseServices;
use crmeb\traits\service\ContactWayQrCode;
use think\annotation\Inject;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\exception\ValidateException;
/**
* 企业微信欢迎语
* Class WorkWelcomeServices
* @package app\services\\work
* @mixin WorkWelcomeDao
*/
class WorkWelcomeServices extends BaseServices
{
use ContactWayQrCode;
/**
* @var WorkWelcomeDao
*/
#[Inject]
protected WorkWelcomeDao $dao;
/**
* 获取列表
* @param array $where
* @return array
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function getList(array $where)
{
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getDataList($where, ['*'], $page, $limit, ['sort', 'create_time'], [
'userList' => function ($query) {
$query->with([
'member' => function ($query) {
$query->field(['userid', 'name']);
}
]);
}
]);
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 获取欢迎语
* @param int $id
* @return array
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function getWelcomeInfo(int $id)
{
$welcomeInfo = $this->dao->get($id, ['*'], [
'userList' => function ($query) {
$query->with([
'member' => function ($query) {
$query->field(['userid', 'name']);
}
]);
}
]);
if (!$welcomeInfo) {
throw new ValidateException('没有查到欢迎语');
}
return $welcomeInfo->toArray();
}
/**
* 保存欢迎语
* @param array $data
* @param int $id
* @return bool
*/
public function saveWelcome(array $data, int $id = 0)
{
$userids = $data['userids'];
unset($data['userids']);
$this->checkWelcome(['text' => ['content' => $data['content']], 'attachments' => $data['attachments']], 0);
/** @var WorkWelcomeRelationServices $relationServices */
$relationServices = app()->make(WorkWelcomeRelationServices::class);
$this->transaction(function () use ($data, $id, $relationServices, $userids) {
if ($id) {
$this->dao->update($id, $data);
} else {
$res = $this->dao->save($data);
$id = $res->id;
}
$userData = [];
foreach ($userids as $userid) {
$userData[] = [
'welcome_id' => $id,
'userid' => $userid
];
}
$relationServices->delete(['welcome_id' => $id]);
if ($userData) {
$relationServices->saveAll($userData);
}
});
return true;
}
/**
* 获取欢迎语
* @param string $userId
* @return array
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function getWorkWelcome(string $userId)
{
if ($userId) {
/** @var WorkWelcomeRelationServices $service */
$service = app()->make(WorkWelcomeRelationServices::class);
$welcomeIds = $service->getColumn(['userid' => $userId], 'welcome_id');
if ($welcomeIds) {
$welcomeList = $this->dao->getDataList(['id' => $welcomeIds], ['*'], 1, 1, ['sort' => 'desc', 'create_time' => 'desc']);
$welcomeWords = $welcomeList[0] ?? [];
}
}
if (empty($welcomeWords)) {
$welcomeList = $this->dao->getDataList(['type' => 0], ['*'], 1, 1, ['sort' => 'desc', 'create_time' => 'desc']);
$welcomeWords = $welcomeList[0] ?? [];
}
return [
'text' => [
'content' => $welcomeWords['content'] ?? '',
],
'attachments' => $welcomeWords['attachments'] ?? [],
];
}
/**
* 删除欢迎语
* @param int $id
* @return bool
*/
public function deleteWelcome(int $id)
{
/** @var WorkWelcomeRelationServices $relationServices */
$relationServices = app()->make(WorkWelcomeRelationServices::class);
$this->transaction(function () use ($id, $relationServices) {
$relationServices->delete(['welcome_id' => $id]);
$this->dao->delete($id);
});
return true;
}
}