Files
cunkebao_v3/Server/application/chukebao/controller/AiPushController.php
2025-11-04 17:48:51 +08:00

506 lines
18 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace app\chukebao\controller;
use app\chukebao\model\AiPush;
use app\chukebao\model\AiPushRecord;
use app\chukebao\model\AutoGreetings;
use library\ResponseHelper;
use think\Db;
class AiPushController extends BaseController
{
/**
* 获取推送列表
* @return \think\response\Json
* @throws \Exception
*/
public function getList()
{
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 10);
$keyword = $this->request->param('keyword', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
$where = [
['companyId', '=', $companyId],
['userId', '=', $userId],
['isDel', '=', 0],
];
if (!empty($keyword)) {
$where[] = ['name', 'like', '%' . $keyword . '%'];
}
$query = AiPush::where($where);
$total = $query->count();
$list = $query->where($where)->page($page, $limit)->order('id desc')->select();
// 处理数据
$list = is_array($list) ? $list : $list->toArray();
foreach ($list as &$item) {
// 解析标签数组
$item['tags'] = json_decode($item['tags'], true);
if (!is_array($item['tags'])) {
$item['tags'] = [];
}
// 格式化推送时机显示文本
$timingTypes = [
1 => '立即推送',
2 => 'AI最佳时机',
3 => '定时推送'
];
$item['timingText'] = $timingTypes[$item['pushTiming']] ?? '未知';
// 处理定时推送时间
if ($item['pushTiming'] == 3 && !empty($item['scheduledTime'])) {
$item['scheduledTime'] = date('Y-m-d H:i:s', $item['scheduledTime']);
} else {
$item['scheduledTime'] = '';
}
// 从记录表计算实际成功率
$pushId = $item['id'];
$totalCount = Db::name('kf_ai_push_record')
->where('pushId', $pushId)
->count();
$sendCount = Db::name('kf_ai_push_record')
->where('pushId', $pushId)
->where('isSend', 1)
->count();
$item['successRate'] = $totalCount > 0 ? round(($sendCount * 100) / $totalCount, 1) : 0;
$item['totalPushCount'] = $totalCount; // 推送总数
$item['sendCount'] = $sendCount; // 成功发送数
}
unset($item);
return ResponseHelper::success(['list' => $list, 'total' => $total]);
}
/**
* 添加
* @return \think\response\Json
* @throws \Exception
*/
public function add()
{
$name = $this->request->param('name', '');
$tags = $this->request->param('tags', ''); // 标签,支持逗号分隔的字符串或数组
$content = $this->request->param('content', '');
$pushTiming = $this->request->param('pushTiming', 1); // 1=立即推送2=最佳时机(AI决定)3=定时推送
$scheduledTime = $this->request->param('scheduledTime', ''); // 定时推送的时间
$status = $this->request->param('status', 1);
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($name) || empty($content)) {
return ResponseHelper::error('推送名称和推送内容不能为空');
}
// 验证推送时机
if (!in_array($pushTiming, [1, 2, 3])) {
return ResponseHelper::error('无效的推送时机类型');
}
// 如果是定时推送,需要验证时间
if ($pushTiming == 3) {
if (empty($scheduledTime)) {
return ResponseHelper::error('定时推送需要设置推送时间');
}
// 验证时间格式
$timestamp = strtotime($scheduledTime);
if ($timestamp === false || $timestamp <= time()) {
return ResponseHelper::error('定时推送时间格式不正确或必须大于当前时间');
}
} else {
$scheduledTime = '';
}
// 处理标签
$tagsArray = [];
if (!empty($tags)) {
if (is_string($tags)) {
// 如果是字符串,按逗号分割
$tagsArray = array_filter(array_map('trim', explode(',', $tags)));
} elseif (is_array($tags)) {
$tagsArray = array_filter(array_map('trim', $tags));
}
}
if (empty($tagsArray)) {
return ResponseHelper::error('目标用户标签不能为空');
}
Db::startTrans();
try {
$aiPush = new AiPush();
$aiPush->name = $name;
$aiPush->tags = json_encode($tagsArray, JSON_UNESCAPED_UNICODE);
$aiPush->content = $content;
$aiPush->pushTiming = $pushTiming;
$aiPush->scheduledTime = $pushTiming == 3 && !empty($scheduledTime) ? strtotime($scheduledTime) : 0;
$aiPush->status = $status;
$aiPush->successRate = 0; // 初始成功率为0
$aiPush->userId = $userId;
$aiPush->companyId = $companyId;
$aiPush->createTime = time();
$aiPush->updateTime = time();
$aiPush->save();
Db::commit();
return ResponseHelper::success(['id' => $aiPush->id], '创建成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('创建失败:' . $e->getMessage());
}
}
/**
* 详情
* @return \think\response\Json
* @throws \Exception
*/
public function details()
{
$id = $this->request->param('id', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($id)) {
return ResponseHelper::error('参数缺失');
}
$data = AiPush::where(['id' => $id, 'isDel' => 0, 'userId' => $userId, 'companyId' => $companyId])->find();
if (empty($data)) {
return ResponseHelper::error('该推送已被删除或者不存在');
}
$data = $data->toArray();
// 解析标签数组
$data['tags'] = json_decode($data['tags'], true);
if (!is_array($data['tags'])) {
$data['tags'] = [];
}
// 标签转为逗号分隔的字符串(用于编辑时回显)
$data['tagsString'] = implode(',', $data['tags']);
// 处理定时推送时间
if ($data['pushTiming'] == 3 && !empty($data['scheduledTime'])) {
$data['scheduledTime'] = date('Y-m-d H:i:s', $data['scheduledTime']);
} else {
$data['scheduledTime'] = '';
}
// 成功率保留一位小数
$data['successRate'] = isset($data['successRate']) ? round($data['successRate'], 1) : 0;
return ResponseHelper::success($data, '获取成功');
}
/**
* 删除
* @return \think\response\Json
* @throws \Exception
*/
public function del()
{
$id = $this->request->param('id', '');
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($id)) {
return ResponseHelper::error('参数缺失');
}
$data = AiPush::where(['id' => $id, 'isDel' => 0, 'userId' => $userId, 'companyId' => $companyId])->find();
if (empty($data)) {
return ResponseHelper::error('该推送已被删除或者不存在');
}
Db::startTrans();
try {
$data->isDel = 1;
$data->delTime = time();
$data->save();
Db::commit();
return ResponseHelper::success('', '删除成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('删除失败:' . $e->getMessage());
}
}
/**
* 更新
* @return \think\response\Json
* @throws \Exception
*/
public function update()
{
$id = $this->request->param('id', '');
$name = $this->request->param('name', '');
$tags = $this->request->param('tags', '');
$content = $this->request->param('content', '');
$pushTiming = $this->request->param('pushTiming', 1);
$scheduledTime = $this->request->param('scheduledTime', '');
$status = $this->request->param('status', 1);
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($id) || empty($name) || empty($content)) {
return ResponseHelper::error('参数缺失');
}
// 验证推送时机
if (!in_array($pushTiming, [1, 2, 3])) {
return ResponseHelper::error('无效的推送时机类型');
}
// 如果是定时推送,需要验证时间
if ($pushTiming == 3) {
if (empty($scheduledTime)) {
return ResponseHelper::error('定时推送需要设置推送时间');
}
// 验证时间格式
$timestamp = strtotime($scheduledTime);
if ($timestamp === false || $timestamp <= time()) {
return ResponseHelper::error('定时推送时间格式不正确或必须大于当前时间');
}
} else {
$scheduledTime = '';
}
// 处理标签
$tagsArray = [];
if (!empty($tags)) {
if (is_string($tags)) {
$tagsArray = array_filter(array_map('trim', explode(',', $tags)));
} elseif (is_array($tags)) {
$tagsArray = array_filter(array_map('trim', $tags));
}
}
if (empty($tagsArray)) {
return ResponseHelper::error('目标用户标签不能为空');
}
$query = AiPush::where(['id' => $id, 'isDel' => 0, 'userId' => $userId, 'companyId' => $companyId])->find();
if (empty($query)) {
return ResponseHelper::error('该推送已被删除或者不存在');
}
Db::startTrans();
try {
$query->name = $name;
$query->tags = json_encode($tagsArray, JSON_UNESCAPED_UNICODE);
$query->content = $content;
$query->pushTiming = $pushTiming;
$query->scheduledTime = $pushTiming == 3 && !empty($scheduledTime) ? strtotime($scheduledTime) : 0;
$query->status = $status;
$query->updateTime = time();
$query->save();
Db::commit();
return ResponseHelper::success('', '修改成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('修改失败:' . $e->getMessage());
}
}
/**
* 修改状态
* @return \think\response\Json
* @throws \Exception
*/
public function setStatus()
{
$id = $this->request->param('id', '');
$status = $this->request->param('status', 1);
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
if (empty($id)) {
return ResponseHelper::error('参数缺失');
}
if (!in_array($status, [0, 1])) {
return ResponseHelper::error('状态值无效');
}
$data = AiPush::where(['id' => $id, 'isDel' => 0, 'userId' => $userId, 'companyId' => $companyId])->find();
if (empty($data)) {
return ResponseHelper::error('该推送已被删除或者不存在');
}
Db::startTrans();
try {
$data->status = $status;
$data->updateTime = time();
$data->save();
Db::commit();
return ResponseHelper::success('', $status == 1 ? '启用成功' : '禁用成功');
} catch (\Exception $e) {
Db::rollback();
return ResponseHelper::error('操作失败:' . $e->getMessage());
}
}
/**
* 统计概览整合自动问候和AI推送
* - 活跃规则自动问候规则近30天
* - 总触发次数(自动问候记录总数)
* - AI推送成功率AI推送的成功率
* - AI智能推送AI推送规则近30天活跃
* - 规则效果排行(自动问候规则,按使用次数排序)
* @return \think\response\Json
*/
public function stats()
{
$companyId = $this->getUserInfo('companyId');
$userId = $this->getUserInfo('id');
$start30d = time() - 30 * 24 * 3600;
try {
// 公司维度(用于除排行外的统计)
$companyWhere = [
['companyId', '=', $companyId],
];
// 排行维度(限定个人)
$rankingWhere = [
['companyId', '=', $companyId],
['userId', '=', $userId],
];
// ========== 自动问候统计 ==========
// 1) 活跃规则自动问候规则近30天有记录的
$activeRules = Db::name('kf_auto_greetings_record')
->where($companyWhere)
->where('createTime', '>=', $start30d)
->distinct(true)
->count('autoId');
// 2) 总触发次数(自动问候记录总数)
$totalTriggers = Db::name('kf_auto_greetings_record')
->where($companyWhere)
->count();
// ========== AI推送统计 ==========
// 3) AI推送成功率
$totalPushes = Db::name('kf_ai_push_record')
->where($companyWhere)
->count();
$sendCount = Db::name('kf_ai_push_record')
->where($companyWhere)
->where('isSend', '=', 1)
->count();
// 成功率百分比保留整数75%
$aiPushSuccessRate = $totalPushes > 0 ? round(($sendCount * 100) / $totalPushes, 0) : 0;
// 4) AI智能推送AI推送规则近30天活跃的
$aiPushCount = Db::name('kf_ai_push_record')
->where($companyWhere)
->where('createTime', '>=', $start30d)
->distinct(true)
->count('pushId');
// ========== 规则效果排行(自动问候规则,按使用次数排序)==========
$ruleRanking = Db::name('kf_auto_greetings_record')
->where($rankingWhere)
->field([
'autoId AS id',
'COUNT(*) AS usageCount'
])
->group('autoId')
->order('usageCount DESC')
->limit(20)
->select();
// 附加规则名称和触发类型
$autoIds = array_values(array_unique(array_column($ruleRanking, 'id')));
$autoIdToRule = [];
if (!empty($autoIds)) {
$rules = AutoGreetings::where([['id', 'in', $autoIds]])
->field('id,name,trigger')
->select();
foreach ($rules as $rule) {
$triggerTypes = [
1 => '新好友',
2 => '首次发消息',
3 => '时间触发',
4 => '关键词',
5 => '生日触发',
6 => '自定义'
];
$autoIdToRule[$rule['id']] = [
'name' => $rule['name'],
'trigger' => $rule['trigger'],
'triggerText' => $triggerTypes[$rule['trigger']] ?? '未知',
];
}
}
foreach ($ruleRanking as &$row) {
$row['usageCount'] = (int)($row['usageCount'] ?? 0);
$row['name'] = $autoIdToRule[$row['id']]['name'] ?? '';
$row['trigger'] = $autoIdToRule[$row['id']]['trigger'] ?? null;
$row['triggerText'] = $autoIdToRule[$row['id']]['triggerText'] ?? '';
// 格式化使用次数显示
$row['usageCountText'] = $row['usageCount'] . ' 次';
}
unset($row);
// 更新主表中的成功率字段(异步或定期更新)
$this->updatePushSuccessRate($companyId);
return ResponseHelper::success([
'activeRules' => (int)$activeRules,
'totalTriggers' => (int)$totalTriggers,
'aiPushSuccessRate' => (int)$aiPushSuccessRate,
'aiPushCount' => (int)$aiPushCount,
'ruleRanking' => $ruleRanking,
], '统计成功');
} catch (\Exception $e) {
return ResponseHelper::error('统计失败:' . $e->getMessage());
}
}
/**
* 更新推送表的成功率字段
* @param int $companyId
* @return void
*/
private function updatePushSuccessRate($companyId)
{
try {
// 获取所有启用的推送
$pushes = AiPush::where([
['companyId', '=', $companyId],
['isDel', '=', 0]
])->field('id')->select();
foreach ($pushes as $push) {
$pushId = $push['id'];
$totalCount = Db::name('kf_ai_push_record')
->where('pushId', $pushId)
->count();
$sendCount = Db::name('kf_ai_push_record')
->where('pushId', $pushId)
->where('isSend', 1)
->count();
$successRate = $totalCount > 0 ? round(($sendCount * 100) / $totalCount, 2) : 0.00;
AiPush::where('id', $pushId)->update([
'successRate' => $successRate,
'updateTime' => time()
]);
}
} catch (\Exception $e) {
// 静默失败,不影响主流程
}
}
}