Merge branch 'feature/distinct' into develop_bm
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\MessageNotificationsService;
|
||||
use app\common\model\MessageNotifications;
|
||||
use support\Request;
|
||||
|
||||
class MessageNotificationsController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new MessageNotificationsService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function indexFormat($data)
|
||||
{
|
||||
if (isset($data['created_at']))
|
||||
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function showFormat($data)
|
||||
{
|
||||
if (isset($data['created_at']))
|
||||
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
public function push(Request $request)
|
||||
{
|
||||
try {
|
||||
$data = $this->validate($this->service->getValidator(), $request->post(), 'store');
|
||||
$res = $this->service->servicePush($request, $data);
|
||||
return $this->success($res, '推送成功');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\controller\BaseController;
|
||||
use app\common\traits\crud\CrudControllerTrait;
|
||||
use app\common\service\MessageUserStatusService;
|
||||
use support\Request;
|
||||
|
||||
class MessageUserStatusController extends BaseController
|
||||
{
|
||||
use CrudControllerTrait;
|
||||
|
||||
protected $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new MessageUserStatusService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表页数据格式化
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
protected function indexFormat($data)
|
||||
{
|
||||
if (isset($data['read_at']) && $data['read_at'] !== null) {
|
||||
$data['read_at'] = date('Y-m-d H:i:s', $data['read_at']);
|
||||
} else {
|
||||
$data['read_at'] = ' - ';
|
||||
}
|
||||
|
||||
if (isset($data['message']))
|
||||
$data['message']['created_at'] = date('Y-m-d H:i:s', $data['message']['created_at']);
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function showFormat($data)
|
||||
{
|
||||
if (isset($data['read_at']) && $data['read_at'] !== null) {
|
||||
$data['read_at'] = date('Y-m-d H:i:s', $data['read_at']);
|
||||
} else {
|
||||
$data['read_at'] = ' - ';
|
||||
}
|
||||
|
||||
if (isset($data['message']))
|
||||
$data['message']['created_at'] = date('Y-m-d H:i:s', $data['message']['created_at']);
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function unReadFormat($data)
|
||||
{
|
||||
foreach ($data as &$item) {
|
||||
if (isset($item['read_at']) && $item['read_at'] !== null) {
|
||||
$item['read_at'] = date('Y-m-d H:i:s', $item['read_at']);
|
||||
} else {
|
||||
$item['read_at'] = ' - ';
|
||||
}
|
||||
|
||||
if (isset($item['message'])) {
|
||||
$item['message']['created_at'] = date('Y-m-d H:i:s', $item['message']['created_at']);
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function read(Request $request)
|
||||
{
|
||||
try {
|
||||
$data = $this->validate($this->service->getValidator(), $request->post(), 'read');
|
||||
$this->service->serviceRead($request, $data);
|
||||
return $this->success([], '操作成功');
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function listUnRead(Request $request)
|
||||
{
|
||||
try {
|
||||
$raw = $this->service->serviceListUnRead($request);
|
||||
$data = $this->transformData($raw, method_exists($this, 'unReadFormat')
|
||||
? [$this, 'unReadFormat']
|
||||
: null);
|
||||
$data = [
|
||||
'list' => $data,
|
||||
'counts' => count($data)
|
||||
];
|
||||
return $this->success($data);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\MessageNotifications;
|
||||
|
||||
class MessageNotificationsLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = MessageNotifications::class;
|
||||
|
||||
|
||||
public function logicDeleteBy($config)
|
||||
{
|
||||
$msg = $this->logicShow($config);
|
||||
if ($msg) {
|
||||
// 删除关联的用户状态记录
|
||||
$msg->userStatuses()->delete();
|
||||
// 删除消息本身
|
||||
$msg->delete();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\logic;
|
||||
|
||||
use app\common\traits\crud\CrudLogicTrait;
|
||||
use app\common\model\MessageUserStatus;
|
||||
|
||||
class MessageUserStatusLogic
|
||||
{
|
||||
use CrudLogicTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = MessageUserStatus::class;
|
||||
|
||||
}
|
||||
@@ -60,9 +60,13 @@ class InquiryLog extends Model
|
||||
return $this->hasOne(User::class, 'id', 'enter_user_id');
|
||||
}
|
||||
|
||||
// public function recipientInfo()
|
||||
// {
|
||||
// return $this->hasOne(User::class, 'id', 'recipient')->where('status', 1);
|
||||
// }
|
||||
public function recipientInfo()
|
||||
{
|
||||
return $this->hasOne(User::class, 'id', 'recipient')->where('status', 1);
|
||||
return $this->hasOne(User::class, 'id', 'recipient');
|
||||
}
|
||||
|
||||
public function channelInfo()
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
class MessageNotifications extends Model
|
||||
{
|
||||
/**
|
||||
* The connection name for the model.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $connection = 'mysql';
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'message_notifications';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = false;
|
||||
|
||||
// const UPDATED_AT = false;
|
||||
|
||||
// protected $dateFormat = 'U';
|
||||
|
||||
/**
|
||||
* 允许自动填充模型字段
|
||||
*/
|
||||
protected $fillable = ['id', 'msg_no', 'sender_id', 'sender_type', 'recipient', 'title', 'content', 'scene', 'push_method', 'created_at'];
|
||||
|
||||
/**
|
||||
* 不允许自动填充模型字段
|
||||
*/
|
||||
protected $guarded = [];
|
||||
|
||||
// 消息创建
|
||||
const SCENE = [
|
||||
self::SCENE0 => '系统消息',
|
||||
self::SCENE1 => '系统通知'
|
||||
];
|
||||
const SCENE0 = 0;
|
||||
const SCENE1 = 1;
|
||||
|
||||
// 推送方式
|
||||
const PUSH_METHOD = [
|
||||
self::PUSH_METHOD0 => '系统推送',
|
||||
self::PUSH_METHOD1 => '手动推送'
|
||||
];
|
||||
const PUSH_METHOD0 = 0;
|
||||
const PUSH_METHOD1 = 1;
|
||||
|
||||
|
||||
/**
|
||||
* 关联用户消息状态 (一对多)
|
||||
*/
|
||||
public function userStatuses()
|
||||
{
|
||||
return $this->hasMany(MessageUserStatus::class, 'msg_id', 'id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已读用户状态
|
||||
*/
|
||||
public function readUsers()
|
||||
{
|
||||
return $this->hasMany(MessageUserStatus::class, 'msg_id', 'id')
|
||||
->where('status', 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取未读用户状态
|
||||
*/
|
||||
public function unreadUsers()
|
||||
{
|
||||
return $this->hasMany(MessageUserStatus::class, 'msg_id', 'id')
|
||||
->where('status', 0);
|
||||
}
|
||||
|
||||
public function sender()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'sender_id', 'id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
class MessageUserStatus extends Model
|
||||
{
|
||||
/**
|
||||
* The connection name for the model.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $connection = 'mysql';
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'message_user_status';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = false;
|
||||
|
||||
|
||||
// protected $dateFormat = 'U';
|
||||
|
||||
/**
|
||||
* 允许自动填充模型字段
|
||||
*/
|
||||
protected $fillable = ['id', 'msg_id', 'uid', 'status', 'read_at'];
|
||||
|
||||
/**
|
||||
* 不允许自动填充模型字段
|
||||
*/
|
||||
protected $guarded = [];
|
||||
|
||||
|
||||
/**
|
||||
* 关联消息 (多对一)
|
||||
*/
|
||||
public function message()
|
||||
{
|
||||
return $this->belongsTo(MessageNotifications::class, 'msg_id', 'id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联用户 (假设你有 User 模型)
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'uid', 'id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\common\logic\MessageNotificationsLogic;
|
||||
use app\common\validate\MessageNotificationsValidate;
|
||||
use app\common\traits\crud\CrudServiceTrait;
|
||||
use app\common\model\MessageNotifications;
|
||||
use app\common\model\MessageUserStatus;
|
||||
use app\common\model\User;
|
||||
use support\Db;
|
||||
use support\Request;
|
||||
|
||||
class MessageNotificationsService
|
||||
{
|
||||
|
||||
use CrudServiceTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = MessageNotifications::class;
|
||||
|
||||
/**
|
||||
* 验证器
|
||||
*/
|
||||
protected $validator = MessageNotificationsValidate::class;
|
||||
|
||||
/**
|
||||
* 获取验证器
|
||||
*/
|
||||
public function getValidator()
|
||||
{
|
||||
return new $this->validator;
|
||||
}
|
||||
|
||||
protected function buildQueryConfig(Request $request, string $scene = 'default'): array
|
||||
{
|
||||
$p = $request->all();
|
||||
|
||||
$config = [];
|
||||
$config['model'] = $this->modelClass;
|
||||
switch ($scene) {
|
||||
case 'index':
|
||||
$config['order']['id'] = 'desc';
|
||||
break;
|
||||
case 'show':
|
||||
$config['where']['id'] = $p['id'];
|
||||
break;
|
||||
case 'store':
|
||||
break;
|
||||
case 'update':
|
||||
$config['where']['id'] = $p['id'];
|
||||
break;
|
||||
case 'destroy':
|
||||
$config['where']['id'] = $p['id'];
|
||||
break;
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
|
||||
public function serviceDestroy(Request $request)
|
||||
{
|
||||
$config = $this->buildQueryConfig($request, 'destroy');
|
||||
$deleted = (new MessageNotificationsLogic())->logicDeleteBy($config);
|
||||
return $this->postProcess('Destroy', $deleted);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加前置处理
|
||||
*/
|
||||
public function servicePush(Request $request, $data)
|
||||
{
|
||||
$pushData = [
|
||||
'sender_id' => $request->user->uid,
|
||||
'sender_type' => 'admin',
|
||||
'title' => $data['title'],
|
||||
'content' => $data['content'],
|
||||
'scene' => 1,
|
||||
'recipient' => $data['recipient'],
|
||||
'push_method' => 1
|
||||
];
|
||||
$msgId = (new MessageNotificationsService())->pushNotice($pushData);
|
||||
return ['msg_id' => $msgId];
|
||||
}
|
||||
|
||||
public function afterServiceList($data)
|
||||
{
|
||||
$data->each(function (&$item) {
|
||||
$temp = explode(',', $item->recipient);
|
||||
$item->recipient_list = User::whereIn('id', $temp)->pluck('name')->toArray();
|
||||
$item->scene_name = MessageNotifications::SCENE[$item->scene];
|
||||
$item->push_method_name = MessageNotifications::PUSH_METHOD[$item->push_method];
|
||||
});
|
||||
unset($item);
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function afterServiceShow($data)
|
||||
{
|
||||
$temp = explode(',', $data->recipient);
|
||||
$data->recipient_list = User::whereIn('id', $temp)->pluck('name')->toArray();
|
||||
$data->scene_name = MessageNotifications::SCENE[$data->scene];
|
||||
$data->push_method_name = MessageNotifications::PUSH_METHOD[$data->push_method];
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ====== ====== ====== 通知操作的处理 ====== ====== ======
|
||||
/**
|
||||
* 推送消息
|
||||
*/
|
||||
public function pushNotice(array $data = [], string $scene = "Default"): int
|
||||
{
|
||||
// 拼装数据(可能需要处理 消息标题、内容等等数据,可自行处理)
|
||||
$pushData = $this->assemblyProcess($scene, $data);
|
||||
// 写入消息
|
||||
$msgId = $this->insertMessage($pushData);
|
||||
// 推送
|
||||
$this->pushWs($msgId, $pushData);
|
||||
return $msgId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行推送
|
||||
*/
|
||||
public function pushWs(int $msgId, array $pushData)
|
||||
{
|
||||
foreach ($pushData['recipient'] as $userId) {
|
||||
// 查询消息数据
|
||||
$config['model'] = MessageUserStatus::class;
|
||||
$config['order']['id'] = 'desc';
|
||||
$config['with']['message'] = ['sender_id', 'sender_type', 'msg_no', 'title', 'content', 'scene', 'created_at'];
|
||||
$config['where']['uid'] = $userId;
|
||||
$config['where']['msg_id'] = $msgId;
|
||||
$config['where']['status'] = 0;
|
||||
$wsData = (new MessageUserStatusService())->logicShow($config)->toArray();
|
||||
|
||||
// 组装数据
|
||||
if (isset($wsData['message']) && $wsData['message']) {
|
||||
$wsData['message']['sender_name'] = User::where('id', $wsData['message']['sender_id'])->value('name') ?: '系统';
|
||||
$wsData['message']['scene_name'] = MessageNotifications::SCENE[$wsData['message']['scene']];
|
||||
$wsData['message']['created_at'] = date('Y-m-d H:i:s', $wsData['message']['created_at']);
|
||||
}
|
||||
|
||||
// 获取未读消息数
|
||||
$counts = MessageUserStatus::where('uid', $userId)->where('status', 0)->count('id');
|
||||
$data = [
|
||||
'notice_data' => $wsData,
|
||||
'unread_counts' => $counts
|
||||
];
|
||||
|
||||
// 执行推送
|
||||
(new \app\common\utils\WsEvents())->pushWsNotice($data, $userId);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入消息
|
||||
* @param array $data
|
||||
* @return int
|
||||
*/
|
||||
public function insertMessage(array $data): int
|
||||
{
|
||||
// 写消息
|
||||
$msgId = $this->createMessage([
|
||||
'sender_id' => $data['sender_id'],
|
||||
'sender_type' => $data['sender_type'],
|
||||
'title' => $data['title'],
|
||||
'content' => $data['content'],
|
||||
'scene' => $data['scene'],
|
||||
'recipient' => implode(',', $data['recipient']),
|
||||
'push_method' => $data['push_method'],
|
||||
]);
|
||||
// 写用户状态
|
||||
$this->addUserStatus($msgId, $data['recipient']);
|
||||
return $msgId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据拼装方法调用
|
||||
*/
|
||||
protected function assemblyProcess(string $op, mixed $data): mixed
|
||||
{
|
||||
$hook = "assembly{$op}";
|
||||
return method_exists($this, $hook)
|
||||
? $this->{$hook}($data)
|
||||
: $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认数据拼装
|
||||
*/
|
||||
public function assemblyDefault(array $data)
|
||||
{
|
||||
return [
|
||||
'sender_id' => isset($data['sender_id']) && $data['sender_id'] ? $data['sender_id'] : 0,
|
||||
'sender_type' => isset($data['sender_type']) && $data['sender_type'] ? $data['sender_type'] : 'system',
|
||||
'title' => isset($data['title']) && $data['title'] ? $data['title'] : '系统通知',
|
||||
'content' => isset($data['content']) && $data['content'] ? $data['content'] : '系统通知-默认',
|
||||
'scene' => isset($data['scene']) && $data['scene'] ? $data['scene'] : 1,
|
||||
'recipient' => isset($data['recipient']) && count($data['recipient']) > 0 ? $data['recipient'] : $this->getAllUserIds(),
|
||||
'push_method' => isset($data['push_method']) && $data['push_method'] ? $data['push_method'] : 0,
|
||||
];
|
||||
}
|
||||
|
||||
// *** *** 可以扩展数据组拼装方法 *** ***
|
||||
|
||||
// ====== ====== ====== 通知操作的基础方法 ====== ====== ======
|
||||
|
||||
/**
|
||||
* 创建一条消息
|
||||
*/
|
||||
public function createMessage(array $data): int
|
||||
{
|
||||
$msgId = Db::table('message_notifications')->insertGetId([
|
||||
'msg_no' => generateMsgId(),
|
||||
'sender_id' => $data['sender_id'],
|
||||
'sender_type' => $data['sender_type'],
|
||||
'title' => $data['title'],
|
||||
'content' => $data['content'],
|
||||
'scene' => $data['scene'],
|
||||
'recipient' => $data['recipient'],
|
||||
'push_method' => $data['push_method'],
|
||||
'created_at' => time(),
|
||||
]);
|
||||
|
||||
return $msgId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量插入用户消息状态
|
||||
*/
|
||||
public function addUserStatus(int $msgId, array $uids)
|
||||
{
|
||||
$rows = [];
|
||||
foreach ($uids as $uid) {
|
||||
$rows[] = [
|
||||
'msg_id' => $msgId,
|
||||
'uid' => $uid,
|
||||
'status' => 0,
|
||||
'read_at' => null
|
||||
];
|
||||
}
|
||||
if ($rows) {
|
||||
Db::table('message_user_status')->insert($rows);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记消息已读
|
||||
*/
|
||||
public function markAsRead(array|string $msgIds, int $uid)
|
||||
{
|
||||
$query = Db::table('message_user_status')->where('uid', $uid)->where('status', 0);
|
||||
if (is_array($msgIds)) {
|
||||
$query->whereIn('msg_id', $msgIds);
|
||||
}
|
||||
return $query->update([
|
||||
'status' => 1,
|
||||
'read_at' => time()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有用户ID
|
||||
*/
|
||||
public function getAllUserIds(): array
|
||||
{
|
||||
return User::where('status', 1)->pluck('id')->toArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\common\model\MessageNotifications;
|
||||
use app\common\validate\MessageUserStatusValidate;
|
||||
use app\common\traits\crud\CrudServiceTrait;
|
||||
use app\common\model\MessageUserStatus;
|
||||
use support\Request;
|
||||
use app\common\model\User;
|
||||
|
||||
class MessageUserStatusService
|
||||
{
|
||||
|
||||
use CrudServiceTrait;
|
||||
|
||||
/**
|
||||
* 指定当前要操作的模型
|
||||
*/
|
||||
protected string $modelClass = MessageUserStatus::class;
|
||||
|
||||
/**
|
||||
* 验证器
|
||||
*/
|
||||
protected $validator = MessageUserStatusValidate::class;
|
||||
|
||||
/**
|
||||
* 获取验证器
|
||||
*/
|
||||
public function getValidator()
|
||||
{
|
||||
return new $this->validator;
|
||||
}
|
||||
|
||||
|
||||
protected function buildQueryConfig(Request $request, string $scene = 'default'): array
|
||||
{
|
||||
$p = $request->all();
|
||||
|
||||
$config = [];
|
||||
$config['model'] = $this->modelClass;
|
||||
switch ($scene) {
|
||||
case 'unread':
|
||||
$config['order']['id'] = 'desc';
|
||||
$config['with']['message'] = ['sender_id', 'sender_type', 'msg_no', 'title', 'content', 'scene', 'created_at'];
|
||||
$config['where']['uid'] = $request->user->uid;
|
||||
$config['where']['status'] = 0;
|
||||
break;
|
||||
case 'index':
|
||||
$config['order']['id'] = 'desc';
|
||||
$config['with']['message'] = ['sender_id', 'sender_type', 'msg_no', 'title', 'content', 'scene', 'created_at'];
|
||||
$config['where']['uid'] = $request->user->uid;
|
||||
break;
|
||||
case 'show':
|
||||
$config['where']['id'] = $p['id'];
|
||||
$config['with']['message'] = ['sender_id', 'sender_type', 'msg_no', 'title', 'content', 'scene', 'created_at'];
|
||||
$config['where']['uid'] = $request->user->uid;
|
||||
break;
|
||||
case 'store':
|
||||
break;
|
||||
case 'update':
|
||||
case 'read':
|
||||
$config['where']['id'] = $p['id'];
|
||||
$config['where']['msg_id'] = $p['msg_id'];
|
||||
$config['where']['uid'] = $request->user->uid;
|
||||
break;
|
||||
case 'destroy':
|
||||
$config['where']['id'] = $p['id'];
|
||||
$config['where']['uid'] = $request->user->uid;
|
||||
break;
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
public function serviceListUnRead(Request $request)
|
||||
{
|
||||
$config = $this->buildQueryConfig($request, 'unread');
|
||||
$result = $this->logicListAll($config);
|
||||
return $this->postProcess('ListUnRead', $result);
|
||||
}
|
||||
|
||||
public function serviceRead(Request $request, array $data = [])
|
||||
{
|
||||
return $this->noticeRead($request->post('msg_ids'), $request->user->uid);
|
||||
}
|
||||
|
||||
public function noticeRead(array|string $msgIds, int $uid): bool
|
||||
{
|
||||
$model = MessageUserStatus::where('uid', $uid)->where('status', 0);
|
||||
if (is_array($msgIds)) {
|
||||
$model->whereIn('msg_id', $msgIds);
|
||||
}
|
||||
$udata['read_at'] = time();
|
||||
$udata['status'] = 1;
|
||||
return $model->update($udata);
|
||||
}
|
||||
|
||||
|
||||
protected function afterServiceList($res)
|
||||
{
|
||||
$res->each(function (&$item) {
|
||||
if (isset($item->message)) {
|
||||
$item->message->sender_name = User::where('id', $item->message->sender_id)->value('name') ?: '系统';
|
||||
$item->message->scene_name = MessageNotifications::SCENE[$item->message->scene];
|
||||
}
|
||||
});
|
||||
unset($item);
|
||||
return $res;
|
||||
}
|
||||
|
||||
protected function afterServiceShow($res)
|
||||
{
|
||||
if (isset($res->message)) {
|
||||
$res->message->sender_name = User::where('id', $res->message->sender_id)->value('name') ?: '系统';
|
||||
$res->message->scene_name = MessageNotifications::SCENE[$res->message->scene];
|
||||
}
|
||||
// 标记已读
|
||||
if ($res->status == 0) {
|
||||
$this->noticeRead([$res->msg_id], $res->uid);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
protected function afterServiceListUnRead($res)
|
||||
{
|
||||
$res->each(function (&$item) {
|
||||
if (isset($item->message)) {
|
||||
$item->message->sender_name = User::where('id', $item->message->sender_id)->value('name') ?: '系统';
|
||||
$item->message->scene_name = MessageNotifications::SCENE[$item->message->scene];
|
||||
}
|
||||
});
|
||||
unset($item);
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
@@ -18,12 +18,13 @@ trait ResponseTrait
|
||||
*/
|
||||
public function success(
|
||||
$data = [],
|
||||
string $message = '请求成功',
|
||||
int $code = 200,
|
||||
string $message = '请求成功',
|
||||
string $status = 'success',
|
||||
int $code = 200,
|
||||
int $httpStatus = 200,
|
||||
array $header = ['Content-Type' => 'application/json']
|
||||
): Response {
|
||||
return $this->result('success', $code, $httpStatus, $message, $data, $header);
|
||||
return $this->result($status, $code, $httpStatus, $message, $data, $header);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\utils;
|
||||
|
||||
use GatewayWorker\Lib\Gateway;
|
||||
use app\common\utils\WsResponse;
|
||||
use app\common\service\MessageNotificationsService;
|
||||
|
||||
class WsEvents
|
||||
{
|
||||
|
||||
private $registerAddress = '127.0.0.1:1236';
|
||||
protected $clientId;
|
||||
protected $eventData;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
Gateway::$registerAddress = $this->registerAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听消息接收
|
||||
*/
|
||||
public function message($client_id, $message)
|
||||
{
|
||||
$this->clientId = $client_id;
|
||||
|
||||
$payload = json_decode($message, true);
|
||||
$event = $payload['event'];
|
||||
$this->eventData = $payload['data'] ?? [];
|
||||
$traceId = $payload['trace_id'] ?? null;
|
||||
|
||||
$method = $this->eventToMethod($event);
|
||||
|
||||
if (method_exists($this, $method)) {
|
||||
try {
|
||||
$this->$method($traceId);
|
||||
} catch (\Throwable $th) {
|
||||
Gateway::sendToClient($client_id, WsResponse::error(
|
||||
$event,
|
||||
500,
|
||||
$th->getMessage(),
|
||||
$traceId
|
||||
));
|
||||
}
|
||||
} else {
|
||||
Gateway::sendToClient($client_id, WsResponse::error(
|
||||
$event,
|
||||
404,
|
||||
"Event handler not found: $method",
|
||||
$traceId
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ================= 系统事件 =================
|
||||
/**
|
||||
* 认证
|
||||
*/
|
||||
public function auth($client_id, $payload)
|
||||
{
|
||||
try {
|
||||
// 检查来源
|
||||
if (!isset($payload['server']['HTTP_ORIGIN'])) {
|
||||
flg('[x] [TOKEN认证] 未定义HTTP_ORIGIN', false);
|
||||
Gateway::sendToClient($client_id, WsResponse::error(
|
||||
'system.auth',
|
||||
1001,
|
||||
'[x] [TOKEN认证] 未定义HTTP_ORIGIN'
|
||||
));
|
||||
Gateway::closeClient($client_id);
|
||||
return;
|
||||
}
|
||||
|
||||
// 连接是否满足条件
|
||||
// if ($payload['server']['HTTP_ORIGIN'] != 'https://wstool.js.org') {
|
||||
// flg('[x] [TOKEN认证] HTTP_ORIGIN不满足条件', false);
|
||||
// Gateway::sendToClient($client_id, WsResponse::error(
|
||||
// 'system.auth',
|
||||
// 1001,
|
||||
// '[x] [TOKEN认证] HTTP_ORIGIN不满足条件'
|
||||
// ));
|
||||
// Gateway::closeClient($client_id);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// 签名检查
|
||||
if (!isset($payload['get']['token'])) {
|
||||
flg('[x] [TOKEN认证] 未携带TOKEN参数', false);
|
||||
Gateway::sendToClient($client_id, WsResponse::error(
|
||||
'system.auth',
|
||||
1001,
|
||||
'[x] [TOKEN认证] 未携带TOKEN参数'
|
||||
));
|
||||
Gateway::closeClient($client_id);
|
||||
return;
|
||||
}
|
||||
|
||||
// 解析TOKEN
|
||||
$user = (new Jwt())->verifyToken($payload['get']['token']);
|
||||
// 绑定用户ID
|
||||
Gateway::bindUid($client_id, $user->uid);
|
||||
|
||||
// 响应
|
||||
Gateway::sendToClient($client_id, WsResponse::success(
|
||||
'system.auth',
|
||||
['uid' => $user->uid]
|
||||
));
|
||||
} catch (\Throwable $th) {
|
||||
flg('[x] [TOKEN认证] TOKEN认证失败:'.$th->getMessage(), false);
|
||||
Gateway::sendToClient($client_id, WsResponse::error(
|
||||
'system.auth',
|
||||
1001,
|
||||
'[x] [TOKEN认证] TOKEN认证失败:'.$th->getMessage()
|
||||
));
|
||||
Gateway::closeClient($client_id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统心跳
|
||||
*
|
||||
* @param string|null $traceId
|
||||
*/
|
||||
public function systemPing(?string $traceId)
|
||||
{
|
||||
Gateway::sendToClient($this->clientId, WsResponse::success(
|
||||
'system.pong',
|
||||
['time' => time()],
|
||||
$traceId
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
// ================= 消息通知事件 =================
|
||||
/**
|
||||
* 管理端主动推送通知
|
||||
*/
|
||||
public function notifyPush(?string $traceId)
|
||||
{
|
||||
$title = $this->eventData['title'] ?? null;
|
||||
$content = $this->eventData['content'] ?? null;
|
||||
$toUids = $this->eventData['to_uids'] ?? [];
|
||||
|
||||
if (!$title || !$content) {
|
||||
Gateway::sendToClient($this->clientId, WsResponse::error(
|
||||
'notify.push.result',
|
||||
400,
|
||||
'标题和内容不能为空',
|
||||
$traceId
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
// 写消息
|
||||
$msgService = new MessageNotificationsService();
|
||||
$msgId = $msgService->createMessage([
|
||||
'sender_id' => $this->eventData['sender_id'] ?? 0,
|
||||
'sender_type' => $this->eventData['sender_type'] ?? 'system',
|
||||
'title' => $title,
|
||||
'content' => $content,
|
||||
'scene' => $this->eventData['scene'] ?? 0,
|
||||
'recipient' => $this->eventData['to_uids'] ? implode(',', $this->eventData['to_uids']) : '',
|
||||
'push_method' => isset($this->eventData['push_method']) ? $this->eventData['push_method'] : 0
|
||||
]);
|
||||
|
||||
// 写用户状态
|
||||
$uids = !empty($toUids) ? $toUids : $msgService->getAllUserIds();
|
||||
$msgService->addUserStatus($msgId, $uids);
|
||||
|
||||
$payload = WsResponse::success('notify.receive', [
|
||||
'msg_id' => $msgId,
|
||||
'title' => $title,
|
||||
'content' => $content,
|
||||
'created_at' => time()
|
||||
], $traceId);
|
||||
|
||||
if (!empty($toUids)) {
|
||||
// 推送给指定用户
|
||||
foreach ($toUids as $uid) {
|
||||
Gateway::sendToUid($uid, $payload);
|
||||
}
|
||||
} else {
|
||||
// 推送给全体在线用户
|
||||
Gateway::sendToAll($payload);
|
||||
}
|
||||
|
||||
// 回复发送者确认
|
||||
Gateway::sendToClient($this->clientId, WsResponse::success(
|
||||
'notify.push.result',
|
||||
['msg_id' => $msgId],
|
||||
$traceId,
|
||||
'消息发送成功'
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端标记通知已读
|
||||
*/
|
||||
public function notifyRead(?string $traceId)
|
||||
{
|
||||
$msgIds = $this->eventData['msg_ids'];
|
||||
if (!is_array($msgIds) && $msgIds !== 'all') {
|
||||
Gateway::sendToClient($this->clientId, WsResponse::error(
|
||||
'notify.read.result',
|
||||
400,
|
||||
'消息ID不能为空',
|
||||
$traceId
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新消息为已读
|
||||
$msgService = new MessageNotificationsService();
|
||||
$msgService->markAsRead($msgIds, Gateway::getUidByClientId($this->clientId));
|
||||
|
||||
Gateway::sendToClient($this->clientId, WsResponse::success(
|
||||
'notify.read.result',
|
||||
['msg_ids' => $msgIds],
|
||||
$traceId
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送消息通知
|
||||
*/
|
||||
public function pushWsNotice(array $wsData, int $userId)
|
||||
{
|
||||
Gateway::sendToUid($userId, WsResponse::success('notify.receive', $wsData));
|
||||
}
|
||||
|
||||
// ================= 辅助方法 =================
|
||||
protected function eventToMethod(string $event): string
|
||||
{
|
||||
$parts = explode('.', $event);
|
||||
$method = array_shift($parts);
|
||||
foreach ($parts as $part) {
|
||||
$method .= ucfirst($part);
|
||||
}
|
||||
return $method;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
namespace app\common\utils;
|
||||
|
||||
class WsResponse
|
||||
{
|
||||
/**
|
||||
* 成功返回
|
||||
*
|
||||
* @param string $event 事件名称
|
||||
* @param array $data 返回数据
|
||||
* @param string|null $traceId 请求追踪ID,可选,默认 null
|
||||
* @param int $code 错误码
|
||||
* @param string $message 错误信息
|
||||
* @return string JSON
|
||||
*/
|
||||
public static function success(string $event, array $data = [], ?string $traceId = null, string $message = 'ok', int $code = 0): string
|
||||
{
|
||||
return json_encode([
|
||||
'event' => $event,
|
||||
'code' => $code,
|
||||
'message' => $message,
|
||||
'data' => $data,
|
||||
'trace_id' => $traceId, // 如果没有传就是 null
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误返回
|
||||
*
|
||||
* @param string $event 事件名称
|
||||
* @param int $code 错误码
|
||||
* @param string $message 错误信息
|
||||
* @param string|null $traceId 请求追踪ID,可选,默认 null
|
||||
* @param array $data 附加数据(可选)
|
||||
* @return string JSON
|
||||
*/
|
||||
public static function error(string $event, int $code, string $message, ?string $traceId = null, array $data = []): string
|
||||
{
|
||||
return json_encode([
|
||||
'event' => $event,
|
||||
'code' => $code,
|
||||
'message' => $message,
|
||||
'data' => $data,
|
||||
'trace_id' => $traceId, // 如果没有传就是 null
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
}
|
||||
@@ -436,7 +436,7 @@ class Emails
|
||||
|
||||
// 初始化 UID 轮询器
|
||||
$uidRotator = new \app\common\utils\RedisUidRotator('email_parsed_result_20250804');
|
||||
$uidRotator->setUids([64, 63, 83, 93, 108, 118, 131, 132, 145, 150, 143, 156, 157, 158], false);
|
||||
$uidRotator->setUids([64, 63, 83, 93, 108, 118, 132, 145, 150, 143, 156, 157, 158], false);
|
||||
|
||||
EmailParsedResult::where('status', 0)
|
||||
->chunkById($batchSize, function ($records) use ($delaySeconds, $throttleAfter, $throttleSeconds, $enterUser, $channelInfo, $uidRotator) {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class MessageNotificationsValidate extends Validate
|
||||
{
|
||||
// 规则
|
||||
protected $rule = [
|
||||
|
||||
];
|
||||
|
||||
// 提示
|
||||
protected $message = [
|
||||
|
||||
];
|
||||
|
||||
// 场景
|
||||
protected $scene = [
|
||||
'store' => [
|
||||
|
||||
],
|
||||
|
||||
'update' => [
|
||||
|
||||
]
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class MessageUserStatusValidate extends Validate
|
||||
{
|
||||
// 规则
|
||||
protected $rule = [
|
||||
|
||||
];
|
||||
|
||||
// 提示
|
||||
protected $message = [
|
||||
|
||||
];
|
||||
|
||||
// 场景
|
||||
protected $scene = [
|
||||
'store' => [
|
||||
|
||||
],
|
||||
|
||||
'update' => [
|
||||
|
||||
]
|
||||
];
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use app\common\model\InquiryLog;
|
||||
use app\common\model\User;
|
||||
use app\common\model\UserRoles;
|
||||
use app\common\model\UserSystems;
|
||||
use app\common\service\MessageNotificationsService;
|
||||
use app\common\traits\DataPermission;
|
||||
use app\common\utils\email\MailSend;
|
||||
use app\common\utils\scaffold\Generate;
|
||||
@@ -77,25 +78,25 @@ class IndexController
|
||||
$options = [
|
||||
'controller' => [
|
||||
'module' => 'common',
|
||||
'name' => 'resources'
|
||||
'name' => 'message_notifications'
|
||||
],
|
||||
'model' => [
|
||||
'module' => 'common',
|
||||
'name' => 'resources'
|
||||
'name' => 'message_notifications'
|
||||
],
|
||||
'service' => [
|
||||
'module' => 'common',
|
||||
'name' => 'resources'
|
||||
'name' => 'message_notifications'
|
||||
],
|
||||
'logic' => [
|
||||
'module' => 'common',
|
||||
'name' => 'resources'
|
||||
'name' => 'message_notifications'
|
||||
],
|
||||
'validate' => [
|
||||
'module' => 'common',
|
||||
'name' => 'resources'
|
||||
'name' => 'message_notifications'
|
||||
],
|
||||
'table' => 'resources'
|
||||
'table' => 'message_notifications'
|
||||
];
|
||||
|
||||
$result = (new Generate($options))->doGenerate();
|
||||
@@ -107,20 +108,41 @@ class IndexController
|
||||
{
|
||||
|
||||
// $data = [
|
||||
// 'code' => 'GD20250703-1DC80',
|
||||
// 'category' => '账号类问题',
|
||||
// 'content' => '这是一条测试工单内容。'
|
||||
// 'sender_id' => 0,
|
||||
// 'sender_type' => 'system',
|
||||
// 'title' => '测试消息 - 标题',
|
||||
// 'content' => '测试消息 - 内容',
|
||||
// 'scene' => 1,
|
||||
// 'recipient' => [1],
|
||||
// ];
|
||||
// \app\common\utils\email\MailSend::send('mzl@hnxinyouhe.com', '【XYH】新的工单提醒', replaceTpl($data, 'work_order'));
|
||||
|
||||
// $options = [
|
||||
// 'corp_id' => 3,
|
||||
// 'user_id' => 666
|
||||
// ];
|
||||
// \Webman\RedisQueue\Redis::send('perm-agt', $options);
|
||||
// $data['recipient'] = [1, 74];
|
||||
// // // 同步推送
|
||||
// // (new MessageNotificationsService())->pushNotice($data);
|
||||
|
||||
$res = Cache::delete("user_resources_74_fdesk");
|
||||
|
||||
return 22;
|
||||
|
||||
// // 异步推送
|
||||
// \Webman\RedisQueue\Redis::send('notice', $data);
|
||||
|
||||
|
||||
$info = InquiryLog::with(['channelInfo', 'countryInfo', 'enter'])->where('id', 1)->where('sync_crm', 0)->first();
|
||||
|
||||
var_dump(empty($info->recipient));
|
||||
|
||||
if ($info) {
|
||||
var_dump(11);
|
||||
} else {
|
||||
var_dump(22);
|
||||
}
|
||||
|
||||
|
||||
if ($info !== NULL) {
|
||||
var_dump(33);
|
||||
} else {
|
||||
var_dump(44);
|
||||
}
|
||||
|
||||
return json(['code' => 0, 'msg' => 'success']);
|
||||
}
|
||||
}
|
||||
@@ -163,7 +163,7 @@ class CustomerController extends BaseController
|
||||
$perPage = $request->input('size', 10);
|
||||
$where = [
|
||||
['user_id', '=', $userId],
|
||||
// ['corp_abbr', '=', $request->basic_data['corp_abbr']],
|
||||
//['corp_abbr', '=', $request->basic_data['corp_abbr']],
|
||||
['follow_next', '<>', 0],
|
||||
];
|
||||
|
||||
@@ -306,6 +306,7 @@ class CustomerController extends BaseController
|
||||
$contacts = Util::getParam($request, 'contacts');
|
||||
$email = Util::getParam($request, 'email');
|
||||
$phone = Util::getParam($request, 'phone');
|
||||
$whatsApp = Util::getParam($request, 'WhatsApp', '');
|
||||
if (Util::isEmpty($contacts) && Util::isEmpty($email) && Util::isEmpty($phone)) {
|
||||
return $this->error(trans('contacts_not_null'));
|
||||
}
|
||||
@@ -314,7 +315,7 @@ class CustomerController extends BaseController
|
||||
$corpId = $corpInfo->id;
|
||||
$data = [
|
||||
'user_id' => $request->user->uid,
|
||||
'corp_abbr' => $corpId,
|
||||
'corp_id' => $corpId,
|
||||
'grade' => Util::getParam($request, 'grade', 0),
|
||||
'channel_id' => $channel_id,
|
||||
'country_id' => $country_id,
|
||||
@@ -322,7 +323,7 @@ class CustomerController extends BaseController
|
||||
'magnitude' => Util::getParam($request, 'magnitude', 0),
|
||||
'email' => $email,
|
||||
'phone' => $phone,
|
||||
'WhatsApp' => Util::getParam($request, 'WhatsApp', ''),
|
||||
'WhatsApp' => $whatsApp,
|
||||
'contacts' => $contacts,
|
||||
'describe' => Util::getParam($request, 'describe'),
|
||||
'lat' => Util::getParam($request, 'lat', 0),
|
||||
@@ -335,6 +336,15 @@ class CustomerController extends BaseController
|
||||
|
||||
if (Util::isEmpty($id)) {
|
||||
$data['enter_user_id'] = $request->user->uid;
|
||||
$distinct = [
|
||||
'email' => $email,
|
||||
'phone' => $phone,
|
||||
'whatsApp' => $whatsApp,
|
||||
];
|
||||
$dis = (new CustomerService())->distinct($distinct,$corpId);
|
||||
if($dis){
|
||||
return $this->success($dis,'客户线索重复,已经业务跟进','distinct');
|
||||
}
|
||||
$res = Customer::create($data);
|
||||
} else {
|
||||
unset($data['grade']);
|
||||
@@ -351,6 +361,25 @@ class CustomerController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重复通知
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function distinctNotice(Request $request){
|
||||
$customer_id = Util::getParam($request, 'customer_id');
|
||||
if (Util::isEmpty($customer_id)) {
|
||||
return $this->error('客户id不能为空');
|
||||
}
|
||||
$res = (new CustomerService())->distinctNotice($customer_id);
|
||||
|
||||
if ($res) {
|
||||
return $this->success($res, '成功');
|
||||
} else {
|
||||
return $this->error(trans('fail'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
@@ -506,12 +535,14 @@ class CustomerController extends BaseController
|
||||
{
|
||||
$customerId = Util::getParam($request, 'customer_id');
|
||||
$userId = $request->user->uid;
|
||||
$user = User::where(['id' => $userId])->first();
|
||||
if (Util::isEmpty($customerId)) {
|
||||
return $this->error(trans('customer_id').trans('not_null'));
|
||||
}
|
||||
$data = [
|
||||
'user_id' => $request->user->uid,
|
||||
'obtain' => time(),
|
||||
'corp_id' => $user->corp_id,
|
||||
];
|
||||
|
||||
try {
|
||||
@@ -592,6 +623,7 @@ class CustomerController extends BaseController
|
||||
$data = [
|
||||
'user_id' => $user->id,
|
||||
'obtain' => time(),
|
||||
'corp_id' => $user->corp_id,
|
||||
];
|
||||
$res = $query->update($data);
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ class FollowController extends BaseController
|
||||
$corpId = $corpInfo->id;
|
||||
$data = [
|
||||
'user_id' => $request->user->uid,
|
||||
'corp_abbr' => $corpId,
|
||||
'corp_id' => $corpId,
|
||||
'customer_id' => $customer_id,
|
||||
'type' => $type,
|
||||
'picture' => $picture,
|
||||
|
||||
@@ -36,15 +36,22 @@ class Follow extends Model
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = true;
|
||||
public $timestamps = false;
|
||||
|
||||
protected $dates = ['created_at', 'updated_at', 'follow_time'];
|
||||
/**
|
||||
* 在保存时手动设置时间戳
|
||||
*/
|
||||
protected static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
protected $casts = [
|
||||
'updated_at' => 'datetime:Y-m-d H:i:s', // 指定序列化格式
|
||||
'created_at' => 'datetime:Y-m-d H:i:s', // 指定序列化格式
|
||||
'follow_time' => 'datetime:Y-m-d H:i:s', // 指定序列化格式
|
||||
// 'updated_at' => 'timestamp', // 指定序列化格式
|
||||
// 'created_at' => 'timestamp', // 指定序列化格式
|
||||
];
|
||||
static::creating(function ($model) {
|
||||
$model->created_at = time();
|
||||
$model->updated_at = time();
|
||||
});
|
||||
|
||||
static::updating(function ($model) {
|
||||
$model->updated_at = time();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -319,4 +319,52 @@ class CustomerService
|
||||
|
||||
return $customer;
|
||||
}
|
||||
|
||||
public function distinct($params,$corpId){
|
||||
|
||||
$customer = Customer::where('corp_id',$corpId)
|
||||
->Orwhere('email', '=', $params['email'])
|
||||
->Orwhere('phone', '=', $params['phone'])
|
||||
->Orwhere('whatsApp', '=', $params['whatsApp'])
|
||||
->first();
|
||||
if (!empty($customer)) {
|
||||
//客户重复,消息通知
|
||||
// if($corpId != $customer->corp_id){
|
||||
// return false;
|
||||
// }
|
||||
return $customer;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户重复通知
|
||||
* @param $params
|
||||
* @param $corpId
|
||||
* @return Customer|false|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|object
|
||||
*/
|
||||
public function distinctNotice($customer_id){
|
||||
|
||||
$customer = Customer::where('id', $customer_id)
|
||||
->first();
|
||||
if (!empty($customer)) {
|
||||
$contacts = $customer->contacts??$customer['phone'];
|
||||
$contacts = $contacts??$customer['whatsApp'];
|
||||
$contacts = $contacts??$customer['email'];
|
||||
$data = [
|
||||
'sender_id' => 0, //发送人ID 0默认系统
|
||||
'sender_type' => 'system', //发送者类型 user admin system
|
||||
'title' => '客户跟进提醒 - ' . $contacts, //标题
|
||||
'content' => $contacts . ' 产生了新的询单,请及时跟进', //内容
|
||||
'scene' => 1, //场景 0系统公共 1系统通知
|
||||
'recipient' => [$customer->user_id], //接收人 空为全部
|
||||
'push_method' => 0 //推送方式0系统推送1手动推送
|
||||
];
|
||||
|
||||
// 异步推送
|
||||
\Webman\RedisQueue\Redis::send('notice', $data);
|
||||
return $customer;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,9 @@ class FollowService
|
||||
$oss_picture = $ossservice->generatePrivateUrl($follow['picture'], 86400);
|
||||
}
|
||||
$follow['picture_full'] = $oss_picture;
|
||||
|
||||
$follow = Util::formatDate($follow,['created_at','updated_at','follow_time']);
|
||||
$follow['follow_time'] = empty($follow['follow_time'])?'':$follow['follow_time'];
|
||||
// $follow = Util::formatDate($follow);
|
||||
return $follow;
|
||||
}
|
||||
|
||||
@@ -126,9 +126,14 @@ class Util
|
||||
|
||||
public static function mask($str)
|
||||
{
|
||||
$length = strlen($str);
|
||||
|
||||
if(empty($str)){
|
||||
return '';
|
||||
}
|
||||
if($length <= 6){
|
||||
return $str;
|
||||
}
|
||||
return substr($str, 0, 3) . '****' . substr($str, -4);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace app\crmAdmin\controller;
|
||||
|
||||
use app\common\model\Corp;
|
||||
use app\crm\model\Customer;
|
||||
use app\crm\utils\Util;
|
||||
use app\crmAdmin\service\CustomerService;
|
||||
use support\Request;
|
||||
|
||||
@@ -28,7 +29,7 @@ class CustomerController extends BaseController
|
||||
try {
|
||||
$param = $request->all();
|
||||
$corp = Corp::where(['abbr' => $request->basic_data['corp_abbr']])->first();
|
||||
$param['corp_abbr'] = $corp->id;
|
||||
$param['corp_id'] = $corp->id;
|
||||
// $param['corp_abbr'] = $request->basic_data['corp_abbr'];
|
||||
$result = (new CustomerService())->list($param);
|
||||
|
||||
@@ -48,7 +49,7 @@ class CustomerController extends BaseController
|
||||
try {
|
||||
$param = $request->all();
|
||||
// $corp = Corp::where(['abbr' => $request->basic_data['corp_abbr']])->first();
|
||||
// $param['corp_abbr'] = $corp->id;
|
||||
// $param['corp_id'] = $corp->id;
|
||||
|
||||
$result = (new CustomerService())->export($param);
|
||||
$excelContent = file_get_contents($result['filepath']);
|
||||
@@ -148,19 +149,18 @@ class CustomerController extends BaseController
|
||||
}
|
||||
|
||||
$customer_id = explode(',', $param['customer_id']);
|
||||
$customer = Customer::whereIn('id',$customer_id)->withTrashed()->first();
|
||||
if(empty($customer)){
|
||||
$query = Customer::whereIn('id',$customer_id)->withTrashed();
|
||||
if(!$query->exists()){
|
||||
return $this->error( trans('fail'));
|
||||
}
|
||||
if($param['scene'] == 'list'){ //管理端客户列表移除
|
||||
$customer->status = 4;
|
||||
|
||||
}else{ //公海软删除
|
||||
$customer->status = 3;
|
||||
|
||||
$scene = $param['scene'] ?? '';
|
||||
if ($scene === 'list') { //管理端客户列表移除
|
||||
$res = $query->update(['status' => 4, 'deleted_at' => time()]);
|
||||
} elseif ($scene === 'force') { //强删除
|
||||
$res = $query->forceDelete();
|
||||
} else { //公海软删除
|
||||
$res = $query->update(['status' => 3, 'deleted_at' => time()]);
|
||||
}
|
||||
$res = $customer->save();
|
||||
$customer->delete();
|
||||
|
||||
return $this->success($res);
|
||||
} catch (\Throwable $th) {
|
||||
@@ -168,6 +168,34 @@ class CustomerController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 客户恢复
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function restore(Request $request)
|
||||
{
|
||||
try {
|
||||
$id = $request->input('id', '');
|
||||
if (Util::isEmpty($id)) {
|
||||
return $this->error(trans('id_empty'));
|
||||
}
|
||||
$ids = explode(',', $id);
|
||||
$query = Customer::withTrashed()->whereIn('id', $ids);
|
||||
if (!$query->exists()) {
|
||||
return $this->error(trans('fail'));
|
||||
}
|
||||
// 恢复软删除(清空 deleted_at)
|
||||
$query->restore();
|
||||
// 恢复为在库状态,放入公海
|
||||
$res = Customer::whereIn('id', $ids)->update(['status' => 1, 'user_id' => 0]);
|
||||
return $this->success($res, trans('success'));
|
||||
} catch (\Throwable $th) {
|
||||
return $this->error($th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户详情
|
||||
* @param Request $request
|
||||
|
||||
@@ -51,12 +51,14 @@ class CustomerService
|
||||
$follow_last = $params['follow_last'] ?? '';
|
||||
$tagId = $params['tag_id'] ?? [];
|
||||
$user_id = $params['user_id'] ?? '';
|
||||
$seas = $params['seas'] ?? '';
|
||||
$seas = $params['seas'] ?? ''; //recyclea:管理端回收站,audit:待审核列表,seas:公海
|
||||
$scene = $params['scene'] ?? '';
|
||||
$where = [];
|
||||
|
||||
if ($scene == 'audit') {
|
||||
$where[] = ['status', '=', 2];
|
||||
} elseif ($scene == 'recyclea') {
|
||||
$where[] = ['status', '>', 2];
|
||||
} else {
|
||||
$where[] = ['status', '=', 1];
|
||||
}
|
||||
@@ -121,8 +123,8 @@ class CustomerService
|
||||
if ($recycle == 'recycle' || $scene == 'recycle' || $scene == 'audit') {
|
||||
$query->onlyTrashed();
|
||||
}
|
||||
if ($seas == 'seas' || $scene == 'seas') {
|
||||
$query->withTrashed();
|
||||
if ($seas == 'seas' || $scene == 'recyclea') {
|
||||
$query = $query->withTrashed();
|
||||
}
|
||||
|
||||
if (!$export) {
|
||||
@@ -153,6 +155,14 @@ class CustomerService
|
||||
}
|
||||
$item['audit'] = $audit;
|
||||
}
|
||||
if ($scene == 'recyclea' && $item['status'] == 3) { //回收站删除来源
|
||||
$item['del_source'] = '公海';
|
||||
}elseif ($scene == 'recyclea' && $item['status'] == 4) {
|
||||
$item['del_source'] = '管理端';
|
||||
}elseif ($scene == 'recyclea' && $item['status'] == 5) {
|
||||
$source = (!empty($item['user_id'])) ? (':' . User::where('id',$item['user_id'])->value('name')) : '';
|
||||
$item['del_source'] = '业务员' . $source;
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($item);
|
||||
@@ -329,6 +339,7 @@ class CustomerService
|
||||
|
||||
$query= Customer::whereIn('id',explode(',',$customerId));
|
||||
$customer = $query->get();
|
||||
$user = User::where(['id' => $userId])->first();
|
||||
|
||||
foreach ($customer as $item) {
|
||||
//记录客户关系变化
|
||||
@@ -347,6 +358,7 @@ class CustomerService
|
||||
}
|
||||
$data = [
|
||||
'user_id' => $userId,
|
||||
'corp_id' => $user->corp_id,
|
||||
'obtain' => time(),
|
||||
];
|
||||
$res = $query->update($data);
|
||||
@@ -469,6 +481,8 @@ class CustomerService
|
||||
|
||||
$ossservice = new OssService();
|
||||
foreach ($follow as $item) {
|
||||
$item = Util::formatDate($item,['created_at','updated_at','follow_time']);
|
||||
$item['follow_time'] = empty($item['follow_time'])?'':$item['follow_time'];
|
||||
$item['picture_full'] = empty($item->picture)?'':$ossservice->generatePrivateUrl($item->picture);
|
||||
}
|
||||
|
||||
@@ -517,14 +531,16 @@ class CustomerService
|
||||
$uidRotator = new \app\common\utils\RedisUidRotator('allocation_customer_user');
|
||||
$uidRotator->setUids($userId, false);
|
||||
|
||||
Customer::whereIn('user_id',$resignUerIds)->chunk(500, function ($customers) use ($uidRotator){
|
||||
foreach ($customers as $customer) {
|
||||
// 获取下一个分配的UID
|
||||
$newUserId = $uidRotator->next();
|
||||
$customer->user_id = $newUserId;
|
||||
$customer->save();
|
||||
}
|
||||
});
|
||||
Customer::whereIn('user_id', $resignUerIds)
|
||||
->orderBy('id', 'asc')
|
||||
->chunkById(500, function ($customers) use ($uidRotator) {
|
||||
foreach ($customers as $customer) {
|
||||
// 获取下一个分配的UID
|
||||
$newUserId = $uidRotator->next();
|
||||
$customer->user_id = $newUserId;
|
||||
$customer->save();
|
||||
}
|
||||
}, 'id');
|
||||
|
||||
|
||||
return [
|
||||
|
||||
@@ -283,4 +283,28 @@ if (!function_exists('normalizeRichText')) {
|
||||
// 替换所有空白实体为普通空格
|
||||
return preg_replace('/\s+/u', ' ', $result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 生成消息编号(唯一业务标识)
|
||||
* 格式:年月日时分秒 + 微秒 + 随机数
|
||||
* 例如:20250820175530987654321
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
if (!function_exists('generateMsgId')) {
|
||||
function generateMsgId(): string
|
||||
{
|
||||
// 时间部分(精确到微秒)
|
||||
$time = microtime(true);
|
||||
$micro = sprintf("%06d", ($time - floor($time)) * 1e6);
|
||||
$date = date('YmdHis', (int)$time);
|
||||
|
||||
// 随机部分(3位)
|
||||
$rand = mt_rand(100, 999);
|
||||
|
||||
// 拼接:日期时间 + 微秒 + 随机数
|
||||
return $date . $micro . $rand;
|
||||
}
|
||||
}
|
||||
@@ -162,6 +162,8 @@ class InquiryLogService
|
||||
$data['r_time'] = time();
|
||||
$data['t_time'] = time();
|
||||
$data['status'] = 2;
|
||||
} else {
|
||||
$data['recipient'] = 0;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
@@ -260,7 +262,7 @@ class InquiryLogService
|
||||
*/
|
||||
protected function afterServiceRelease($res)
|
||||
{
|
||||
RedisQueueRedis::send('inquiry-to-crm', $res);
|
||||
RedisQueueRedis::send('inquiry-to-crm-new', $res);
|
||||
return $res;
|
||||
}
|
||||
|
||||
@@ -269,7 +271,7 @@ class InquiryLogService
|
||||
*/
|
||||
protected function afterServiceAllocation($res)
|
||||
{
|
||||
RedisQueueRedis::send('inquiry-to-crm', $res);
|
||||
RedisQueueRedis::send('inquiry-to-crm-new', $res);
|
||||
return $res;
|
||||
}
|
||||
|
||||
@@ -278,7 +280,7 @@ class InquiryLogService
|
||||
*/
|
||||
protected function afterServiceStore($res)
|
||||
{
|
||||
RedisQueueRedis::send('inquiry-to-crm', $res);
|
||||
RedisQueueRedis::send('inquiry-to-crm-new', $res);
|
||||
return $res;
|
||||
}
|
||||
|
||||
@@ -288,7 +290,7 @@ class InquiryLogService
|
||||
protected function afterServiceUpdate($res)
|
||||
{
|
||||
if ($res['recipient']) {
|
||||
RedisQueueRedis::send('inquiry-to-crm-update', $res);
|
||||
RedisQueueRedis::send('inquiry-to-crm-new', $res);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
@@ -298,7 +300,7 @@ class InquiryLogService
|
||||
*/
|
||||
protected function afterServiceReAllocation($res)
|
||||
{
|
||||
RedisQueueRedis::send('inquiry-to-crm-update', $res);
|
||||
RedisQueueRedis::send('inquiry-to-crm-new', $res);
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,18 +37,11 @@ class InquiryToCrm implements Consumer
|
||||
if ($uinfo) {
|
||||
$corpId = $uinfo->corp_id;
|
||||
}
|
||||
// // 消息推送参数 分配
|
||||
// $scene = 'inquiry-person-allocation';
|
||||
// $receiveType = 'person';
|
||||
} else {
|
||||
// // 消息推送参数 发布
|
||||
// $scene = 'inquiry-group-send';
|
||||
// $receiveType = 'group';
|
||||
}
|
||||
// 同步客户数据
|
||||
$crmCustomer = new CrmCustomer();
|
||||
$crmCustomer->sn = $info->order_no;
|
||||
$crmCustomer->corp_abbr = $corpId;
|
||||
$crmCustomer->corp_id = $corpId;
|
||||
$crmCustomer->user_id = $recipient;
|
||||
$crmCustomer->type = 1;
|
||||
$crmCustomer->channel_id = $info->channel;
|
||||
@@ -61,6 +54,9 @@ class InquiryToCrm implements Consumer
|
||||
$crmCustomer->desc_collect = $info->content;
|
||||
$crmCustomer->enter_user_id = $info->enter_user_id;
|
||||
$crmCustomer->obtain = time();
|
||||
if(empty($recipient)){
|
||||
$crmCustomer->deleted_at = time();
|
||||
}
|
||||
// 修改同步CRM状态
|
||||
$info->sync_crm = 1;
|
||||
// 保存
|
||||
@@ -68,15 +64,26 @@ class InquiryToCrm implements Consumer
|
||||
throw new BusinessException('同步失败~');
|
||||
}
|
||||
|
||||
// 发送邮件消息
|
||||
if ($uinfo && $uinfo->email) {
|
||||
$options = [
|
||||
'id' => $info->id,
|
||||
'email' => $uinfo->email
|
||||
];
|
||||
// Log::info('发送邮件消息:' . json_encode($options));
|
||||
\Webman\RedisQueue\Redis::send('email-message', $options);
|
||||
}
|
||||
// // 发送邮件消息
|
||||
// if ($uinfo && $uinfo->email) {
|
||||
// $options = [
|
||||
// 'id' => $info->id,
|
||||
// 'email' => $uinfo->email
|
||||
// ];
|
||||
// // Log::info('发送邮件消息:' . json_encode($options));
|
||||
// \Webman\RedisQueue\Redis::send('email-message', $options);
|
||||
// }
|
||||
|
||||
$wsdata = [
|
||||
'sender_id' => 0,
|
||||
'sender_type' => 'system',
|
||||
'title' => '有新的客户线索,' . $info->order_no . ',请及时处理',
|
||||
'content' => '有新的客户线索,' . $info->order_no . ',请及时处理',
|
||||
'scene' => 1,
|
||||
'recipient' => [$info->recipient]
|
||||
];
|
||||
// 异步推送
|
||||
\Webman\RedisQueue\Redis::send('notice', $wsdata);
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
throw $th;
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
namespace app\queue\redis;
|
||||
|
||||
use app\inquiry\service\InquiryLogService;
|
||||
use app\common\model\CrmCustomer;
|
||||
use app\common\model\InquiryLog;
|
||||
use Webman\RedisQueue\Consumer;
|
||||
use app\common\model\User;
|
||||
use app\common\utils\FeishuMessage;
|
||||
use support\exception\BusinessException;
|
||||
use support\Log;
|
||||
use support\Model;
|
||||
|
||||
class InquiryToCrmNew implements Consumer
|
||||
{
|
||||
// 要消费的队列名
|
||||
public $queue = 'inquiry-to-crm-new';
|
||||
|
||||
// 连接名,对应 plugin/webman/redis-queue/redis.php 里的连接`
|
||||
public $connection = 'default';
|
||||
|
||||
// 消费
|
||||
public function consume($data)
|
||||
{
|
||||
|
||||
/*
|
||||
询单 与 CRM 之间的同步场景描述
|
||||
|
||||
添加(询单)
|
||||
添加 不发 不分 (不同步)1
|
||||
添加 发了 不分 (要同步)2
|
||||
添加 不发 分了 (要同步)3
|
||||
添加 不发 直分 (要同步)4
|
||||
|
||||
更新(询单)
|
||||
没发 没分 更新 (不同步)5
|
||||
没发 分过 更新 (要同步)6
|
||||
发过 没分 更新 (要同步)7
|
||||
发过 分过 更新 (要同步)8
|
||||
*/
|
||||
|
||||
try {
|
||||
// 获取询盘信息
|
||||
$info = InquiryLog::with(['channelInfo', 'countryInfo', 'enter'])->where('id', $data['id'])->first();
|
||||
|
||||
// 无论添加 或 更新,询单必然要存在
|
||||
if (!$info) {
|
||||
throw new BusinessException('询盘不存在:' . $data['id']);
|
||||
}
|
||||
|
||||
if ($info->status == 0 && $info->recipient == 0) {
|
||||
// 如果询单未发、未分,则不与crm同步,不做任何处理
|
||||
return true;
|
||||
}
|
||||
|
||||
// 更改库信息
|
||||
$this->syncCustomer($info);
|
||||
} catch (\Throwable $th) {
|
||||
throw $th;
|
||||
}
|
||||
}
|
||||
|
||||
public function pushWs($title, $content, $recipient = [])
|
||||
{
|
||||
$wsdata = [
|
||||
'sender_id' => 0,
|
||||
'sender_type' => 'system',
|
||||
'title' => $title,
|
||||
'content' => $content,
|
||||
'scene' => 1,
|
||||
'recipient' => $recipient
|
||||
];
|
||||
// 异步推送
|
||||
\Webman\RedisQueue\Redis::send('notice', $wsdata);
|
||||
}
|
||||
|
||||
public function syncCustomer(Model $info)
|
||||
{
|
||||
$uInfo = $this->recipientInfo($info);
|
||||
$crmCustomerInfo = CrmCustomer::where('sn', $info->order_no)->first();
|
||||
if ($crmCustomerInfo) {
|
||||
|
||||
// 消息推送
|
||||
if ($info->status == 2 && empty($crmCustomerInfo->user_id)) {
|
||||
// $notice = '分配';
|
||||
$title = '[询单·分配] 新的客户线索【' . $info->order_no . '】,请及时处理';
|
||||
$content = '[询单·分配] 新的客户线索【' . $info->order_no . '】,请及时处理';
|
||||
$recipient = [$info->recipient];
|
||||
$this->pushWs($title, $content, $recipient);
|
||||
} else {
|
||||
if ($info->status == 2 && $crmCustomerInfo->user_id == $info->recipient) {
|
||||
// $notice = '更新';
|
||||
$title = '[询单·更新] 客户线索' . $info->order_no . '有新的调整,请及时处理';
|
||||
$content = '[询单·更新] 客户线索' . $info->order_no . '有新的调整,请及时处理';
|
||||
$recipient = [$info->recipient];
|
||||
$this->pushWs($title, $content, $recipient);
|
||||
} else {
|
||||
// $notice = '重新分配';
|
||||
$title = '[询单·分配] 新的客户线索【' . $info->order_no . '】,请及时处理';
|
||||
$content = '[询单·分配] 新的客户线索【' . $info->order_no . '】,请及时处理';
|
||||
$recipient = [$info->recipient];
|
||||
$this->pushWs($title, $content, $recipient);
|
||||
}
|
||||
}
|
||||
|
||||
$udata = [
|
||||
'corp_id' => $uInfo['corpId'],
|
||||
'user_id' => $info->recipient,
|
||||
'channel_id' => $info->channel,
|
||||
'country_id' => $info->country,
|
||||
'magnitude' => $info->quality,
|
||||
'email' => $info->email,
|
||||
'phone' => $info->tel,
|
||||
'WhatsApp' => $info->whatsapp,
|
||||
'contacts' => $info->contacts,
|
||||
'desc_collect' => $info->content,
|
||||
];
|
||||
if (!$crmCustomerInfo->update($udata)) {
|
||||
throw new BusinessException('同步失败,更新客户线索失败');
|
||||
}
|
||||
} else {
|
||||
$crmCustomer = new CrmCustomer();
|
||||
$crmCustomer->sn = $info->order_no;
|
||||
$crmCustomer->corp_id = $uInfo['corpId'];
|
||||
$crmCustomer->user_id = $uInfo['recipient'];
|
||||
$crmCustomer->type = 1;
|
||||
$crmCustomer->channel_id = $info->channel;
|
||||
$crmCustomer->country_id = $info->country;
|
||||
$crmCustomer->magnitude = $info->quality;
|
||||
$crmCustomer->email = $info->email;
|
||||
$crmCustomer->phone = $info->tel;
|
||||
$crmCustomer->WhatsApp = $info->whatsapp;
|
||||
$crmCustomer->contacts = $info->contacts;
|
||||
$crmCustomer->desc_collect = $info->content;
|
||||
$crmCustomer->enter_user_id = $info->enter_user_id;
|
||||
$crmCustomer->obtain = time();
|
||||
if(empty($uInfo['recipient'])){
|
||||
$crmCustomer->deleted_at = time();
|
||||
}
|
||||
// 修改同步CRM状态
|
||||
$info->sync_crm = 1;
|
||||
// 保存
|
||||
if (!$crmCustomer->save() || !$info->save()) {
|
||||
throw new BusinessException('同步失败,创建客户线索失败');
|
||||
}
|
||||
|
||||
// 消息推送
|
||||
if ($info->status == 1) {
|
||||
// $notice = '发布';
|
||||
$title = '[询单·公海] 新的客户线索【' . $info->order_no . '】发布';
|
||||
$content = '[询单·公海] 新的客户线索【' . $info->order_no . '】发布';
|
||||
$recipient = [$info->recipient];
|
||||
$this->pushWs($title, $content);
|
||||
}
|
||||
|
||||
if ($info->status == 2) {
|
||||
// $notice = '分配';
|
||||
$title = '[询单·分配] 新的客户线索【' . $info->order_no . '】,请及时处理';
|
||||
$content = '[询单·分配] 新的客户线索【' . $info->order_no . '】,请及时处理';
|
||||
$recipient = [$info->recipient];
|
||||
$this->pushWs($title, $content, $recipient);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function recipientInfo(Model $info)
|
||||
{
|
||||
$recipient = 0;
|
||||
$corpId = 0;
|
||||
$uinfo = null;
|
||||
if (!empty($info->recipient)) {
|
||||
$recipient = $info->recipient;
|
||||
$uinfo = User::where('id', $info->recipient)->first();
|
||||
if ($uinfo) {
|
||||
$corpId = $uinfo->corp_id;
|
||||
}
|
||||
}
|
||||
return [
|
||||
'recipient' => $recipient,
|
||||
'corpId' => $corpId,
|
||||
'uinfo' => $uinfo
|
||||
];
|
||||
}
|
||||
|
||||
// 消费失败回调
|
||||
/*
|
||||
$package = [
|
||||
'id' => 1357277951, // 消息ID
|
||||
'time' => 1709170510, // 消息时间
|
||||
'delay' => 0, // 延迟时间
|
||||
'attempts' => 2, // 消费次数
|
||||
'queue' => 'send-mail', // 队列名
|
||||
'data' => ['to' => 'tom@gmail.com', 'content' => 'hello'], // 消息内容
|
||||
'max_attempts' => 5, // 最大重试次数
|
||||
'error' => '错误信息' // 错误信息
|
||||
]
|
||||
*/
|
||||
public function onConsumeFailure(\Throwable $e, $package)
|
||||
{
|
||||
echo "consume failure\n";
|
||||
echo $e->getMessage() . "\n";
|
||||
// 无需反序列化
|
||||
var_export($package);
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ class InquiryToCrmUpdate implements Consumer
|
||||
$crmCustomerInfo = CrmCustomer::where('sn', $info->order_no)->first();
|
||||
if ($crmCustomerInfo) {
|
||||
$udata = [
|
||||
'corp_abbr' => $corpId,
|
||||
'corp_id' => $corpId,
|
||||
'user_id' => $info->recipient,
|
||||
'channel_id' => $info->channel,
|
||||
'country_id' => $info->country,
|
||||
@@ -54,7 +54,7 @@ class InquiryToCrmUpdate implements Consumer
|
||||
// 同步客户数据
|
||||
$crmCustomer = new CrmCustomer();
|
||||
$crmCustomer->sn = $info->order_no;
|
||||
$crmCustomer->corp_abbr = $corpId;
|
||||
$crmCustomer->corp_id = $corpId;
|
||||
$crmCustomer->user_id = $info->recipient;
|
||||
$crmCustomer->type = 1;
|
||||
$crmCustomer->channel_id = $info->channel;
|
||||
@@ -66,6 +66,9 @@ class InquiryToCrmUpdate implements Consumer
|
||||
$crmCustomer->contacts = $info->contacts;
|
||||
$crmCustomer->desc_collect = $info->content;
|
||||
$crmCustomer->enter_user_id = $info->enter_user_id;
|
||||
if(empty($info->recipient)){
|
||||
$crmCustomer->deleted_at = time();
|
||||
}
|
||||
// 修改同步CRM状态
|
||||
$info->sync_crm = 1;
|
||||
// 保存
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace app\queue\redis;
|
||||
|
||||
use support\exception\BusinessException;
|
||||
use Webman\RedisQueue\Consumer;
|
||||
|
||||
class Notice implements Consumer
|
||||
{
|
||||
// 要消费的队列名
|
||||
public $queue = 'notice';
|
||||
|
||||
// 连接名,对应 plugin/webman/redis-queue/redis.php 里的连接`
|
||||
public $connection = 'default';
|
||||
|
||||
// 消费
|
||||
public function consume($data)
|
||||
{
|
||||
try {
|
||||
(new \app\common\service\MessageNotificationsService())->pushNotice($data);
|
||||
return true;
|
||||
} catch (\Throwable $th) {
|
||||
var_dump('4 :'.$th->getFile()."|".$th->getLine()."|".$th->getMessage());
|
||||
throw new BusinessException($th->getFile()."|".$th->getLine()."|".$th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 消费失败回调
|
||||
/*
|
||||
$package = [
|
||||
'id' => 1357277951, // 消息ID
|
||||
'time' => 1709170510, // 消息时间
|
||||
'delay' => 0, // 延迟时间
|
||||
'attempts' => 2, // 消费次数
|
||||
'queue' => 'send-mail', // 队列名
|
||||
'data' => ['to' => 'tom@gmail.com', 'content' => 'hello'], // 消息内容
|
||||
'max_attempts' => 5, // 最大重试次数
|
||||
'error' => '错误信息' // 错误信息
|
||||
]
|
||||
*/
|
||||
public function onConsumeFailure(\Throwable $e, $package)
|
||||
{
|
||||
echo "consume failure\n";
|
||||
echo $e->getMessage() . "\n";
|
||||
// 无需反序列化
|
||||
var_export($package);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace app\ticket\service;
|
||||
|
||||
use app\common\model\User;
|
||||
use app\common\service\MessageNotificationsService;
|
||||
use app\common\service\OssService;
|
||||
use app\ticket\logic\CategorieLogic;
|
||||
use app\ticket\logic\ConversationLogic;
|
||||
@@ -54,43 +55,47 @@ class ConversationService
|
||||
$ticket = (new TicketLogic())->find($data['ticket_id']);
|
||||
$categorie= (new CategorieLogic())->find($ticket['categorie_id']);
|
||||
$categorieName = isset($categorie['translation']) ? $categorie['translation']['name'] : '';
|
||||
|
||||
$notice = [
|
||||
'code' => $ticket['code'],
|
||||
'category' => $categorieName,
|
||||
'content' => $data['content'],
|
||||
'reply' => '',
|
||||
];
|
||||
//$emailDefault = 'app@hnxinyouhe.com'; //公共邮箱
|
||||
//$emailDefault = 'support@hnxinyouhe.com'; //公共邮箱
|
||||
$emailDefault = env('TICKET_EMAIL_DEFAULT');
|
||||
if (empty($conversation)) {
|
||||
$email = $emailDefault;
|
||||
|
||||
$userDefault = [1, 75, 78];
|
||||
$tiket_content = $ticket['content'];
|
||||
if (empty($conversation)) { //没有沟通记录
|
||||
$title = '新工单提醒-'.$categorieName;
|
||||
$notice_user_id = $userDefault;
|
||||
}else{
|
||||
$title = '工单回复';
|
||||
$tiket = (new TicketLogic())->find($conversation['ticket_id']);
|
||||
if($data['user_id'] != $tiket['user_id']){
|
||||
$email = User::where('id',$tiket['user_id'])->value('email');
|
||||
$notice_user_id = [$tiket['user_id']];
|
||||
}else{
|
||||
$conversationLast = Conversation::where('ticket_id', '=', $conversation['ticket_id'])
|
||||
->where('user_id', '!=', $data['user_id'])
|
||||
->orderBy('id', 'desc')
|
||||
->first();
|
||||
|
||||
$email = (!empty($conversationLast['user_id']))?User::where('id',$conversationLast['user_id'])->value('email',$emailDefault):$emailDefault;
|
||||
$notice_user_id = (!empty($conversationLast['user_id']))? [$conversationLast['user_id']]:$userDefault;
|
||||
}
|
||||
$notice['content'] = $tiket['content'];
|
||||
$tiket_content = $tiket['content'];
|
||||
|
||||
$status_fanyi = array_map(fn($v) => "{{{$v}}}", array_flip(Constants::TICKET_STATUS));
|
||||
$locale = locale();
|
||||
$ticket_status = $locale == 'zh'? Constants::TICKET_STATUS_ZH:Constants::TICKET_STATUS_EN;
|
||||
$content = str_replace($status_fanyi,$ticket_status,$data['content']);
|
||||
|
||||
$notice['reply'] = '<li>最新回复:' . $content . '</li>';
|
||||
}
|
||||
if(!empty($emailDefault)){
|
||||
\app\common\utils\email\MailSend::send($email, '【XYH】新的工单提醒', replaceTpl($notice, 'work_order'));
|
||||
$reply = $content?($content."\n".'工单内容:'):'' ;
|
||||
}
|
||||
|
||||
$notice = ['sender_id' => 0, //发送人ID 0默认系统
|
||||
'sender_type' => 'system', //发送者类型 user admin system
|
||||
'title' => $title, //标题
|
||||
'content' => ($reply??'') . $tiket_content, //内容
|
||||
'scene' => 1, //场景 0系统公共 1系统通知
|
||||
'recipient' => $notice_user_id, //接收人 空为全部
|
||||
'push_method' => 0 //推送方式0系统推送1手动推送
|
||||
];
|
||||
|
||||
// 异步推送
|
||||
\Webman\RedisQueue\Redis::send('notice', $notice);
|
||||
|
||||
unset($data['categorie_id']);
|
||||
unset($data['code']);
|
||||
|
||||
|
||||
+2
-1
@@ -45,7 +45,8 @@
|
||||
"symfony/filesystem": "^7.3",
|
||||
"workerman/crontab": "^1.0",
|
||||
"phpoffice/phpspreadsheet": "^4.5",
|
||||
"phpmailer/phpmailer": "^6.10"
|
||||
"phpmailer/phpmailer": "^6.10",
|
||||
"webman/gateway-worker": "^1.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-event": "For better performance. "
|
||||
|
||||
Generated
+88
-1
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "505fb3d97c338ea53b787196fcc3daaa",
|
||||
"content-hash": "7c70d8c8bda8a5098b8baed463db8839",
|
||||
"packages": [
|
||||
{
|
||||
"name": "aliyuncs/oss-sdk-php",
|
||||
@@ -6634,6 +6634,39 @@
|
||||
},
|
||||
"time": "2025-04-04T13:23:25+00:00"
|
||||
},
|
||||
{
|
||||
"name": "webman/gateway-worker",
|
||||
"version": "v1.0.9",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/webman-php/gateway-worker.git",
|
||||
"reference": "6780c1c788c273a24699caee84feb14224234000"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/webman-php/gateway-worker/zipball/6780c1c788c273a24699caee84feb14224234000",
|
||||
"reference": "6780c1c788c273a24699caee84feb14224234000",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"workerman/gateway-worker": ">=3.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Webman\\GatewayWorker\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/webman-php/gateway-worker/issues",
|
||||
"source": "https://github.com/webman-php/gateway-worker/tree/v1.0.9"
|
||||
},
|
||||
"time": "2024-04-30T01:05:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "webman/redis",
|
||||
"version": "v2.1.3",
|
||||
@@ -6793,6 +6826,60 @@
|
||||
},
|
||||
"time": "2025-01-15T07:20:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "workerman/gateway-worker",
|
||||
"version": "v4.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/walkor/GatewayWorker.git",
|
||||
"reference": "25e4430982caa98f82e9187d092ce34b2fddfc90"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/walkor/GatewayWorker/zipball/25e4430982caa98f82e9187d092ce34b2fddfc90",
|
||||
"reference": "25e4430982caa98f82e9187d092ce34b2fddfc90",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2",
|
||||
"workerman/workerman": "^4.0.30 || ^5.1"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "4.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GatewayWorker\\": "./src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"homepage": "http://www.workerman.net",
|
||||
"keywords": [
|
||||
"communication",
|
||||
"distributed"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/walkor/GatewayWorker/issues",
|
||||
"source": "https://github.com/walkor/GatewayWorker/tree/v4.0.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://opencollective.com/walkor",
|
||||
"type": "open_collective"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/walkor",
|
||||
"type": "patreon"
|
||||
}
|
||||
],
|
||||
"time": "2025-06-27T01:30:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "workerman/redis",
|
||||
"version": "v2.0.4",
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
return [
|
||||
'enable' => true,
|
||||
];
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
use Webman\GatewayWorker\Gateway;
|
||||
use Webman\GatewayWorker\BusinessWorker;
|
||||
use Webman\GatewayWorker\Register;
|
||||
|
||||
return [
|
||||
'gateway' => [
|
||||
'handler' => Gateway::class,
|
||||
'listen' => 'websocket://0.0.0.0:' . getenv('GATEWAY_LISTEN'),
|
||||
'count' => 2,
|
||||
'reloadable' => false,
|
||||
'constructor' => ['config' => [
|
||||
'lanIp' => '127.0.0.1',
|
||||
'startPort' => 2300,
|
||||
'pingInterval' => 55,
|
||||
'pingNotResponseLimit' => 1,
|
||||
'pingData' => '',
|
||||
'registerAddress' => '127.0.0.1:1236',
|
||||
'onConnect' => function(){},
|
||||
]]
|
||||
],
|
||||
'worker' => [
|
||||
'handler' => BusinessWorker::class,
|
||||
'count' => cpu_count()*2,
|
||||
'constructor' => ['config' => [
|
||||
'eventHandler' => plugin\webman\gateway\Events::class,
|
||||
'name' => 'ChatBusinessWorker',
|
||||
'registerAddress' => '127.0.0.1:1236',
|
||||
]]
|
||||
],
|
||||
'register' => [
|
||||
'handler' => Register::class,
|
||||
'listen' => 'text://127.0.0.1:1236',
|
||||
'count' => 1, // Must be 1
|
||||
'reloadable' => false,
|
||||
'constructor' => []
|
||||
],
|
||||
];
|
||||
@@ -64,10 +64,29 @@ Route::group('/common', function () {
|
||||
Route::any('/cache/clear', [app\common\controller\PresetController::class, 'cacheClear']);
|
||||
Route::any('/menus', [app\common\controller\PresetController::class, 'menus']);
|
||||
});
|
||||
|
||||
|
||||
// 消息通知
|
||||
Route::group('/notice', function () {
|
||||
Route::any('/list', [app\common\controller\MessageUserStatusController::class, 'index']);
|
||||
Route::any('/show', [app\common\controller\MessageUserStatusController::class, 'show']);
|
||||
Route::any('/read', [app\common\controller\MessageUserStatusController::class, 'read']);
|
||||
Route::any('/destroy', [app\common\controller\MessageUserStatusController::class, 'destroy']);
|
||||
|
||||
Route::any('/unread', [app\common\controller\MessageUserStatusController::class, 'listUnRead']);
|
||||
});
|
||||
});
|
||||
|
||||
// 后台管理
|
||||
Route::group('/admin', function () {
|
||||
// 消息通知
|
||||
Route::group('/notice', function () {
|
||||
Route::any('/list', [app\common\controller\MessageNotificationsController::class, 'index']);
|
||||
Route::any('/show', [app\common\controller\MessageNotificationsController::class, 'show']);
|
||||
Route::any('/push', [app\common\controller\MessageNotificationsController::class, 'push']);
|
||||
Route::any('/destroy', [app\common\controller\MessageNotificationsController::class, 'destroy']);
|
||||
});
|
||||
|
||||
// 角色
|
||||
Route::group('/role', function () {
|
||||
Route::any('/list', [app\admin\controller\RoleController::class, 'index']);
|
||||
@@ -157,6 +176,7 @@ Route::group('/admin', function () {
|
||||
Route::any('/transfer', [app\crmAdmin\controller\CustomerController::class, 'transfer']);
|
||||
Route::any('/detail', [app\crmAdmin\controller\CustomerController::class, 'detail']);
|
||||
Route::any('/delete', [app\crmAdmin\controller\CustomerController::class, 'delete']);
|
||||
Route::any('/restore', [app\crmAdmin\controller\CustomerController::class, 'restore']); //恢复客户
|
||||
Route::any('/cede', [app\crm\controller\CustomerController::class, 'cede']);
|
||||
Route::any('/follow', [app\crmAdmin\controller\CustomerController::class, 'follow']);
|
||||
Route::any('/allocation', [app\crmAdmin\controller\CustomerController::class, 'allocation']);
|
||||
@@ -214,6 +234,7 @@ Route::group('/crm', function () {
|
||||
Route::any('/customer/getCountry', [app\crm\controller\CustomerController::class, 'getCountry']);
|
||||
Route::any('/customer/followNext', [app\crm\controller\CustomerController::class, 'followNext']);
|
||||
Route::any('/customer/report', [app\crm\controller\CustomerController::class, 'report']);
|
||||
Route::any('/customer/distinctNotice', [app\crm\controller\CustomerController::class, 'distinctNotice']);
|
||||
|
||||
Route::any('/tagGroup/list', [app\crm\controller\TagGroupController::class, 'list']);
|
||||
Route::any('/tagGroup/updatePivot', [app\crm\controller\TagGroupController::class, 'updatePivot']);
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'basi_cdata' => [
|
||||
'/', // 首页
|
||||
'/index/index', // 首页
|
||||
'/index/view', // 飞书调试
|
||||
'/perm', // 调试-添加权限
|
||||
|
||||
'/common/preset/corps', // 公司列表
|
||||
'/common/preset/feishu/app', // 飞书应用信息
|
||||
]
|
||||
];
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace plugin\webman\gateway;
|
||||
|
||||
use app\common\utils\WsEvents;
|
||||
use GatewayWorker\Lib\Gateway;
|
||||
|
||||
class Events
|
||||
{
|
||||
|
||||
protected static $wsEvents;
|
||||
|
||||
public static function onWorkerStart($worker)
|
||||
{
|
||||
self::$wsEvents = new WsEvents();
|
||||
}
|
||||
|
||||
public static function onConnect($client_id)
|
||||
{
|
||||
// 仅 TCP 连接,不一定有 WebSocket 握手
|
||||
}
|
||||
|
||||
public static function onWebSocketConnect($client_id, $data)
|
||||
{
|
||||
self::$wsEvents->auth($client_id, $data);
|
||||
}
|
||||
|
||||
public static function onMessage($client_id, $message)
|
||||
{
|
||||
self::$wsEvents->message($client_id, $message);
|
||||
}
|
||||
|
||||
public static function onClose($client_id)
|
||||
{
|
||||
var_dump('关闭客户端ID:'.$client_id);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user