Initial commit: queue workspace

Made-with: Cursor
This commit is contained in:
apple
2026-03-21 02:55:24 +08:00
commit 78de918c37
12388 changed files with 1840126 additions and 0 deletions

View File

@@ -0,0 +1,229 @@
<?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\kefu;
use app\services\BaseServices;
use app\dao\message\service\StoreServiceDao;
use app\services\message\service\StoreServiceAuxiliaryServices;
use app\services\message\service\StoreServiceServices;
use app\services\user\UserServices;
use app\services\wechat\WechatUserServices;
use app\webscoket\SocketPush;
use think\annotation\Inject;
use think\exception\ValidateException;
use app\services\message\service\StoreServiceLogServices;
use app\services\message\service\StoreServiceRecordServices;
use think\facade\Log;
/**
* Class KefuServices
* @package app\services\kefu
* @mixin StoreServiceDao
*/
class KefuServices extends BaseServices
{
const GECJUROK = 'gElnUk';
/**
* @var StoreServiceDao
*/
#[Inject]
protected StoreServiceDao $dao;
/**
* 获取客服列表
* @param array $where
* @param array $noId
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getServiceList(array $where, array $noId)
{
$where['status'] = 1;
$where['account_status'] = 1;
$where['noId'] = $noId;
$where['online'] = 1;
[$page, $limit] = $this->getPageValue();
$list = $this->dao->getServiceList($where, $page, $limit);
$count = $this->dao->count($where);
return compact('list', 'count');
}
/**
* 获取聊天记录
* @param int $uid
* @param int $toUid
* @param int $upperId
* @param int $is_tourist
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getChatList(int $uid, int $toUid, int $upperId, int $is_tourist = 0)
{
/** @var StoreServiceLogServices $service */
$service = app()->make(StoreServiceLogServices::class);
[$page, $limit] = $this->getPageValue();
return array_reverse($service->tidyChat($service->getServiceChatList(['chat' => [$uid, $toUid], 'is_tourist' => $is_tourist], $limit, $upperId)));
}
/**
* 转移客服
* @param int $kfuUid
* @param int $uid
* @param int $toUid
* @return mixed
*/
public function setTransfer(int $kfuUid, int $uid, int $kfuToUid)
{
if ($uid === $kfuToUid) {
throw new ValidateException('自己不能转接给自己');
}
/** @var StoreServiceAuxiliaryServices $auxiliaryServices */
$auxiliaryServices = app()->make(StoreServiceAuxiliaryServices::class);
/** @var StoreServiceLogServices $service */
$service = app()->make(StoreServiceLogServices::class);
$addTime = $auxiliaryServices->value(['binding_id' => $kfuUid, 'relation_id' => $uid], 'update_time');
$list = $service->getMessageList(['chat' => [$kfuUid, $uid], 'add_time' => $addTime]);
$data = [];
foreach ($list as $item) {
if ($item['to_uid'] == $kfuUid) {
$item['to_uid'] = $kfuToUid;
}
if ($item['uid'] == $kfuUid) {
$item['uid'] = $kfuToUid;
}
$item['add_time'] = time();
unset($item['id']);
$data[] = $item;
}
$record = $this->transaction(function () use ($data, $service, $kfuUid, $uid, $kfuToUid, $auxiliaryServices) {
if ($data) {
$num = count($data) - 1;
$messageData = $data[$num] ?? [];
$res = $service->saveAll($data);
} else {
$num = 0;
$res = true;
$messageData = [];
}
/** @var StoreServiceRecordServices $serviceRecord */
$serviceRecord = app()->make(StoreServiceRecordServices::class);
$info = $serviceRecord->get(['user_id' => $kfuUid, 'to_uid' => $uid], ['type', 'message_type', 'is_tourist', 'avatar', 'nickname']);
$record = $serviceRecord->saveRecord($uid, $kfuToUid, $messageData['msn'] ?? '', $info['type'] ?? 1, $messageData['message_type'] ?? 1, $num, $info['is_tourist'] ?? 0, $info['nickname'] ?? "", $info['avatar'] ?? '');
$res = $res && $auxiliaryServices->saveAuxliary(['binding_id' => $kfuUid, 'relation_id' => $uid]);
if (!$res && !$record) {
throw new ValidateException('转接客服失败');
}
return $record;
});
try {
if (!$record['is_tourist']) {
/** @var UserServices $userService */
$userService = app()->make(UserServices::class);
$_userInfo = $userService->getUserInfo($uid, 'nickname,avatar');
$record['nickname'] = $_userInfo['nickname'];
$record['avatar'] = $_userInfo['avatar'];
}
$keufInfo = $this->dao->get(['uid' => $kfuUid], ['avatar', 'nickname']);
if ($keufInfo) {
$keufInfo = $keufInfo->toArray();
} else {
$keufInfo = (object)[];
}
//给转接的客服发送消息通知
SocketPush::kefu()->type('transfer')->to($kfuToUid)->data(['recored' => $record, 'kefuInfo' => $keufInfo])->push();
//告知用户对接此用户聊天
$keufToInfo = $this->dao->get(['uid' => $kfuToUid], ['avatar', 'nickname']);
SocketPush::user()->type('to_transfer')->to($uid)->data(['toUid' => $kfuToUid, 'avatar' => $keufToInfo['avatar'] ?? '', 'nickname' => $keufToInfo['nickname'] ?? ''])->push();
} catch (\Exception $e) {
}
return true;
}
/**
* 关键字回复,没有默认关键词会自动发送给客服
* @param string $reply
* @param string $openId
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function replyTransferService(string $reply, string $openId)
{
/** @var WechatUserServices $userServices */
$userServices = app()->make(WechatUserServices::class);
$userInfo = $userServices->get(['openid' => $openId], ['uid', 'nickname', 'headimgurl as avatar']);
if (!$userInfo) {
return true;
}
/** @var StoreServiceServices $kfServices */
$kfServices = app()->make(StoreServiceServices::class);
$serviceInfoList = $kfServices->getServiceList(['status' => 1, 'online' => 1]);
if (!count($serviceInfoList)) {
return true;
}
$uids = array_column($serviceInfoList['list'], 'uid');
if (!$uids) {
return true;
}
/** @var StoreServiceRecordServices $recordServices */
$recordServices = app()->make(StoreServiceRecordServices::class);
//上次聊天客服优先对话
$toUid = $recordServices->getLatelyMsgUid(['to_uid' => $userInfo['uid']], 'user_id');
//如果上次聊天的客不在当前客服中从新获取新的客服人员
if (!in_array($toUid, $uids)) {
$toUid = 0;
}
if (!$toUid) {
mt_srand();
$toUid = $uids[array_rand($uids)] ?? 0;
}
if (!$toUid) {
return true;
}
/** @var StoreServiceLogServices $logServices */
$logServices = app()->make(StoreServiceLogServices::class);
$num = $logServices->getMessageNum(['uid' => $userInfo['uid'], 'to_uid' => $toUid, 'type' => 0, 'is_tourist' => 0]);
$record = $recordServices->saveRecord($userInfo['uid'], $toUid, $reply, 1, 1, $num, 0, $userInfo['nickname'] ?? "", $userInfo['avatar'] ?? '');
$data = [
'add_time' => time(),
'is_tourist' => 0,
'to_uid' => $toUid,
'msn' => $reply,
'uid' => $userInfo['uid'],
'type' => 0
];
$data = $logServices->save($data);
$data = $data->toArray();
$data['_add_time'] = $data['add_time'];
$data['add_time'] = strtotime($data['add_time']);
$data['record'] = $record;
try {
SocketPush::kefu()->type('mssage_num')->to($toUid)->data([
'uid' => $userInfo['uid'],
'num' => $num,
'recored' => $data['record']
])->push();
SocketPush::admin()->to($toUid)->data($data)->type('reply')->push();
} catch (\Throwable $e) {
Log::error('没有开启长连接无法推送消息,消息内容为:' . $reply);
}
}
}

View File

@@ -0,0 +1,171 @@
<?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\kefu;
use crmeb\basic\BaseAuth;
use app\services\BaseServices;
use crmeb\exceptions\AuthException;
use crmeb\services\CacheService;
use app\dao\message\service\StoreServiceDao;
use crmeb\services\wechat\OfficialAccount;
use crmeb\utils\ApiErrorCode;
use think\annotation\Inject;
use think\exception\ValidateException;
use app\services\wechat\WechatUserServices;
/**
* 客服登录
* Class LoginServices
* @package app\services\kefu
* @mixin StoreServiceDao
*/
class LoginServices extends BaseServices
{
const FEPAORPL = 'OSeCVa';
/**
* @var StoreServiceDao
*/
#[Inject]
protected StoreServiceDao $dao;
/**
* 客服账号密码登录
* @param string $account
* @param string $password
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function authLogin(string $account, string $password = null)
{
$kefuInfo = $this->dao->get(['account' => $account]);
if (!$kefuInfo) {
throw new ValidateException('没有此用户');
}
if ($password && !password_verify($password, $kefuInfo->password)) {
throw new ValidateException('账号或密码错误');
}
if (!$kefuInfo->status || !$kefuInfo->account_status) {
throw new ValidateException('您已被禁止登录');
}
$token = $this->createToken($kefuInfo->id, 'kefu', $kefuInfo->password);
$kefuInfo->online = 1;
$kefuInfo->update_time = time();
$kefuInfo->ip = request()->ip();
$kefuInfo->save();
return [
'token' => $token['token'],
'exp_time' => $token['params']['exp'],
'kefuInfo' => $kefuInfo->hidden(['password', 'ip', 'update_time', 'add_time', 'status', 'mer_id', 'customer', 'notify'])->toArray()
];
}
/**
* 解析token
* @param string $token
* @return array
* @throws \Psr\SimpleCache\InvalidArgumentException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function parseToken(string $token)
{
/** @var BaseAuth $services */
$services = app()->make(BaseAuth::class);
$adminInfo = $services->parseToken($token, function ($id) {
return $this->dao->get($id);
});
if (isset($adminInfo->auth) && $adminInfo->auth !== md5($adminInfo->password)) {
throw new AuthException(ApiErrorCode::ERR_LOGIN_INVALID);
}
return $adminInfo->hidden(['password', 'ip', 'status']);
}
/**
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function wechatAuth()
{
/** @var OfficialAccount $service */
$service = app()->make(OfficialAccount::class);
$original = $service->setAccessEnd(OfficialAccount::PC)->userFromCode();
if (!$original) {
throw new ValidateException('授权失败');
}
if (!isset($original['unionid'])) {
throw new ValidateException('unionid不存在');
}
/** @var WechatUserServices $userService */
$userService = app()->make(WechatUserServices::class);
$uid = $userService->value(['unionid' => $original['unionid']], 'uid');
if (!$uid) {
throw new ValidateException('获取用户UID失败');
}
$kefuInfo = $this->dao->get(['uid' => $uid]);
if (!$kefuInfo) {
throw new ValidateException('客服不存在');
}
if (!$kefuInfo->status) {
throw new ValidateException('您已被禁止登录');
}
$token = $this->createToken($kefuInfo->id, 'kefu', $kefuInfo->password);
$kefuInfo->update_time = time();
$kefuInfo->ip = request()->ip();
$kefuInfo->save();
return [
'token' => $token['token'],
'exp_time' => $token['params']['exp'],
'kefuInfo' => $kefuInfo->hidden(['password', 'ip', 'update_time', 'add_time', 'status', 'mer_id', 'customer', 'notify'])->toArray()
];
}
/**
* 检测有没有人扫描登录
* @param string $key
* @return array|int[]
* @throws \Psr\SimpleCache\InvalidArgumentException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function scanLogin(string $key)
{
$hasKey = CacheService::has($key);
if ($hasKey === false) {
$status = 0;//不存在需要刷新二维码
} else {
$keyValue = CacheService::get($key);
if ($keyValue === '0') {
$status = 1;//正在扫描中
$kefuInfo = $this->dao->get(['uniqid' => $key], ['account', 'uniqid']);
if ($kefuInfo) {
$tokenInfo = $this->authLogin($kefuInfo->account);
$tokenInfo['status'] = 3;
$kefuInfo->uniqid = '';
$kefuInfo->save();
CacheService::delete($key);
return $tokenInfo;
}
} else {
$status = 2;//没有扫描
}
}
return ['status' => $status];
}
}

View File

@@ -0,0 +1,111 @@
<?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\kefu;
use app\services\BaseServices;
use app\services\product\product\StoreProductRelationServices;
use think\annotation\Inject;
use think\exception\ValidateException;
use app\dao\product\product\StoreProductDao;
use app\services\order\StoreOrderStoreOrderCartInfoServices;
use app\services\product\product\StoreProductVisitServices;
/**
* Class ProductServices
* @package app\services\kefu
* @mixin StoreProductDao
*/
class ProductServices extends BaseServices
{
const FMJUAVFD = 'WuJEUS';
/**
* @var StoreProductDao
*/
#[Inject]
protected StoreProductDao $dao;
/**
* 获取用户购买记录
* @param int $uid
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getProductCartList(int $uid, string $storeName = '')
{
[$page, $limit] = $this->getPageValue();
$where = [];
if (!$storeName) {//搜索不局限加入购物车|浏览
/** @var StoreOrderStoreOrderCartInfoServices $services */
$services = app()->make(StoreOrderStoreOrderCartInfoServices::class);
$where['id'] = $services->getUserCartProductIds(['uid' => $uid]);
} else {
$where['store_name'] = $storeName;
$where['pid'] = 0;
}
return $this->dao->getProductCartList($where, $page, $limit, ['id', 'IFNULL(sales,0) + IFNULL(ficti,0) as sales', 'store_name', 'image', 'stock', 'price']);
}
/**
* 获取用户浏览足记
* @param int $uid
* @return mixed
*/
public function getVisitProductList(int $uid, string $storeName = '')
{
[$page, $limit] = $this->getPageValue();
/** @var StoreProductVisitServices $service */
$service = app()->make(StoreProductVisitServices::class);
return $service->getUserVisitProductList(['uid' => $uid, 'store_name' => $storeName], $page, $limit);
}
/**
* 获取热销商品前20
* @param int $uid
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getProductHotSale(int $uid, string $storeName = '')
{
/** @var StoreOrderStoreOrderCartInfoServices $services */
$services = app()->make(StoreOrderStoreOrderCartInfoServices::class);
$productIds = $services->getUserCartProductIds(['uid' => $uid]);
/** @var StoreProductRelationServices $cateService */
$cateService = app()->make(StoreProductRelationServices::class);
$where['id'] = $cateService->cateIdByProduct($cateService->productIdByCateId($productIds));
$where['store_name'] = $storeName;
return $this->dao->getUserProductHotSale($where);
}
/**
* 获取商品详情
* @param int $id
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getProductInfo(int $id)
{
$productInfo = $this->dao->get($id, ['store_name', 'IFNULL(sales,0) + IFNULL(ficti,0) as sales', 'image',
'slider_image', 'price', 'vip_price', 'ot_price', 'stock', 'id'], ['descriptions']);
if (!$productInfo) {
throw new ValidateException('商品未查到');
}
return $productInfo->toArray();
}
}

View File

@@ -0,0 +1,91 @@
<?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\kefu;
use app\dao\user\UserDao;
use app\services\BaseServices;
use think\annotation\Inject;
use think\exception\ValidateException;
use app\services\user\UserServices as BaseUserServices;
use app\services\user\label\UserLabelServices;
use app\services\user\level\SystemUserLevelServices;
use app\services\user\label\UserLabelRelationServices;
use app\services\message\service\StoreServiceRecordServices;
/**
* Class UserServices
* @package app\services\kefu
* @mixin UserDao
*/
class UserServices extends BaseServices
{
const KXUAMBKF = '$MAO8w';
/**
* @var UserDao
*/
#[Inject]
protected UserDao $dao;
/**
* 获取用户信息
* @param int $uid
* @param int $store_id
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function getUserInfo(int $uid, int $store_id = 0)
{
/** @var StoreServiceRecordServices $kefuService */
$kefuService = app()->make(StoreServiceRecordServices::class);
if (!$kefuService->count(['to_uid' => $uid])) {
throw new ValidateException('不存在此用户');
}
$userInfo = $this->dao->get($uid, ['nickname', 'avatar', 'spread_uid', 'is_promoter', 'birthday', 'now_money', 'user_type', 'level', 'group_id', 'phone', 'is_money_level'], ['userGroup']);
if (!$userInfo) {
throw new ValidateException('用户不存在');
}
/** @var BaseUserServices $userServices */
$userServices = app()->make(BaseUserServices::class);
$userInfo['is_promoter'] = $userServices->checkUserPromoter($uid);
/** @var UserLabelRelationServices $labalServices */
$labalServices = app()->make(UserLabelRelationServices::class);
if ($store_id) {
$where = ['uid' => $uid, 'type' => 1, 'relation_id' => $store_id];
} else {
$where = ['uid' => $uid, 'type' => 0];
}
$labalId = $labalServices->getColumn($where, 'label_id', 'label_id');
/** @var UserLabelServices $services */
$services = app()->make(UserLabelServices::class);
$labelNames = $services->getColumn([['id', 'in', $labalId]], 'label_name');
$userInfo->labelNames = $labelNames;
$userInfo->spread_name = $userInfo->level_name = '';
if ($userInfo->spread_uid) {
$userInfo->spread_name = $this->dao->value(['uid' => $userInfo->spread_uid], 'nickname');
}
if ($userInfo->level) {
/** @var SystemUserLevelServices $levelService */
$levelService = app()->make(SystemUserLevelServices::class);
$userInfo->level_name = $levelService->value(['id' => $userInfo->level], 'name');
}
if ($userInfo->userGroup) {
$userInfo->group_name = $userInfo->userGroup->group_name;
unset($userInfo->userGroup);
}
return $userInfo->toArray();
}
}