代码提交
This commit is contained in:
@@ -7,7 +7,7 @@ Route::group('v1/ai', function () {
|
||||
|
||||
//openai、chatGPT
|
||||
Route::group('openai', function () {
|
||||
Route::post('text', 'app\ai\controller\OpenAi@text');
|
||||
Route::post('text', 'app\ai\controller\OpenAI@text');
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -3,14 +3,19 @@
|
||||
namespace app\ai\controller;
|
||||
|
||||
use think\facade\Env;
|
||||
class OpenAI
|
||||
use think\Controller;
|
||||
|
||||
|
||||
class OpenAI extends Controller
|
||||
{
|
||||
protected $apiUrl;
|
||||
protected $apiKey;
|
||||
protected $headers;
|
||||
|
||||
public function __init()
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->apiUrl = Env::get('openAi.apiUrl');
|
||||
$this->apiKey = Env::get('openAi.apiKey');
|
||||
|
||||
@@ -24,7 +29,7 @@ class OpenAI
|
||||
|
||||
public function text()
|
||||
{
|
||||
$this->__init();
|
||||
|
||||
$params = [
|
||||
'model' => 'gpt-3.5-turbo-0125',
|
||||
'input' => 'DHA 从孕期到出生到老年都需要,助力大脑发育🧠/减缓脑压力有助记忆/给大脑动力#贝蒂喜藻油DHA 双标认证每粒 150毫克,高含量、高性价比从小吃到老,长期吃更健康 重写这条朋友圈 要求: 1、原本的字数和意思不要修改超过10% 2、出现品牌名或个人名字就去除'
|
||||
@@ -46,7 +51,6 @@ class OpenAI
|
||||
*/
|
||||
public function bedtimeStory()
|
||||
{
|
||||
$this->__init();
|
||||
|
||||
// API请求参数
|
||||
$params = [
|
||||
|
||||
@@ -141,8 +141,18 @@ Route::group('v1/', function () {
|
||||
Route::get('stats', 'app\chukebao\controller\AutoGreetingsController@stats');
|
||||
});
|
||||
|
||||
//AI智能推送
|
||||
Route::group('aiPush/', function () {
|
||||
Route::get('list', 'app\chukebao\controller\AiPushController@getList'); // 获取推送列表
|
||||
Route::post('add', 'app\chukebao\controller\AiPushController@add'); // 添加推送
|
||||
Route::get('details', 'app\chukebao\controller\AiPushController@details'); // 推送详情
|
||||
Route::delete('del', 'app\chukebao\controller\AiPushController@del'); // 删除推送
|
||||
Route::post('update', 'app\chukebao\controller\AiPushController@update'); // 更新推送
|
||||
Route::get('setStatus', 'app\chukebao\controller\AiPushController@setStatus'); // 修改状态
|
||||
Route::get('stats', 'app\chukebao\controller\AiPushController@stats'); // 统计概览
|
||||
});
|
||||
|
||||
|
||||
//自动问候
|
||||
Route::group('notice/', function () {
|
||||
Route::get('list', 'app\chukebao\controller\NoticeController@getList');
|
||||
Route::put('readMessage', 'app\chukebao\controller\NoticeController@readMessage');
|
||||
|
||||
505
Server/application/chukebao/controller/AiPushController.php
Normal file
505
Server/application/chukebao/controller/AiPushController.php
Normal file
@@ -0,0 +1,505 @@
|
||||
<?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) {
|
||||
// 静默失败,不影响主流程
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,15 +9,19 @@ use think\Db;
|
||||
class AutoGreetingsController extends BaseController
|
||||
{
|
||||
|
||||
/**
|
||||
* 获取问候规则列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getList(){
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
$is_template = $this->request->param('is_template', 0);
|
||||
$triggerType = $this->request->param('triggerType', ''); // 触发类型筛选
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
|
||||
if($is_template == 1){
|
||||
$where = [
|
||||
['is_template','=',1],
|
||||
@@ -31,20 +35,46 @@ class AutoGreetingsController extends BaseController
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if(!empty($keyword)){
|
||||
$where[] = ['name','like','%'.$keyword.'%'];
|
||||
}
|
||||
|
||||
if(!empty($triggerType)){
|
||||
$where[] = ['trigger','=',$triggerType];
|
||||
}
|
||||
|
||||
$query = AutoGreetings::where($where);
|
||||
$total = $query->count();
|
||||
$list = $query->where($where)->page($page,$limit)->order('id desc')->select();
|
||||
$list = $query->where($where)->page($page,$limit)->order('level asc,id desc')->select();
|
||||
|
||||
// 获取使用次数
|
||||
$list = is_array($list) ? $list : $list->toArray();
|
||||
$ids = array_column($list, 'id');
|
||||
$usageCounts = [];
|
||||
if (!empty($ids)) {
|
||||
$counts = Db::name('kf_auto_greetings_record')
|
||||
->where('autoId', 'in', $ids)
|
||||
->field('autoId, COUNT(*) as count')
|
||||
->group('autoId')
|
||||
->select();
|
||||
foreach ($counts as $count) {
|
||||
$usageCounts[$count['autoId']] = (int)$count['count'];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($list as &$item) {
|
||||
$item['trigger'] = json_decode($item['trigger'],true);
|
||||
$item['condition'] = json_decode($item['condition'], true);
|
||||
$item['usageCount'] = $usageCounts[$item['id']] ?? 0;
|
||||
// 格式化触发类型显示文本
|
||||
$triggerTypes = [
|
||||
1 => '新好友',
|
||||
2 => '首次发消息',
|
||||
3 => '时间触发',
|
||||
4 => '关键词触发',
|
||||
5 => '生日触发',
|
||||
6 => '自定义'
|
||||
];
|
||||
$item['triggerText'] = $triggerTypes[$item['trigger']] ?? '未知';
|
||||
}
|
||||
unset($item);
|
||||
|
||||
@@ -52,6 +82,315 @@ class AutoGreetingsController extends BaseController
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 校验trigger类型对应的condition
|
||||
* @param int $trigger 触发类型
|
||||
* @param mixed $condition 条件参数
|
||||
* @return array|string 返回处理后的condition数组,或错误信息字符串
|
||||
*/
|
||||
private function validateTriggerCondition($trigger, $condition)
|
||||
{
|
||||
// trigger类型:1=新好友,2=首次发消息,3=时间触发,4=关键词触发,5=生日触发,6=自定义
|
||||
switch ($trigger) {
|
||||
case 1: // 新好友
|
||||
// 不需要condition
|
||||
return [];
|
||||
|
||||
case 2: // 首次发消息
|
||||
// 不需要condition
|
||||
return [];
|
||||
|
||||
case 3: // 时间触发
|
||||
// 需要condition,格式为:{"type": "daily_time|yearly_datetime|fixed_range|workday", "value": "..."}
|
||||
if (empty($condition)) {
|
||||
return '时间触发类型需要配置具体的触发条件';
|
||||
}
|
||||
$condition = is_array($condition) ? $condition : json_decode($condition, true);
|
||||
if (empty($condition) || !is_array($condition)) {
|
||||
return '时间触发类型的条件格式不正确,应为数组格式';
|
||||
}
|
||||
|
||||
// 验证必须包含type字段
|
||||
if (!isset($condition['type']) || empty($condition['type'])) {
|
||||
return '时间触发类型必须指定触发方式:daily_time(每天固定时间)、yearly_datetime(每年固定日期时间)、fixed_range(固定时间段)、workday(工作日)';
|
||||
}
|
||||
|
||||
$timeType = $condition['type'];
|
||||
$allowedTypes = ['daily_time', 'yearly_datetime', 'fixed_range', 'workday'];
|
||||
// 兼容旧版本的 fixed_time,自动转换为 daily_time
|
||||
if ($timeType === 'fixed_time') {
|
||||
$timeType = 'daily_time';
|
||||
}
|
||||
if (!in_array($timeType, $allowedTypes)) {
|
||||
return '时间触发类型无效,必须为:daily_time(每天固定时间)、yearly_datetime(每年固定日期时间)、fixed_range(固定时间段)、workday(工作日)';
|
||||
}
|
||||
|
||||
// 根据不同的type验证value
|
||||
switch ($timeType) {
|
||||
case 'daily_time': // 每天固定时间(每天的几点几分)
|
||||
// value应该是时间字符串,格式:HH:mm,如 "14:30"
|
||||
if (!isset($condition['value']) || empty($condition['value'])) {
|
||||
return '每天固定时间类型需要配置具体时间,格式:HH:mm(如 14:30)';
|
||||
}
|
||||
$timeValue = $condition['value'];
|
||||
if (!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $timeValue)) {
|
||||
return '每天固定时间格式不正确,应为 HH:mm 格式(如 14:30)';
|
||||
}
|
||||
return [
|
||||
'type' => 'daily_time',
|
||||
'value' => $timeValue
|
||||
];
|
||||
|
||||
case 'yearly_datetime': // 每年固定日期时间(每年的几月几号几点几分)
|
||||
// value应该是日期时间字符串,格式:MM-dd HH:mm,如 "12-25 14:30"
|
||||
if (!isset($condition['value']) || empty($condition['value'])) {
|
||||
return '每年固定日期时间类型需要配置具体日期和时间,格式:MM-dd HH:mm(如 12-25 14:30)';
|
||||
}
|
||||
$datetimeValue = $condition['value'];
|
||||
// 验证格式:MM-dd HH:mm
|
||||
if (!preg_match('/^(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]) ([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $datetimeValue)) {
|
||||
return '每年固定日期时间格式不正确,应为 MM-dd HH:mm 格式(如 12-25 14:30)';
|
||||
}
|
||||
// 进一步验证日期是否有效(例如2月30日不存在)
|
||||
list($datePart, $timePart) = explode(' ', $datetimeValue);
|
||||
list($month, $day) = explode('-', $datePart);
|
||||
if (!checkdate((int)$month, (int)$day, 2000)) { // 使用2000年作为参考年份验证日期有效性
|
||||
return '日期无效,请检查月份和日期是否正确(如2月不能有30日)';
|
||||
}
|
||||
return [
|
||||
'type' => 'yearly_datetime',
|
||||
'value' => $datetimeValue
|
||||
];
|
||||
|
||||
case 'fixed_range': // 固定时间段
|
||||
// value应该是时间段数组,格式:["09:00", "18:00"]
|
||||
if (!isset($condition['value']) || !is_array($condition['value'])) {
|
||||
return '固定时间段类型需要配置时间段,格式:["开始时间", "结束时间"](如 ["09:00", "18:00"])';
|
||||
}
|
||||
$rangeValue = $condition['value'];
|
||||
if (count($rangeValue) !== 2) {
|
||||
return '固定时间段应为包含两个时间点的数组,格式:["09:00", "18:00"]';
|
||||
}
|
||||
// 验证时间格式
|
||||
foreach ($rangeValue as $time) {
|
||||
if (!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $time)) {
|
||||
return '时间段格式不正确,应为 HH:mm 格式(如 09:00)';
|
||||
}
|
||||
}
|
||||
// 验证开始时间小于结束时间
|
||||
$startTime = strtotime('2000-01-01 ' . $rangeValue[0]);
|
||||
$endTime = strtotime('2000-01-01 ' . $rangeValue[1]);
|
||||
if ($startTime >= $endTime) {
|
||||
return '开始时间必须小于结束时间';
|
||||
}
|
||||
return [
|
||||
'type' => 'fixed_range',
|
||||
'value' => $rangeValue
|
||||
];
|
||||
|
||||
case 'workday': // 工作日
|
||||
// 工作日需要配置时间,格式:HH:mm(如 09:00)
|
||||
if (!isset($condition['value']) || empty($condition['value'])) {
|
||||
return '工作日触发类型需要配置时间,格式:HH:mm(如 09:00)';
|
||||
}
|
||||
$timeValue = trim($condition['value']);
|
||||
// 验证格式:HH:mm
|
||||
if (!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $timeValue)) {
|
||||
return '工作日时间格式不正确,应为 HH:mm 格式(如 09:00)';
|
||||
}
|
||||
return [
|
||||
'type' => 'workday',
|
||||
'value' => $timeValue
|
||||
];
|
||||
|
||||
default:
|
||||
return '时间触发类型无效';
|
||||
}
|
||||
|
||||
case 4: // 关键词触发
|
||||
// 需要condition,格式:{"keywords": ["关键词1", "关键词2"], "match_type": "exact|fuzzy"}
|
||||
if (empty($condition)) {
|
||||
return '关键词触发类型需要配置至少一个关键词';
|
||||
}
|
||||
|
||||
// 如果是字符串,尝试解析JSON
|
||||
if (is_string($condition)) {
|
||||
$decoded = json_decode($condition, true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$condition = $decoded;
|
||||
} else {
|
||||
return '关键词触发类型格式错误,应为对象格式:{"keywords": ["关键词1", "关键词2"], "match_type": "exact|fuzzy"}';
|
||||
}
|
||||
}
|
||||
|
||||
// 必须是对象格式
|
||||
if (!is_array($condition) || !isset($condition['keywords'])) {
|
||||
return '关键词触发类型格式错误,应为对象格式:{"keywords": ["关键词1", "关键词2"], "match_type": "exact|fuzzy"}';
|
||||
}
|
||||
|
||||
$keywords = $condition['keywords'];
|
||||
$matchType = isset($condition['match_type']) ? $condition['match_type'] : 'fuzzy';
|
||||
|
||||
// 验证match_type
|
||||
if (!in_array($matchType, ['exact', 'fuzzy'])) {
|
||||
return '匹配类型无效,必须为:exact(精准匹配)或 fuzzy(模糊匹配)';
|
||||
}
|
||||
|
||||
// 处理keywords
|
||||
if (is_string($keywords)) {
|
||||
$keywords = explode(',', $keywords);
|
||||
}
|
||||
if (!is_array($keywords)) {
|
||||
return '关键词格式不正确,应为数组格式';
|
||||
}
|
||||
|
||||
// 过滤空值并去重
|
||||
$keywords = array_filter(array_map('trim', $keywords));
|
||||
if (empty($keywords)) {
|
||||
return '关键词触发类型需要配置至少一个关键词';
|
||||
}
|
||||
|
||||
// 验证每个关键词不为空
|
||||
foreach ($keywords as $keyword) {
|
||||
if (empty($keyword)) {
|
||||
return '关键词不能为空';
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'keywords' => array_values($keywords),
|
||||
'match_type' => $matchType
|
||||
];
|
||||
|
||||
case 5: // 生日触发
|
||||
// 需要condition,格式支持:
|
||||
// 1. 月日字符串:'10-10' 或 '10-10 09:00'(MM-DD格式,不包含年份)
|
||||
// 2. 对象格式:{'month': 10, 'day': 10, 'time': '09:00'} 或 {'month': '10', 'day': '10', 'time_range': ['09:00', '10:00']}
|
||||
if (empty($condition)) {
|
||||
return '生日触发类型需要配置日期条件';
|
||||
}
|
||||
|
||||
// 如果是字符串,只接受 MM-DD 格式(不包含年份)
|
||||
if (is_string($condition)) {
|
||||
// 检查是否包含时间部分
|
||||
if (preg_match('/^(\d{1,2})-(\d{1,2})\s+(\d{2}:\d{2})$/', $condition, $matches)) {
|
||||
// 格式:'10-10 09:00'
|
||||
$month = (int)$matches[1];
|
||||
$day = (int)$matches[2];
|
||||
if ($month < 1 || $month > 12 || $day < 1 || $day > 31) {
|
||||
return '生日日期格式不正确,月份应为1-12,日期应为1-31';
|
||||
}
|
||||
return [
|
||||
'month' => $month,
|
||||
'day' => $day,
|
||||
'time' => $matches[3]
|
||||
];
|
||||
} elseif (preg_match('/^(\d{1,2})-(\d{1,2})$/', $condition, $matches)) {
|
||||
// 格式:'10-10'(不指定时间,当天任何时间都可以触发)
|
||||
$month = (int)$matches[1];
|
||||
$day = (int)$matches[2];
|
||||
if ($month < 1 || $month > 12 || $day < 1 || $day > 31) {
|
||||
return '生日日期格式不正确,月份应为1-12,日期应为1-31';
|
||||
}
|
||||
return [
|
||||
'month' => $month,
|
||||
'day' => $day
|
||||
];
|
||||
} else {
|
||||
return '生日日期格式不正确,应为 MM-DD 或 MM-DD HH:mm 格式(如 10-10 或 10-10 09:00),不包含年份';
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是数组,可能是对象格式或旧格式
|
||||
if (is_array($condition)) {
|
||||
// 检查是否是旧格式(仅兼容 MM-DD 格式的数组)
|
||||
if (isset($condition[0]) && is_string($condition[0])) {
|
||||
$dateStr = $condition[0];
|
||||
// 只接受 MM-DD 格式:'10-10' 或 '10-10 09:00'
|
||||
if (preg_match('/^(\d{1,2})-(\d{1,2})(?:\s+(\d{2}:\d{2}))?$/', $dateStr, $matches)) {
|
||||
$month = (int)$matches[1];
|
||||
$day = (int)$matches[2];
|
||||
if ($month < 1 || $month > 12 || $day < 1 || $day > 31) {
|
||||
return '生日日期格式不正确,月份应为1-12,日期应为1-31';
|
||||
}
|
||||
if (isset($matches[3])) {
|
||||
return [
|
||||
'month' => $month,
|
||||
'day' => $day,
|
||||
'time' => $matches[3]
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
'month' => $month,
|
||||
'day' => $day
|
||||
];
|
||||
}
|
||||
} else {
|
||||
return '生日日期格式不正确,应为 MM-DD 格式(如 10-10),不包含年份';
|
||||
}
|
||||
}
|
||||
|
||||
// 新格式:{'month': 10, 'day': 10, 'time': '09:00'}
|
||||
if (isset($condition['month']) && isset($condition['day'])) {
|
||||
$month = (int)$condition['month'];
|
||||
$day = (int)$condition['day'];
|
||||
|
||||
if ($month < 1 || $month > 12) {
|
||||
return '生日月份格式不正确,应为1-12';
|
||||
}
|
||||
if ($day < 1 || $day > 31) {
|
||||
return '生日日期格式不正确,应为1-31';
|
||||
}
|
||||
|
||||
$result = [
|
||||
'month' => $month,
|
||||
'day' => $day
|
||||
];
|
||||
|
||||
// 检查是否配置了时间
|
||||
if (isset($condition['time']) && !empty($condition['time'])) {
|
||||
$time = trim($condition['time']);
|
||||
if (!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $time)) {
|
||||
return '生日时间格式不正确,应为 HH:mm 格式(如 09:00)';
|
||||
}
|
||||
$result['time'] = $time;
|
||||
}
|
||||
|
||||
// 检查是否配置了时间范围
|
||||
if (isset($condition['time_range']) && is_array($condition['time_range']) && count($condition['time_range']) === 2) {
|
||||
$startTime = trim($condition['time_range'][0]);
|
||||
$endTime = trim($condition['time_range'][1]);
|
||||
if (!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $startTime) ||
|
||||
!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $endTime)) {
|
||||
return '生日时间范围格式不正确,应为 ["HH:mm", "HH:mm"] 格式';
|
||||
}
|
||||
$result['time_range'] = [$startTime, $endTime];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
return '生日触发条件格式不正确,需要提供month和day字段';
|
||||
}
|
||||
|
||||
return '生日触发条件格式不正确';
|
||||
|
||||
case 6: // 自定义
|
||||
// 自定义类型,condition可选,如果有则必须是数组格式
|
||||
if (!empty($condition)) {
|
||||
$condition = is_array($condition) ? $condition : json_decode($condition, true);
|
||||
if (!is_array($condition)) {
|
||||
return '自定义类型的条件格式不正确,应为数组格式';
|
||||
}
|
||||
return $condition;
|
||||
}
|
||||
return [];
|
||||
|
||||
default:
|
||||
return '无效的触发类型';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
* @return \think\response\Json
|
||||
@@ -71,17 +410,18 @@ class AutoGreetingsController extends BaseController
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
if (in_array($trigger,[2,3]) && empty($condition)){
|
||||
return ResponseHelper::error('具体条件不能为空');
|
||||
// 校验trigger类型
|
||||
if (!in_array($trigger, [1, 2, 3, 4, 5, 6])) {
|
||||
return ResponseHelper::error('无效的触发类型');
|
||||
}
|
||||
|
||||
if ($trigger == 2){
|
||||
$condition = !empty($condition) ? $condition : [];
|
||||
}
|
||||
|
||||
if ($trigger == 3){
|
||||
$condition = explode(',',$condition);
|
||||
// 校验并处理condition
|
||||
$conditionResult = $this->validateTriggerCondition($trigger, $condition);
|
||||
if (is_string($conditionResult)) {
|
||||
// 返回的是错误信息
|
||||
return ResponseHelper::error($conditionResult);
|
||||
}
|
||||
$condition = $conditionResult;
|
||||
|
||||
|
||||
Db::startTrans();
|
||||
@@ -97,9 +437,10 @@ class AutoGreetingsController extends BaseController
|
||||
$AutoGreetings->companyId = $companyId;
|
||||
$AutoGreetings->updateTime = time();
|
||||
$AutoGreetings->createTime = time();
|
||||
$AutoGreetings->usageCount = 0; // 初始化使用次数为0
|
||||
$AutoGreetings->save();
|
||||
Db::commit();
|
||||
return ResponseHelper::success(' ','创建成功');
|
||||
return ResponseHelper::success(['id' => $AutoGreetings->id],'创建成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('创建失败:'.$e->getMessage());
|
||||
@@ -128,11 +469,13 @@ class AutoGreetingsController extends BaseController
|
||||
|
||||
|
||||
$data['condition'] = json_decode($data['condition'],true);
|
||||
if ($data['trigger'] == 3){
|
||||
$data['condition'] = implode(',',$data['condition']);
|
||||
}
|
||||
|
||||
// 获取使用次数
|
||||
$usageCount = Db::name('kf_auto_greetings_record')
|
||||
->where('autoId', $id)
|
||||
->count();
|
||||
$data['usageCount'] = (int)$usageCount;
|
||||
|
||||
unset($data['createTime'],$data['updateTime'],$data['isDel'],$data['delTime']);
|
||||
return ResponseHelper::success($data,'获取成功');
|
||||
}
|
||||
|
||||
@@ -175,7 +518,7 @@ class AutoGreetingsController extends BaseController
|
||||
$id = $this->request->param('id', '');
|
||||
$name = $this->request->param('name', '');
|
||||
$trigger = $this->request->param('trigger', 0);
|
||||
$condition = $this->request->param('condition', []);
|
||||
$condition = $this->request->param('condition', '');
|
||||
$content = $this->request->param('content', '');
|
||||
$level = $this->request->param('level', 0);
|
||||
$status = $this->request->param('status', 1);
|
||||
@@ -186,17 +529,18 @@ class AutoGreetingsController extends BaseController
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
if (in_array($trigger,[2,3]) && empty($condition)){
|
||||
return ResponseHelper::error('具体条件不能为空');
|
||||
// 校验trigger类型
|
||||
if (!in_array($trigger, [1, 2, 3, 4, 5, 6])) {
|
||||
return ResponseHelper::error('无效的触发类型');
|
||||
}
|
||||
|
||||
if ($trigger == 2){
|
||||
$condition = !empty($condition) ? $condition : [];
|
||||
}
|
||||
|
||||
if ($trigger == 3){
|
||||
$condition = explode(',',$condition);
|
||||
// 校验并处理condition
|
||||
$conditionResult = $this->validateTriggerCondition($trigger, $condition);
|
||||
if (is_string($conditionResult)) {
|
||||
// 返回的是错误信息
|
||||
return ResponseHelper::error($conditionResult);
|
||||
}
|
||||
$condition = $conditionResult;
|
||||
|
||||
|
||||
$query = AutoGreetings::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
|
||||
@@ -244,11 +588,16 @@ class AutoGreetingsController extends BaseController
|
||||
}
|
||||
Db::startTrans();
|
||||
try {
|
||||
$query->status = !empty($query['status']) ? 0 : 1;
|
||||
$status = $this->request->param('status', '');
|
||||
if ($status !== '') {
|
||||
$query->status = (int)$status;
|
||||
} else {
|
||||
$query->status = $query->status == 1 ? 0 : 1;
|
||||
}
|
||||
$query->updateTime = time();
|
||||
$query->save();
|
||||
Db::commit();
|
||||
return ResponseHelper::success(' ','修改成功');
|
||||
return ResponseHelper::success(['status' => $query->status],'修改成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('修改失败:'.$e->getMessage());
|
||||
|
||||
17
Server/application/chukebao/model/AiPush.php
Normal file
17
Server/application/chukebao/model/AiPush.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class AiPush extends Model
|
||||
{
|
||||
protected $pk = 'id';
|
||||
protected $name = 'kf_ai_push';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $createTime = 'createTime';
|
||||
protected $updateTime = 'updateTime';
|
||||
}
|
||||
|
||||
16
Server/application/chukebao/model/AiPushRecord.php
Normal file
16
Server/application/chukebao/model/AiPushRecord.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class AiPushRecord extends Model
|
||||
{
|
||||
protected $pk = 'id';
|
||||
protected $name = 'kf_ai_push_record';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $createTime = 'createTime';
|
||||
}
|
||||
|
||||
@@ -78,6 +78,14 @@ class TaskServer extends Server
|
||||
});
|
||||
}
|
||||
|
||||
// 在一个进程里处理自动问候任务
|
||||
if ($current_worker_id == 1) {
|
||||
// 每60秒检查一次自动问候规则
|
||||
Timer::add(60, function () use ($adapter) {
|
||||
$adapter->handleAutoGreetings();
|
||||
});
|
||||
}
|
||||
|
||||
// 更多其他后台任务
|
||||
// ......
|
||||
|
||||
|
||||
@@ -112,8 +112,6 @@ class PaymentService
|
||||
$parsed = $this->parseXmlOrRaw($response);
|
||||
|
||||
|
||||
exit_data($parsed);
|
||||
|
||||
if ($parsed['status'] == 0 && $parsed['result_code'] == 0) {
|
||||
Db::commit();
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@ class LoginController extends Controller
|
||||
->join('device d', 'du.deviceId = d.id and u.companyId = du.companyId')
|
||||
->where(['d.deviceImei' => $deviceId, 'u.deleteTime' => 0, 'du.deleteTime' => 0, 'd.deleteTime' => 0])
|
||||
->find();
|
||||
if (empty($user)) {
|
||||
return errorJson('用户不存在');
|
||||
}
|
||||
$member = array_merge($user, [
|
||||
'lastLoginIp' => $this->request->ip(),
|
||||
'lastLoginTime' => time()
|
||||
|
||||
@@ -1537,4 +1537,500 @@ class Adapter implements WeChatServiceInterface
|
||||
} while ($affected > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理自动问候功能
|
||||
* 根据不同的触发类型检查并发送问候消息
|
||||
*/
|
||||
public function handleAutoGreetings()
|
||||
{
|
||||
try {
|
||||
// 获取所有启用的问候规则
|
||||
$rules = Db::name('kf_auto_greetings')
|
||||
->where(['status' => 1, 'isDel' => 0])
|
||||
->order('level asc, id asc')
|
||||
->select();
|
||||
|
||||
if (empty($rules)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($rules as $rule) {
|
||||
$trigger = $rule['trigger'];
|
||||
$condition = json_decode($rule['condition'], true);
|
||||
|
||||
switch ($trigger) {
|
||||
case 1: // 新好友
|
||||
$this->handleNewFriendGreeting($rule);
|
||||
break;
|
||||
case 2: // 首次发消息
|
||||
$this->handleFirstMessageGreeting($rule);
|
||||
break;
|
||||
case 3: // 时间触发
|
||||
$this->handleTimeTriggerGreeting($rule, $condition);
|
||||
break;
|
||||
case 4: // 关键词触发
|
||||
$this->handleKeywordTriggerGreeting($rule, $condition);
|
||||
break;
|
||||
case 5: // 生日触发
|
||||
$this->handleBirthdayTriggerGreeting($rule, $condition);
|
||||
break;
|
||||
case 6: // 自定义
|
||||
$this->handleCustomTriggerGreeting($rule, $condition);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('自动问候处理失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理新好友触发
|
||||
*/
|
||||
private function handleNewFriendGreeting($rule)
|
||||
{
|
||||
// 获取最近24小时内添加的好友(避免重复处理)
|
||||
$last24h = time() - 24 * 3600;
|
||||
|
||||
// 查询该用户/公司最近24小时内新添加的好友
|
||||
// 通过 s2_wechat_account -> s2_company_account 关联获取 companyId
|
||||
$friends = Db::table('s2_wechat_friend')
|
||||
->alias('wf')
|
||||
->join(['s2_wechat_account' => 'wa'], 'wf.wechatAccountId = wa.id')
|
||||
->join(['s2_company_account' => 'ca'], 'wa.deviceAccountId = ca.id')
|
||||
->where([
|
||||
['wf.isPassed', '=', 1],
|
||||
['wf.isDeleted', '=', 0],
|
||||
['wf.passTime', '>=', $last24h],
|
||||
['ca.departmentId', '=', $rule['companyId']],
|
||||
])
|
||||
->field('wf.id, wf.wechatAccountId')
|
||||
->select();
|
||||
|
||||
foreach ($friends as $friend) {
|
||||
// 检查是否已经发送过问候
|
||||
$exists = Db::name('kf_auto_greetings_record')
|
||||
->where([
|
||||
'autoId' => $rule['id'],
|
||||
'friendIdOrGroupId' => $friend['id'],
|
||||
'wechatAccountId' => $friend['wechatAccountId'],
|
||||
])
|
||||
->find();
|
||||
|
||||
if (!$exists) {
|
||||
$this->sendGreetingMessage($rule, $friend['wechatAccountId'], $friend['id'], 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理首次发消息触发
|
||||
*/
|
||||
private function handleFirstMessageGreeting($rule)
|
||||
{
|
||||
// 获取最近1小时内收到的消息
|
||||
$last1h = time() - 3600;
|
||||
|
||||
// 查询消息表,找出首次发消息的好友
|
||||
// 通过 s2_wechat_account -> s2_company_account 关联获取 companyId
|
||||
$messages = Db::table('s2_wechat_message')
|
||||
->alias('wm')
|
||||
->join(['s2_wechat_account' => 'wa'], 'wm.wechatAccountId = wa.id')
|
||||
->join(['s2_company_account' => 'ca'], 'wa.deviceAccountId = ca.id')
|
||||
->where([
|
||||
['wm.isSend', '=', 0], // 接收的消息
|
||||
['wm.wechatChatroomId', '=', 0], // 个人消息
|
||||
['wm.createTime', '>=', $last1h],
|
||||
['ca.departmentId', '=', $rule['companyId']],
|
||||
])
|
||||
->group('wm.wechatFriendId, wm.wechatAccountId')
|
||||
->field('wm.wechatFriendId, wm.wechatAccountId, MIN(wm.createTime) as firstMsgTime')
|
||||
->select();
|
||||
|
||||
foreach ($messages as $msg) {
|
||||
// 检查该好友是否之前发送过消息
|
||||
$previousMsg = Db::table('s2_wechat_message')
|
||||
->where([
|
||||
'wechatFriendId' => $msg['wechatFriendId'],
|
||||
'wechatAccountId' => $msg['wechatAccountId'],
|
||||
'isSend' => 0,
|
||||
])
|
||||
->where('createTime', '<', $msg['firstMsgTime'])
|
||||
->find();
|
||||
|
||||
// 如果是首次发消息,且没有发送过问候
|
||||
if (!$previousMsg) {
|
||||
$exists = Db::name('kf_auto_greetings_record')
|
||||
->where([
|
||||
'autoId' => $rule['id'],
|
||||
'friendIdOrGroupId' => $msg['wechatFriendId'],
|
||||
'wechatAccountId' => $msg['wechatAccountId'],
|
||||
])
|
||||
->find();
|
||||
|
||||
if (!$exists) {
|
||||
$this->sendGreetingMessage($rule, $msg['wechatAccountId'], $msg['wechatFriendId'], 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理时间触发
|
||||
*/
|
||||
private function handleTimeTriggerGreeting($rule, $condition)
|
||||
{
|
||||
if (empty($condition) || !isset($condition['type'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$currentTime = date('H:i', $now);
|
||||
$currentDate = date('m-d', $now);
|
||||
$currentDateTime = date('m-d H:i', $now);
|
||||
$currentWeekday = date('w', $now); // 0=周日, 1=周一, ..., 6=周六
|
||||
|
||||
$shouldTrigger = false;
|
||||
|
||||
switch ($condition['type']) {
|
||||
case 'daily_time': // 每天固定时间
|
||||
if ($currentTime === $condition['value']) {
|
||||
$shouldTrigger = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'yearly_datetime': // 每年固定日期时间
|
||||
if ($currentDateTime === $condition['value']) {
|
||||
$shouldTrigger = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'fixed_range': // 固定时间段
|
||||
if (is_array($condition['value']) && count($condition['value']) === 2) {
|
||||
$startTime = strtotime('2000-01-01 ' . $condition['value'][0]);
|
||||
$endTime = strtotime('2000-01-01 ' . $condition['value'][1]);
|
||||
$currentTimeStamp = strtotime('2000-01-01 ' . $currentTime);
|
||||
|
||||
if ($currentTimeStamp >= $startTime && $currentTimeStamp <= $endTime) {
|
||||
$shouldTrigger = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'workday': // 工作日
|
||||
// 周一到周五(1-5)
|
||||
if ($currentWeekday >= 1 && $currentWeekday <= 5 && $currentTime === $condition['value']) {
|
||||
$shouldTrigger = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if ($shouldTrigger) {
|
||||
// 获取该用户/公司的所有好友
|
||||
// 通过 s2_wechat_account -> s2_company_account 关联获取 companyId
|
||||
$friends = Db::table('s2_wechat_friend')
|
||||
->alias('wf')
|
||||
->join(['s2_wechat_account' => 'wa'], 'wf.wechatAccountId = wa.id')
|
||||
->join(['s2_company_account' => 'ca'], 'wa.deviceAccountId = ca.id')
|
||||
->where([
|
||||
['wf.isPassed', '=', 1],
|
||||
['wf.isDeleted', '=', 0],
|
||||
['ca.departmentId', '=', $rule['companyId']],
|
||||
])
|
||||
->field('wf.id, wf.wechatAccountId')
|
||||
->select();
|
||||
|
||||
foreach ($friends as $friend) {
|
||||
// 检查今天是否已经发送过
|
||||
$todayStart = strtotime(date('Y-m-d 00:00:00'));
|
||||
$exists = Db::name('kf_auto_greetings_record')
|
||||
->where([
|
||||
'autoId' => $rule['id'],
|
||||
'friendIdOrGroupId' => $friend['id'],
|
||||
'wechatAccountId' => $friend['wechatAccountId'],
|
||||
])
|
||||
->where('createTime', '>=', $todayStart)
|
||||
->find();
|
||||
|
||||
if (!$exists) {
|
||||
$this->sendGreetingMessage($rule, $friend['wechatAccountId'], $friend['id'], 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理关键词触发
|
||||
*/
|
||||
private function handleKeywordTriggerGreeting($rule, $condition)
|
||||
{
|
||||
if (empty($condition) || empty($condition['keywords'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$keywords = $condition['keywords'];
|
||||
$matchType = $condition['match_type'] ?? 'fuzzy';
|
||||
|
||||
// 获取最近1小时内收到的消息
|
||||
$last1h = time() - 3600;
|
||||
|
||||
// 通过 s2_wechat_account -> s2_company_account 关联获取 companyId
|
||||
$messages = Db::table('s2_wechat_message')
|
||||
->alias('wm')
|
||||
->join(['s2_wechat_account' => 'wa'], 'wm.wechatAccountId = wa.id')
|
||||
->join(['s2_company_account' => 'ca'], 'wa.deviceAccountId = ca.id')
|
||||
->where([
|
||||
['wm.isSend', '=', 0], // 接收的消息
|
||||
['wm.wechatChatroomId', '=', 0], // 个人消息
|
||||
['wm.msgType', '=', 1], // 文本消息
|
||||
['wm.createTime', '>=', $last1h],
|
||||
['ca.departmentId', '=', $rule['companyId']],
|
||||
])
|
||||
->field('wm.*')
|
||||
->select();
|
||||
|
||||
foreach ($messages as $msg) {
|
||||
$content = $msg['content'] ?? '';
|
||||
|
||||
// 检查关键词匹配
|
||||
$matched = false;
|
||||
foreach ($keywords as $keyword) {
|
||||
if ($matchType === 'exact') {
|
||||
// 精准匹配
|
||||
if ($content === $keyword) {
|
||||
$matched = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// 模糊匹配
|
||||
if (strpos($content, $keyword) !== false) {
|
||||
$matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($matched) {
|
||||
// 检查是否已经发送过问候(同一好友同一规则,1小时内只发送一次)
|
||||
$last1h = time() - 3600;
|
||||
$exists = Db::name('kf_auto_greetings_record')
|
||||
->where([
|
||||
'autoId' => $rule['id'],
|
||||
'friendIdOrGroupId' => $msg['wechatFriendId'],
|
||||
'wechatAccountId' => $msg['wechatAccountId'],
|
||||
])
|
||||
->where('createTime', '>=', $last1h)
|
||||
->find();
|
||||
|
||||
if (!$exists) {
|
||||
$this->sendGreetingMessage($rule, $msg['wechatAccountId'], $msg['wechatFriendId'], 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理生日触发
|
||||
*/
|
||||
private function handleBirthdayTriggerGreeting($rule, $condition)
|
||||
{
|
||||
if (empty($condition)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 解析condition格式
|
||||
// 支持格式:
|
||||
// 1. {'month': 10, 'day': 10} - 当天任何时间都可以触发
|
||||
// 2. {'month': 10, 'day': 10, 'time': '09:00'} - 当天指定时间触发
|
||||
// 3. {'month': 10, 'day': 10, 'time_range': ['09:00', '10:00']} - 当天时间范围内触发
|
||||
// 兼容旧格式:['10-10'] 或 '10-10'(仅支持 MM-DD 格式,不包含年份)
|
||||
|
||||
$birthdayMonth = null;
|
||||
$birthdayDay = null;
|
||||
$birthdayTime = null;
|
||||
$timeRange = null;
|
||||
|
||||
if (is_array($condition)) {
|
||||
// 新格式:对象格式 {'month': 10, 'day': 10}
|
||||
if (isset($condition['month']) && isset($condition['day'])) {
|
||||
$birthdayMonth = (int)$condition['month'];
|
||||
$birthdayDay = (int)$condition['day'];
|
||||
$birthdayTime = $condition['time'] ?? null;
|
||||
$timeRange = $condition['time_range'] ?? null;
|
||||
}
|
||||
// 兼容旧格式:['10-10'] 或 ['10-10 09:00'](仅支持 MM-DD 格式)
|
||||
elseif (isset($condition[0])) {
|
||||
$dateStr = $condition[0];
|
||||
// 只接受月日格式:'10-10' 或 '10-10 09:00'
|
||||
if (preg_match('/^(\d{1,2})-(\d{1,2})(?:\s+(\d{2}:\d{2}))?$/', $dateStr, $matches)) {
|
||||
$birthdayMonth = (int)$matches[1];
|
||||
$birthdayDay = (int)$matches[2];
|
||||
if (isset($matches[3])) {
|
||||
$birthdayTime = $matches[3];
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif (is_string($condition)) {
|
||||
// 字符串格式:只接受 '10-10' 或 '10-10 09:00'(MM-DD 格式,不包含年份)
|
||||
if (preg_match('/^(\d{1,2})-(\d{1,2})(?:\s+(\d{2}:\d{2}))?$/', $condition, $matches)) {
|
||||
$birthdayMonth = (int)$matches[1];
|
||||
$birthdayDay = (int)$matches[2];
|
||||
if (isset($matches[3])) {
|
||||
$birthdayTime = $matches[3];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($birthdayMonth === null || $birthdayDay === null || $birthdayMonth < 1 || $birthdayMonth > 12 || $birthdayDay < 1 || $birthdayDay > 31) {
|
||||
return;
|
||||
}
|
||||
|
||||
$todayMonth = (int)date('m');
|
||||
$todayDay = (int)date('d');
|
||||
|
||||
// 检查今天是否是生日(只匹配月日,不匹配年份)
|
||||
if ($todayMonth !== $birthdayMonth || $todayDay !== $birthdayDay) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果配置了时间,检查当前时间是否匹配
|
||||
$now = time();
|
||||
$currentTime = date('H:i', $now);
|
||||
|
||||
if ($birthdayTime !== null) {
|
||||
// 指定了具体时间,检查是否在指定时间(允许1分钟误差,避免定时任务执行时间不精确)
|
||||
$birthdayTimestamp = strtotime('2000-01-01 ' . $birthdayTime);
|
||||
$currentTimestamp = strtotime('2000-01-01 ' . $currentTime);
|
||||
$diff = abs($currentTimestamp - $birthdayTimestamp);
|
||||
|
||||
// 如果时间差超过2分钟,不触发(允许1分钟误差)
|
||||
if ($diff > 120) {
|
||||
return;
|
||||
}
|
||||
} elseif ($timeRange !== null && is_array($timeRange) && count($timeRange) === 2) {
|
||||
// 指定了时间范围,检查当前时间是否在范围内
|
||||
$startTime = strtotime('2000-01-01 ' . $timeRange[0]);
|
||||
$endTime = strtotime('2000-01-01 ' . $timeRange[1]);
|
||||
$currentTimestamp = strtotime('2000-01-01 ' . $currentTime);
|
||||
|
||||
if ($currentTimestamp < $startTime || $currentTimestamp > $endTime) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 如果没有配置时间或时间范围,则当天任何时间都可以触发
|
||||
|
||||
// 获取该用户/公司的所有好友
|
||||
// 通过 s2_wechat_account -> s2_company_account 关联获取 companyId
|
||||
$friends = Db::table('s2_wechat_friend')
|
||||
->alias('wf')
|
||||
->join(['s2_wechat_account' => 'wa'], 'wf.wechatAccountId = wa.id')
|
||||
->join(['s2_company_account' => 'ca'], 'wa.deviceAccountId = ca.id')
|
||||
->where([
|
||||
['wf.isPassed', '=', 1],
|
||||
['wf.isDeleted', '=', 0],
|
||||
['ca.departmentId', '=', $rule['companyId']],
|
||||
])
|
||||
->field('wf.id, wf.wechatAccountId')
|
||||
->select();
|
||||
|
||||
foreach ($friends as $friend) {
|
||||
// 检查今天是否已经发送过
|
||||
$todayStart = strtotime(date('Y-m-d 00:00:00'));
|
||||
$exists = Db::name('kf_auto_greetings_record')
|
||||
->where([
|
||||
'autoId' => $rule['id'],
|
||||
'friendIdOrGroupId' => $friend['id'],
|
||||
'wechatAccountId' => $friend['wechatAccountId'],
|
||||
])
|
||||
->where('createTime', '>=', $todayStart)
|
||||
->find();
|
||||
|
||||
if (!$exists) {
|
||||
$this->sendGreetingMessage($rule, $friend['wechatAccountId'], $friend['id'], 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理自定义触发
|
||||
*/
|
||||
private function handleCustomTriggerGreeting($rule, $condition)
|
||||
{
|
||||
// 自定义类型需要根据具体业务需求实现
|
||||
// 这里提供一个基础框架,可根据实际需求扩展
|
||||
// 暂时不实现,留待后续扩展
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送问候消息
|
||||
* @param array $rule 问候规则
|
||||
* @param int $wechatAccountId 微信账号ID
|
||||
* @param int $friendId 好友ID
|
||||
* @param int $groupId 群ID(0表示个人消息)
|
||||
*/
|
||||
private function sendGreetingMessage($rule, $wechatAccountId, $friendId, $groupId = 0)
|
||||
{
|
||||
try {
|
||||
$content = $rule['content'];
|
||||
|
||||
// 创建记录
|
||||
$recordId = Db::name('kf_auto_greetings_record')->insertGetId([
|
||||
'autoId' => $rule['id'],
|
||||
'userId' => $rule['userId'],
|
||||
'companyId' => $rule['companyId'],
|
||||
'wechatAccountId' => $wechatAccountId,
|
||||
'friendIdOrGroupId' => $friendId,
|
||||
'isSend' => 0,
|
||||
'sendTime' => 0,
|
||||
'receiveTime' => 0,
|
||||
'createTime' => time(),
|
||||
]);
|
||||
|
||||
// 发送消息(文本消息)
|
||||
$username = Env::get('api.username', '');
|
||||
$password = Env::get('api.password', '');
|
||||
$toAccountId = '';
|
||||
if (!empty($username) || !empty($password)) {
|
||||
$toAccountId = Db::name('users')->where('account', $username)->value('s2_accountId');
|
||||
}
|
||||
|
||||
$wsController = new WebSocketController(['userName' => $username, 'password' => $password, 'accountId' => $toAccountId]);
|
||||
|
||||
$sendTime = time();
|
||||
$result = $wsController->sendPersonal([
|
||||
'wechatFriendId' => $friendId,
|
||||
'wechatAccountId' => $wechatAccountId,
|
||||
'msgType' => 1, // 文本消息
|
||||
'content' => $content,
|
||||
]);
|
||||
|
||||
$isSend = 0;
|
||||
$receiveTime = 0;
|
||||
|
||||
// 解析返回结果
|
||||
$resultData = json_decode($result, true);
|
||||
if (!empty($resultData) && $resultData['code'] == 200) {
|
||||
$isSend = 1;
|
||||
$receiveTime = time(); // 简化处理,实际应该从返回结果中获取
|
||||
}
|
||||
|
||||
// 更新记录
|
||||
Db::name('kf_auto_greetings_record')
|
||||
->where('id', $recordId)
|
||||
->update([
|
||||
'isSend' => $isSend,
|
||||
'sendTime' => $sendTime,
|
||||
'receiveTime' => $receiveTime,
|
||||
]);
|
||||
|
||||
// 更新规则使用次数
|
||||
Db::name('kf_auto_greetings')
|
||||
->where('id', $rule['id'])
|
||||
->setInc('usageCount');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('发送问候消息失败:' . $e->getMessage() . ',规则ID:' . $rule['id']);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user