Merge branch 'develop' of https://gitee.com/Tyssen/yi-shi into develop
This commit is contained in:
4
Server/application/http/middleware/JwtAuth.php → Server/application/common/middleware/jwt.php
Executable file → Normal file
4
Server/application/http/middleware/JwtAuth.php → Server/application/common/middleware/jwt.php
Executable file → Normal file
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace app\http\middleware;
|
||||
namespace app\common\middleware;
|
||||
|
||||
use app\common\util\JwtUtil;
|
||||
use think\facade\Log;
|
||||
@@ -7,7 +7,7 @@ use think\facade\Log;
|
||||
/**
|
||||
* JWT认证中间件
|
||||
*/
|
||||
class JwtAuth
|
||||
class jwt
|
||||
{
|
||||
/**
|
||||
* 处理请求
|
||||
@@ -11,30 +11,7 @@ class Device extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $name = 'device';
|
||||
|
||||
// 设置主键
|
||||
protected $pk = 'id';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createTime';
|
||||
protected $updateTime = 'updateTime';
|
||||
protected $deleteTime = 'deleteTime';
|
||||
|
||||
// 定义字段类型
|
||||
protected $type = [
|
||||
'id' => 'integer',
|
||||
'createTime' => 'integer',
|
||||
'updateTime' => 'integer',
|
||||
'deleteTime' => 'integer',
|
||||
'alive' => 'integer',
|
||||
'isDeleted' => 'integer',
|
||||
'tenantId' => 'integer',
|
||||
'groupId' => 'integer'
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* 获取设备总数
|
||||
* @param array $where 查询条件
|
||||
|
||||
@@ -11,27 +11,7 @@ class DeviceHandleLog extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $name = 'device_handle_log';
|
||||
protected $prefix = 'tk_';
|
||||
|
||||
// 设置主键
|
||||
protected $pk = 'id';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = 'datetime';
|
||||
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createTime';
|
||||
protected $updateTime = false;
|
||||
|
||||
// 定义字段类型
|
||||
protected $type = [
|
||||
'id' => 'integer',
|
||||
'userId' => 'integer',
|
||||
'deviceId' => 'integer',
|
||||
'companyId' => 'integer',
|
||||
'createTime' => 'datetime'
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* 添加设备操作日志
|
||||
* @param array $data 日志数据
|
||||
|
||||
@@ -11,23 +11,6 @@ class DeviceWechatLogin extends Model
|
||||
// 设置表名
|
||||
protected $name = 'device_wechat_login';
|
||||
|
||||
// 设置主键
|
||||
protected $pk = 'id';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createTime';
|
||||
|
||||
// 定义字段类型
|
||||
protected $type = [
|
||||
'id' => 'integer',
|
||||
'deviceId' => 'integer',
|
||||
'companyId' => 'integer',
|
||||
'createTime' => 'integer'
|
||||
];
|
||||
|
||||
/**
|
||||
* 查询设备关联的微信ID列表
|
||||
* @param int $deviceId 设备ID
|
||||
|
||||
@@ -14,44 +14,6 @@ class FriendTask extends Model
|
||||
*/
|
||||
protected $table = 'tk_friend_task';
|
||||
|
||||
/**
|
||||
* 主键
|
||||
* @var string
|
||||
*/
|
||||
protected $pk = 'id';
|
||||
|
||||
/**
|
||||
* 自动写入时间戳
|
||||
* @var bool
|
||||
*/
|
||||
protected $autoWriteTimestamp = true;
|
||||
|
||||
/**
|
||||
* 创建时间字段
|
||||
* @var string
|
||||
*/
|
||||
protected $createTime = 'createTime';
|
||||
|
||||
/**
|
||||
* 更新时间字段
|
||||
* @var string
|
||||
*/
|
||||
protected $updateTime = 'updateTime';
|
||||
|
||||
/**
|
||||
* 字段类型
|
||||
* @var array
|
||||
*/
|
||||
protected $type = [
|
||||
'id' => 'integer',
|
||||
'tenantId' => 'integer',
|
||||
'operatorAccountId' => 'integer',
|
||||
'status' => 'integer',
|
||||
'wechatAccountId' => 'integer',
|
||||
'createTime' => 'integer',
|
||||
'updateTime' => 'integer'
|
||||
];
|
||||
|
||||
/**
|
||||
* 状态常量
|
||||
*/
|
||||
|
||||
@@ -12,36 +12,6 @@ class WechatAccount extends Model
|
||||
// 设置表名
|
||||
protected $name = 'wechat_account';
|
||||
|
||||
// 设置主键
|
||||
protected $pk = 'id';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = 'datetime';
|
||||
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createTime';
|
||||
protected $updateTime = 'updateTime';
|
||||
|
||||
// 定义字段类型
|
||||
protected $type = [
|
||||
'id' => 'integer',
|
||||
'deviceAccountId' => 'integer',
|
||||
'keFuAlive' => 'integer',
|
||||
'deviceAlive' => 'integer',
|
||||
'wechatAlive' => 'integer',
|
||||
'yesterdayMsgCount' => 'integer',
|
||||
'sevenDayMsgCount' => 'integer',
|
||||
'thirtyDayMsgCount' => 'integer',
|
||||
'totalFriend' => 'integer',
|
||||
'maleFriend' => 'integer',
|
||||
'femaleFriend' => 'integer',
|
||||
'gender' => 'integer',
|
||||
'currentDeviceId' => 'integer',
|
||||
'isDeleted' => 'integer',
|
||||
'groupId' => 'integer',
|
||||
'status' => 'integer'
|
||||
];
|
||||
|
||||
/**
|
||||
* 获取在线微信账号数量
|
||||
*
|
||||
|
||||
@@ -11,34 +11,7 @@ class WechatFriend extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $name = 'wechat_friend';
|
||||
protected $prefix = 'tk_';
|
||||
|
||||
// 设置主键
|
||||
protected $pk = 'id';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = 'datetime';
|
||||
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createTime';
|
||||
protected $updateTime = 'updateTime';
|
||||
|
||||
// 定义字段类型
|
||||
protected $type = [
|
||||
'id' => 'integer',
|
||||
'wechatAccountId' => 'integer',
|
||||
'gender' => 'integer',
|
||||
'addFrom' => 'integer',
|
||||
'isDeleted' => 'integer',
|
||||
'isPassed' => 'integer',
|
||||
'accountId' => 'integer',
|
||||
'groupId' => 'integer',
|
||||
'labels' => 'json',
|
||||
'deleteTime' => 'datetime',
|
||||
'passTime' => 'datetime',
|
||||
'createTime' => 'datetime'
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* 根据微信账号ID获取好友列表
|
||||
*
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
namespace app\http\middleware;
|
||||
|
||||
/**
|
||||
* JWT中间件别名
|
||||
* 解决类不存在: app\http\middleware\jwt的问题
|
||||
*/
|
||||
class jwt extends JwtAuth
|
||||
{
|
||||
// 继承JwtAuth的所有功能
|
||||
}
|
||||
@@ -10,6 +10,10 @@ Route::group('v1/', function () {
|
||||
// 获客场景相关
|
||||
Route::group('plan/scenes', function () {
|
||||
Route::get('', 'app\\plan\\controller\\Scene@index'); // 获取场景列表
|
||||
Route::get(':id', 'app\\plan\\controller\\Scene@read'); // 获取场景详情
|
||||
});
|
||||
|
||||
// 流量标签相关
|
||||
Route::group('traffic/tags', function () {
|
||||
Route::get('', 'app\\plan\\controller\\TrafficTag@index'); // 获取标签列表
|
||||
});
|
||||
})->middleware(['jwt']);
|
||||
@@ -1,247 +0,0 @@
|
||||
<?php
|
||||
namespace app\plan\controller;
|
||||
|
||||
use think\Controller;
|
||||
use think\Request;
|
||||
use app\plan\model\Tag as TagModel;
|
||||
use think\facade\Log;
|
||||
|
||||
/**
|
||||
* 标签控制器
|
||||
*/
|
||||
class Tag extends Controller
|
||||
{
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
protected function initialize()
|
||||
{
|
||||
parent::initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取标签列表
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$type = Request::param('type', '');
|
||||
$status = Request::param('status', 1, 'intval');
|
||||
|
||||
// 查询标签列表
|
||||
$tags = TagModel::getTagsByType($type, $status);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => $tags
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建标签
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$data = Request::post();
|
||||
|
||||
// 数据验证
|
||||
if (empty($data['name']) || empty($data['type'])) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'msg' => '缺少必要参数'
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
// 创建或获取标签
|
||||
$tagId = TagModel::getOrCreate($data['name'], $data['type']);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '创建成功',
|
||||
'data' => $tagId
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('创建标签异常', [
|
||||
'data' => $data,
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return json([
|
||||
'code' => 500,
|
||||
'msg' => '创建失败:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量创建标签
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function batchCreate()
|
||||
{
|
||||
$data = Request::post();
|
||||
|
||||
// 数据验证
|
||||
if (empty($data['names']) || empty($data['type'])) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'msg' => '缺少必要参数'
|
||||
]);
|
||||
}
|
||||
|
||||
// 检查名称数组
|
||||
if (!is_array($data['names'])) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'msg' => '标签名称必须是数组'
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
$result = [];
|
||||
|
||||
// 批量处理标签
|
||||
foreach ($data['names'] as $name) {
|
||||
$name = trim($name);
|
||||
if (empty($name)) continue;
|
||||
|
||||
$tagId = TagModel::getOrCreate($name, $data['type']);
|
||||
$result[] = [
|
||||
'id' => $tagId,
|
||||
'name' => $name,
|
||||
'type' => $data['type']
|
||||
];
|
||||
}
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '创建成功',
|
||||
'data' => $result
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('批量创建标签异常', [
|
||||
'data' => $data,
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return json([
|
||||
'code' => 500,
|
||||
'msg' => '创建失败:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新标签
|
||||
*
|
||||
* @param int $id
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function update($id)
|
||||
{
|
||||
$data = Request::put();
|
||||
|
||||
// 检查标签是否存在
|
||||
$tag = TagModel::get($id);
|
||||
if (!$tag) {
|
||||
return json([
|
||||
'code' => 404,
|
||||
'msg' => '标签不存在'
|
||||
]);
|
||||
}
|
||||
|
||||
// 准备更新数据
|
||||
$updateData = [];
|
||||
|
||||
// 只允许更新特定字段
|
||||
$allowedFields = ['name', 'status'];
|
||||
foreach ($allowedFields as $field) {
|
||||
if (isset($data[$field])) {
|
||||
$updateData[$field] = $data[$field];
|
||||
}
|
||||
}
|
||||
|
||||
// 更新标签
|
||||
$tag->save($updateData);
|
||||
|
||||
// 如果更新了标签名称,且该标签有使用次数,则增加计数
|
||||
if (isset($updateData['name']) && $updateData['name'] != $tag->name && $tag->count > 0) {
|
||||
$tag->updateCount(1);
|
||||
}
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '更新成功'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除标签
|
||||
*
|
||||
* @param int $id
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function delete($id)
|
||||
{
|
||||
// 检查标签是否存在
|
||||
$tag = TagModel::get($id);
|
||||
if (!$tag) {
|
||||
return json([
|
||||
'code' => 404,
|
||||
'msg' => '标签不存在'
|
||||
]);
|
||||
}
|
||||
|
||||
// 更新状态为删除
|
||||
$tag->save([
|
||||
'status' => 0
|
||||
]);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '删除成功'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取标签名称
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getNames()
|
||||
{
|
||||
$ids = Request::param('ids');
|
||||
|
||||
// 验证参数
|
||||
if (empty($ids)) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'msg' => '缺少标签ID参数'
|
||||
]);
|
||||
}
|
||||
|
||||
// 处理参数
|
||||
if (is_string($ids)) {
|
||||
$ids = explode(',', $ids);
|
||||
}
|
||||
|
||||
// 获取标签名称
|
||||
$names = TagModel::getTagNames($ids);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => $names
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,380 +0,0 @@
|
||||
<?php
|
||||
namespace app\plan\controller;
|
||||
|
||||
use think\Controller;
|
||||
use think\Request;
|
||||
use app\plan\model\TrafficPool;
|
||||
use app\plan\model\TrafficSource;
|
||||
use app\plan\service\SceneHandler;
|
||||
use think\facade\Log;
|
||||
|
||||
/**
|
||||
* 流量控制器
|
||||
*/
|
||||
class Traffic extends Controller
|
||||
{
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
protected function initialize()
|
||||
{
|
||||
parent::initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取流量池列表
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$page = Request::param('page', 1, 'intval');
|
||||
$limit = Request::param('limit', 10, 'intval');
|
||||
$keyword = Request::param('keyword', '');
|
||||
$status = Request::param('status', '', 'trim');
|
||||
$gender = Request::param('gender', '', 'trim');
|
||||
|
||||
// 构建查询条件
|
||||
$where = [];
|
||||
if (!empty($keyword)) {
|
||||
$where[] = ['mobile|tags', 'like', "%{$keyword}%"];
|
||||
}
|
||||
|
||||
if ($status !== '') {
|
||||
$where[] = ['status', '=', intval($status)];
|
||||
}
|
||||
|
||||
if ($gender !== '') {
|
||||
$where[] = ['gender', '=', intval($gender)];
|
||||
}
|
||||
|
||||
// 查询流量池列表
|
||||
$result = TrafficPool::getAvailableTraffic($where, 'id desc', $page, $limit);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => $result
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取流量详情
|
||||
*
|
||||
* @param int $id
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function read($id)
|
||||
{
|
||||
$traffic = TrafficPool::get($id);
|
||||
if (!$traffic) {
|
||||
return json([
|
||||
'code' => 404,
|
||||
'msg' => '流量记录不存在'
|
||||
]);
|
||||
}
|
||||
|
||||
// 获取流量来源
|
||||
$sources = TrafficSource::getSourcesByTrafficId($id);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'traffic' => $traffic,
|
||||
'sources' => $sources
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建或更新流量
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$data = Request::post();
|
||||
|
||||
// 数据验证
|
||||
$validate = validate('app\plan\validate\Traffic');
|
||||
if (!$validate->check($data)) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'msg' => $validate->getError()
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
// 添加或更新流量
|
||||
$result = TrafficPool::addOrUpdateTraffic(
|
||||
$data['mobile'],
|
||||
$data['gender'] ?? 0,
|
||||
$data['age'] ?? 0,
|
||||
$data['tags'] ?? '',
|
||||
$data['province'] ?? '',
|
||||
$data['city'] ?? '',
|
||||
$data['source_channel'] ?? '',
|
||||
$data['source_detail'] ?? []
|
||||
);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '保存成功',
|
||||
'data' => $result
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('保存流量记录异常', [
|
||||
'data' => $data,
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return json([
|
||||
'code' => 500,
|
||||
'msg' => '保存失败:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新流量记录
|
||||
*
|
||||
* @param int $id
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function update($id)
|
||||
{
|
||||
$data = Request::put();
|
||||
|
||||
// 检查流量记录是否存在
|
||||
$traffic = TrafficPool::get($id);
|
||||
if (!$traffic) {
|
||||
return json([
|
||||
'code' => 404,
|
||||
'msg' => '流量记录不存在'
|
||||
]);
|
||||
}
|
||||
|
||||
// 准备更新数据
|
||||
$updateData = [];
|
||||
|
||||
// 只允许更新特定字段
|
||||
$allowedFields = ['gender', 'age', 'tags', 'province', 'city', 'status'];
|
||||
foreach ($allowedFields as $field) {
|
||||
if (isset($data[$field])) {
|
||||
$updateData[$field] = $data[$field];
|
||||
}
|
||||
}
|
||||
|
||||
// 更新流量记录
|
||||
$traffic->save($updateData);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '更新成功'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除流量记录
|
||||
*
|
||||
* @param int $id
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function delete($id)
|
||||
{
|
||||
// 检查流量记录是否存在
|
||||
$traffic = TrafficPool::get($id);
|
||||
if (!$traffic) {
|
||||
return json([
|
||||
'code' => 404,
|
||||
'msg' => '流量记录不存在'
|
||||
]);
|
||||
}
|
||||
|
||||
// 更新状态为无效
|
||||
$traffic->save([
|
||||
'status' => 0
|
||||
]);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '删除成功'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取流量来源统计
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function sourceStats()
|
||||
{
|
||||
$channel = Request::param('channel', '');
|
||||
$planId = Request::param('plan_id', 0, 'intval');
|
||||
$sceneId = Request::param('scene_id', 0, 'intval');
|
||||
$startDate = Request::param('start_date', '', 'trim');
|
||||
$endDate = Request::param('end_date', '', 'trim');
|
||||
|
||||
// 获取统计数据
|
||||
$stats = TrafficSource::getSourceStats($channel, $planId, $sceneId, $startDate, $endDate);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => $stats
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理外部流量
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function handleExternalTraffic()
|
||||
{
|
||||
$data = Request::post();
|
||||
|
||||
// 验证必要参数
|
||||
if (empty($data['scene_id']) || empty($data['mobile'])) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'msg' => '缺少必要参数'
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取场景处理器
|
||||
$handler = SceneHandler::getHandler($data['scene_id']);
|
||||
|
||||
// 根据场景类型处理流量
|
||||
switch ($data['scene_type'] ?? '') {
|
||||
case 'poster':
|
||||
$result = $handler->handlePosterScan($data['mobile'], $data);
|
||||
break;
|
||||
|
||||
case 'order':
|
||||
$result = $handler->handleOrderImport($data['orders'] ?? []);
|
||||
break;
|
||||
|
||||
default:
|
||||
$result = $handler->handleChannelTraffic($data['mobile'], $data['channel'] ?? '', $data);
|
||||
}
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '处理成功',
|
||||
'data' => $result
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('处理外部流量异常', [
|
||||
'data' => $data,
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return json([
|
||||
'code' => 500,
|
||||
'msg' => '处理失败:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量导入流量
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function importTraffic()
|
||||
{
|
||||
// 检查是否上传了文件
|
||||
$file = Request::file('file');
|
||||
if (!$file) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'msg' => '未上传文件'
|
||||
]);
|
||||
}
|
||||
|
||||
// 检查文件类型,只允许csv或xlsx
|
||||
$fileExt = strtolower($file->getOriginalExtension());
|
||||
if (!in_array($fileExt, ['csv', 'xlsx'])) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'msg' => '仅支持CSV或XLSX格式文件'
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
// 处理上传文件
|
||||
$saveName = \think\facade\Filesystem::disk('upload')->putFile('traffic', $file);
|
||||
$filePath = app()->getRuntimePath() . 'storage/upload/' . $saveName;
|
||||
|
||||
// 读取文件内容并导入
|
||||
$results = [];
|
||||
$success = 0;
|
||||
$fail = 0;
|
||||
|
||||
// 这里简化处理,实际应当使用专业的Excel/CSV解析库
|
||||
if ($fileExt == 'csv') {
|
||||
$handle = fopen($filePath, 'r');
|
||||
|
||||
// 跳过标题行
|
||||
fgetcsv($handle);
|
||||
|
||||
while (($data = fgetcsv($handle)) !== false) {
|
||||
if (count($data) < 1) continue;
|
||||
|
||||
$mobile = trim($data[0]);
|
||||
// 验证手机号
|
||||
if (!preg_match('/^1[3-9]\d{9}$/', $mobile)) {
|
||||
$fail++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 添加或更新流量
|
||||
TrafficPool::addOrUpdateTraffic(
|
||||
$mobile,
|
||||
isset($data[1]) ? intval($data[1]) : 0, // 性别
|
||||
isset($data[2]) ? intval($data[2]) : 0, // 年龄
|
||||
isset($data[3]) ? $data[3] : '', // 标签
|
||||
isset($data[4]) ? $data[4] : '', // 省份
|
||||
isset($data[5]) ? $data[5] : '', // 城市
|
||||
'import', // 来源渠道
|
||||
['detail' => '批量导入'] // 来源详情
|
||||
);
|
||||
|
||||
$success++;
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
} else {
|
||||
// 处理xlsx文件,实际应当使用专业的Excel解析库
|
||||
// 此处代码省略,依赖于具体的Excel解析库
|
||||
}
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '导入完成',
|
||||
'data' => [
|
||||
'success' => $success,
|
||||
'fail' => $fail
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('批量导入流量异常', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return json([
|
||||
'code' => 500,
|
||||
'msg' => '导入失败:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
64
Server/application/plan/controller/TrafficTag.php
Normal file
64
Server/application/plan/controller/TrafficTag.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
namespace app\plan\controller;
|
||||
|
||||
use think\Controller;
|
||||
use think\facade\Request;
|
||||
use app\plan\model\TrafficTag as TrafficTagModel;
|
||||
|
||||
/**
|
||||
* 流量标签控制器
|
||||
*/
|
||||
class TrafficTag extends Controller
|
||||
{
|
||||
/**
|
||||
* 获取标签列表
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
// 获取登录用户信息
|
||||
$userInfo = request()->userInfo;
|
||||
|
||||
// 获取查询条件
|
||||
$where = [];
|
||||
|
||||
// 关键词搜索
|
||||
$keyword = Request::param('keyword', '');
|
||||
if (!empty($keyword)) {
|
||||
$where[] = ['tagName', 'like', "%{$keyword}%"];
|
||||
}
|
||||
|
||||
// 添加公司ID过滤条件
|
||||
$where[] = ['companyId', '=', $userInfo['companyId']];
|
||||
|
||||
// 获取分页参数
|
||||
$page = (int)Request::param('page', 1);
|
||||
$limit = (int)Request::param('limit', 200); // 默认每页显示200条
|
||||
|
||||
// 获取排序参数
|
||||
$sort = Request::param('sort', 'id');
|
||||
$order = Request::param('order', 'desc');
|
||||
|
||||
// 查询列表
|
||||
$list = TrafficTagModel::getTagsByCompany($where, "{$sort} {$order}", $page, $limit);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'total' => $list->total(),
|
||||
'list' => $list->items(),
|
||||
'page' => $page,
|
||||
'limit' => $limit
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return json([
|
||||
'code' => 500,
|
||||
'msg' => '获取失败:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
<?php
|
||||
namespace app\plan\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 计划执行记录模型
|
||||
*/
|
||||
class PlanExecution extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $name = 'plan_execution';
|
||||
protected $prefix = 'tk_';
|
||||
|
||||
// 设置主键
|
||||
protected $pk = 'id';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createTime';
|
||||
protected $updateTime = 'updateTime';
|
||||
|
||||
// 定义字段类型
|
||||
protected $type = [
|
||||
'id' => 'integer',
|
||||
'plan_id' => 'integer',
|
||||
'traffic_id' => 'integer',
|
||||
'step' => 'integer',
|
||||
'status' => 'integer',
|
||||
'result' => 'json',
|
||||
'start_time' => 'integer',
|
||||
'end_time' => 'integer',
|
||||
'createTime' => 'integer',
|
||||
'updateTime' => 'integer'
|
||||
];
|
||||
|
||||
/**
|
||||
* 状态文本获取器
|
||||
* @param int $value 状态值
|
||||
* @return string 状态文本
|
||||
*/
|
||||
public function getStatusTextAttr($value, $data)
|
||||
{
|
||||
$status = [
|
||||
0 => '等待',
|
||||
1 => '进行中',
|
||||
2 => '成功',
|
||||
3 => '失败'
|
||||
];
|
||||
return isset($status[$data['status']]) ? $status[$data['status']] : '未知';
|
||||
}
|
||||
|
||||
/**
|
||||
* 步骤文本获取器
|
||||
* @param int $value 步骤值
|
||||
* @return string 步骤文本
|
||||
*/
|
||||
public function getStepTextAttr($value, $data)
|
||||
{
|
||||
$steps = [
|
||||
1 => '基础配置',
|
||||
2 => '加友计划',
|
||||
3 => 'API调用',
|
||||
4 => '标签处理'
|
||||
];
|
||||
return isset($steps[$data['step']]) ? $steps[$data['step']] : '未知';
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建执行记录
|
||||
* @param int $planId 计划ID
|
||||
* @param int $step 步骤
|
||||
* @param array $data 额外数据
|
||||
* @return int 新增记录ID
|
||||
*/
|
||||
public static function createExecution($planId, $step, $data = [])
|
||||
{
|
||||
$model = new self();
|
||||
$model->save(array_merge([
|
||||
'plan_id' => $planId,
|
||||
'step' => $step,
|
||||
'status' => 0, // 等待状态
|
||||
'start_time' => time()
|
||||
], $data));
|
||||
|
||||
return $model->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新执行状态
|
||||
* @param int $id 记录ID
|
||||
* @param int $status 状态
|
||||
* @param array $data 额外数据
|
||||
* @return bool 更新结果
|
||||
*/
|
||||
public static function updateExecution($id, $status, $data = [])
|
||||
{
|
||||
$updateData = array_merge([
|
||||
'status' => $status
|
||||
], $data);
|
||||
|
||||
// 如果是完成或失败状态,添加结束时间
|
||||
if ($status == 2 || $status == 3) {
|
||||
$updateData['end_time'] = time();
|
||||
}
|
||||
|
||||
return self::where('id', $id)->update($updateData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取计划的执行记录
|
||||
* @param int $planId 计划ID
|
||||
* @param int $step 步骤
|
||||
* @return array 执行记录
|
||||
*/
|
||||
public static function getPlanExecutions($planId, $step = null)
|
||||
{
|
||||
$where = [
|
||||
['plan_id', '=', $planId]
|
||||
];
|
||||
|
||||
if ($step !== null) {
|
||||
$where[] = ['step', '=', $step];
|
||||
}
|
||||
|
||||
return self::where($where)
|
||||
->order('createTime DESC')
|
||||
->select();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最近的执行记录
|
||||
* @param int $planId 计划ID
|
||||
* @param int $step 步骤
|
||||
* @return array|null 执行记录
|
||||
*/
|
||||
public static function getLatestExecution($planId, $step)
|
||||
{
|
||||
return self::where([
|
||||
['plan_id', '=', $planId],
|
||||
['step', '=', $step]
|
||||
])
|
||||
->order('createTime DESC')
|
||||
->find();
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联计划
|
||||
*/
|
||||
public function plan()
|
||||
{
|
||||
return $this->belongsTo('PlanTask', 'plan_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联流量
|
||||
*/
|
||||
public function traffic()
|
||||
{
|
||||
return $this->belongsTo('TrafficPool', 'traffic_id');
|
||||
}
|
||||
}
|
||||
@@ -10,28 +10,7 @@ class PlanScene extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $name = 'plan_scene';
|
||||
protected $prefix = 'tk_';
|
||||
|
||||
// 设置主键
|
||||
protected $pk = 'id';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createTime';
|
||||
protected $updateTime = 'updateTime';
|
||||
protected $deleteTime = 'deleteTime';
|
||||
|
||||
// 定义字段类型
|
||||
protected $type = [
|
||||
'id' => 'integer',
|
||||
'status' => 'integer',
|
||||
'createTime' => 'integer',
|
||||
'updateTime' => 'integer',
|
||||
'deleteTime' => 'integer'
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* 获取场景列表
|
||||
*
|
||||
@@ -61,15 +40,4 @@ class PlanScene extends Model
|
||||
'limit' => $limit
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个场景信息
|
||||
*
|
||||
* @param int $id 场景ID
|
||||
* @return array|null 场景信息
|
||||
*/
|
||||
public static function getSceneInfo($id)
|
||||
{
|
||||
return self::where('id', $id)->find();
|
||||
}
|
||||
}
|
||||
@@ -10,140 +10,4 @@ class PlanTask extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $name = 'plan_task';
|
||||
protected $prefix = 'tk_';
|
||||
|
||||
// 设置主键
|
||||
protected $pk = 'id';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createTime';
|
||||
protected $updateTime = 'updateTime';
|
||||
protected $deleteTime = 'deleteTime';
|
||||
|
||||
// 定义字段类型
|
||||
protected $type = [
|
||||
'id' => 'integer',
|
||||
'device_id' => 'integer',
|
||||
'scene_id' => 'integer',
|
||||
'scene_config' => 'json',
|
||||
'status' => 'integer',
|
||||
'current_step' => 'integer',
|
||||
'priority' => 'integer',
|
||||
'created_by' => 'integer',
|
||||
'createTime' => 'integer',
|
||||
'updateTime' => 'integer',
|
||||
'deleteTime' => 'integer'
|
||||
];
|
||||
|
||||
/**
|
||||
* 状态文本获取器
|
||||
* @param int $value 状态值
|
||||
* @return string 状态文本
|
||||
*/
|
||||
public function getStatusTextAttr($value, $data)
|
||||
{
|
||||
$status = [
|
||||
0 => '停用',
|
||||
1 => '启用',
|
||||
2 => '完成',
|
||||
3 => '失败'
|
||||
];
|
||||
return isset($status[$data['status']]) ? $status[$data['status']] : '未知';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取待执行的任务列表
|
||||
* @param int $limit 限制数量
|
||||
* @return array 任务列表
|
||||
*/
|
||||
public static function getPendingTasks($limit = 10)
|
||||
{
|
||||
return self::where('status', 1)
|
||||
->order('priority DESC, id ASC')
|
||||
->limit($limit)
|
||||
->select();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新任务状态
|
||||
* @param int $id 任务ID
|
||||
* @param int $status 新状态
|
||||
* @param int $currentStep 当前步骤
|
||||
* @return bool 更新结果
|
||||
*/
|
||||
public static function updateTaskStatus($id, $status, $currentStep = null)
|
||||
{
|
||||
$data = ['status' => $status];
|
||||
if ($currentStep !== null) {
|
||||
$data['current_step'] = $currentStep;
|
||||
}
|
||||
|
||||
return self::where('id', $id)->update($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务详情
|
||||
* @param int $id 任务ID
|
||||
* @return array|null 任务详情
|
||||
*/
|
||||
public static function getTaskDetail($id)
|
||||
{
|
||||
return self::where('id', $id)->find();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务列表
|
||||
* @param array $where 查询条件
|
||||
* @param string $order 排序
|
||||
* @param int $page 页码
|
||||
* @param int $limit 每页数量
|
||||
* @return array 任务列表和总数
|
||||
*/
|
||||
public static function getTaskList($where = [], $order = 'id desc', $page = 1, $limit = 10)
|
||||
{
|
||||
// 构建查询
|
||||
$query = self::where($where);
|
||||
|
||||
// 计算总数
|
||||
$total = $query->count();
|
||||
|
||||
// 分页查询数据
|
||||
$list = $query->page($page, $limit)
|
||||
->order($order)
|
||||
->select();
|
||||
|
||||
return [
|
||||
'list' => $list,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'limit' => $limit
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联场景
|
||||
*/
|
||||
public function scene()
|
||||
{
|
||||
return $this->belongsTo('PlanScene', 'scene_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联设备
|
||||
*/
|
||||
public function device()
|
||||
{
|
||||
return $this->belongsTo('app\devices\model\Device', 'device_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联执行记录
|
||||
*/
|
||||
public function executions()
|
||||
{
|
||||
return $this->hasMany('PlanExecution', 'plan_id');
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
<?php
|
||||
namespace app\plan\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 标签模型
|
||||
*/
|
||||
class Tag extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $name = 'tag';
|
||||
protected $prefix = 'tk_';
|
||||
|
||||
// 设置主键
|
||||
protected $pk = 'id';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createTime';
|
||||
protected $updateTime = 'updateTime';
|
||||
|
||||
// 定义字段类型
|
||||
protected $type = [
|
||||
'id' => 'integer',
|
||||
'count' => 'integer',
|
||||
'status' => 'integer',
|
||||
'createTime' => 'integer',
|
||||
'updateTime' => 'integer'
|
||||
];
|
||||
|
||||
/**
|
||||
* 获取或创建标签
|
||||
* @param string $name 标签名
|
||||
* @param string $type 标签类型
|
||||
* @param string $color 标签颜色
|
||||
* @return int 标签ID
|
||||
*/
|
||||
public static function getOrCreate($name, $type = 'traffic', $color = '')
|
||||
{
|
||||
$tag = self::where([
|
||||
['name', '=', $name],
|
||||
['type', '=', $type]
|
||||
])->find();
|
||||
|
||||
if ($tag) {
|
||||
return $tag['id'];
|
||||
} else {
|
||||
$model = new self();
|
||||
$model->save([
|
||||
'name' => $name,
|
||||
'type' => $type,
|
||||
'color' => $color ?: self::getRandomColor(),
|
||||
'count' => 0,
|
||||
'status' => 1
|
||||
]);
|
||||
return $model->id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新标签使用次数
|
||||
* @param int $id 标签ID
|
||||
* @param int $increment 增量
|
||||
* @return bool 更新结果
|
||||
*/
|
||||
public static function updateCount($id, $increment = 1)
|
||||
{
|
||||
return self::where('id', $id)->inc('count', $increment)->update();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取标签列表
|
||||
* @param string $type 标签类型
|
||||
* @param array $where 额外条件
|
||||
* @return array 标签列表
|
||||
*/
|
||||
public static function getTagsByType($type = 'traffic', $where = [])
|
||||
{
|
||||
$conditions = array_merge([
|
||||
['type', '=', $type],
|
||||
['status', '=', 1]
|
||||
], $where);
|
||||
|
||||
return self::where($conditions)
|
||||
->order('count DESC, id DESC')
|
||||
->select();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID获取标签名称
|
||||
* @param array $ids 标签ID数组
|
||||
* @return array 标签名称数组
|
||||
*/
|
||||
public static function getTagNames($ids)
|
||||
{
|
||||
if (empty($ids)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$tagIds = is_array($ids) ? $ids : explode(',', $ids);
|
||||
|
||||
$tags = self::where('id', 'in', $tagIds)->column('name');
|
||||
|
||||
return $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取随机颜色
|
||||
* @return string 颜色代码
|
||||
*/
|
||||
private static function getRandomColor()
|
||||
{
|
||||
$colors = [
|
||||
'#f44336', '#e91e63', '#9c27b0', '#673ab7', '#3f51b5',
|
||||
'#2196f3', '#03a9f4', '#00bcd4', '#009688', '#4caf50',
|
||||
'#8bc34a', '#cddc39', '#ffeb3b', '#ffc107', '#ff9800',
|
||||
'#ff5722', '#795548', '#9e9e9e', '#607d8b'
|
||||
];
|
||||
|
||||
return $colors[array_rand($colors)];
|
||||
}
|
||||
}
|
||||
@@ -10,129 +10,4 @@ class TrafficPool extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $name = 'traffic_pool';
|
||||
protected $prefix = 'tk_';
|
||||
|
||||
// 设置主键
|
||||
protected $pk = 'id';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createTime';
|
||||
protected $updateTime = 'updateTime';
|
||||
protected $deleteTime = 'deleteTime';
|
||||
|
||||
// 定义字段类型
|
||||
protected $type = [
|
||||
'id' => 'integer',
|
||||
'gender' => 'integer',
|
||||
'age' => 'integer',
|
||||
'status' => 'integer',
|
||||
'last_used_time' => 'integer',
|
||||
'createTime' => 'integer',
|
||||
'updateTime' => 'integer',
|
||||
'deleteTime' => 'integer'
|
||||
];
|
||||
|
||||
/**
|
||||
* 添加或更新流量信息
|
||||
* @param string $mobile 手机号
|
||||
* @param array $data 流量数据
|
||||
* @return int|bool 流量ID或更新结果
|
||||
*/
|
||||
public static function addOrUpdateTraffic($mobile, $data = [])
|
||||
{
|
||||
// 查询是否已存在该手机号
|
||||
$exists = self::where('mobile', $mobile)->find();
|
||||
|
||||
// 设置通用数据
|
||||
$saveData = array_merge([
|
||||
'mobile' => $mobile,
|
||||
'status' => 1,
|
||||
'last_used_time' => time()
|
||||
], $data);
|
||||
|
||||
if ($exists) {
|
||||
// 更新已存在的流量记录
|
||||
return self::where('id', $exists['id'])->update($saveData);
|
||||
} else {
|
||||
// 创建新的流量记录
|
||||
$model = new self();
|
||||
$model->save($saveData);
|
||||
return $model->id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用的流量列表
|
||||
* @param array $where 查询条件
|
||||
* @param string $order 排序
|
||||
* @param int $page 页码
|
||||
* @param int $limit 每页数量
|
||||
* @return array 流量列表和总数
|
||||
*/
|
||||
public static function getAvailableTraffic($where = [], $order = 'last_used_time ASC', $page = 1, $limit = 10)
|
||||
{
|
||||
// 确保只查询有效流量
|
||||
$where[] = ['status', '=', 1];
|
||||
|
||||
// 构建查询
|
||||
$query = self::where($where);
|
||||
|
||||
// 计算总数
|
||||
$total = $query->count();
|
||||
|
||||
// 分页查询数据
|
||||
$list = $query->page($page, $limit)
|
||||
->order($order)
|
||||
->select();
|
||||
|
||||
return [
|
||||
'list' => $list,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'limit' => $limit
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置流量使用时间
|
||||
* @param int $id 流量ID
|
||||
* @return bool 更新结果
|
||||
*/
|
||||
public static function setTrafficUsed($id)
|
||||
{
|
||||
return self::where('id', $id)->update([
|
||||
'last_used_time' => time()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取流量详情
|
||||
* @param int $id 流量ID
|
||||
* @return array|null 流量详情
|
||||
*/
|
||||
public static function getTrafficDetail($id)
|
||||
{
|
||||
return self::where('id', $id)->find();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据手机号获取流量详情
|
||||
* @param string $mobile 手机号
|
||||
* @return array|null 流量详情
|
||||
*/
|
||||
public static function getTrafficByMobile($mobile)
|
||||
{
|
||||
return self::where('mobile', $mobile)->find();
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联流量来源
|
||||
*/
|
||||
public function sources()
|
||||
{
|
||||
return $this->hasMany('TrafficSource', 'traffic_id');
|
||||
}
|
||||
}
|
||||
@@ -10,142 +10,4 @@ class TrafficSource extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $name = 'traffic_source';
|
||||
protected $prefix = 'tk_';
|
||||
|
||||
// 设置主键
|
||||
protected $pk = 'id';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createTime';
|
||||
protected $updateTime = false; // 没有更新时间字段
|
||||
|
||||
// 定义字段类型
|
||||
protected $type = [
|
||||
'id' => 'integer',
|
||||
'traffic_id' => 'integer',
|
||||
'plan_id' => 'integer',
|
||||
'scene_id' => 'integer',
|
||||
'source_detail' => 'json',
|
||||
'createTime' => 'integer'
|
||||
];
|
||||
|
||||
/**
|
||||
* 渠道文本获取器
|
||||
* @param string $value 渠道值
|
||||
* @return string 渠道文本
|
||||
*/
|
||||
public function getChannelTextAttr($value, $data)
|
||||
{
|
||||
$channels = [
|
||||
'poster' => '海报',
|
||||
'order' => '订单',
|
||||
'douyin' => '抖音',
|
||||
'xiaohongshu' => '小红书',
|
||||
'phone' => '电话',
|
||||
'wechat' => '公众号',
|
||||
'group' => '微信群',
|
||||
'payment' => '付款码',
|
||||
'api' => 'API接口'
|
||||
];
|
||||
|
||||
return isset($channels[$data['channel']]) ? $channels[$data['channel']] : '未知';
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加流量来源记录
|
||||
* @param int $trafficId 流量ID
|
||||
* @param string $channel 渠道
|
||||
* @param array $data 额外数据
|
||||
* @return int 新增记录ID
|
||||
*/
|
||||
public static function addSource($trafficId, $channel, $data = [])
|
||||
{
|
||||
$model = new self();
|
||||
$model->save(array_merge([
|
||||
'traffic_id' => $trafficId,
|
||||
'channel' => $channel,
|
||||
'ip' => request()->ip(),
|
||||
'user_agent' => request()->header('user-agent')
|
||||
], $data));
|
||||
|
||||
return $model->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取流量来源列表
|
||||
* @param int $trafficId 流量ID
|
||||
* @return array 来源列表
|
||||
*/
|
||||
public static function getSourcesByTrafficId($trafficId)
|
||||
{
|
||||
return self::where('traffic_id', $trafficId)
|
||||
->order('createTime DESC')
|
||||
->select();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取来源统计
|
||||
* @param string $channel 渠道
|
||||
* @param int $planId 计划ID
|
||||
* @param int $sceneId 场景ID
|
||||
* @param string $startTime 开始时间
|
||||
* @param string $endTime 结束时间
|
||||
* @return array 统计数据
|
||||
*/
|
||||
public static function getSourceStats($channel = null, $planId = null, $sceneId = null, $startTime = null, $endTime = null)
|
||||
{
|
||||
$where = [];
|
||||
|
||||
if ($channel !== null) {
|
||||
$where[] = ['channel', '=', $channel];
|
||||
}
|
||||
|
||||
if ($planId !== null) {
|
||||
$where[] = ['plan_id', '=', $planId];
|
||||
}
|
||||
|
||||
if ($sceneId !== null) {
|
||||
$where[] = ['scene_id', '=', $sceneId];
|
||||
}
|
||||
|
||||
if ($startTime !== null) {
|
||||
$where[] = ['createTime', '>=', strtotime($startTime)];
|
||||
}
|
||||
|
||||
if ($endTime !== null) {
|
||||
$where[] = ['createTime', '<=', strtotime($endTime)];
|
||||
}
|
||||
|
||||
return self::where($where)
|
||||
->field('channel, COUNT(*) as count')
|
||||
->group('channel')
|
||||
->select();
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联流量
|
||||
*/
|
||||
public function traffic()
|
||||
{
|
||||
return $this->belongsTo('TrafficPool', 'traffic_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联计划
|
||||
*/
|
||||
public function plan()
|
||||
{
|
||||
return $this->belongsTo('PlanTask', 'plan_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联场景
|
||||
*/
|
||||
public function scene()
|
||||
{
|
||||
return $this->belongsTo('PlanScene', 'scene_id');
|
||||
}
|
||||
}
|
||||
32
Server/application/plan/model/TrafficTag.php
Normal file
32
Server/application/plan/model/TrafficTag.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
namespace app\plan\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 流量标签模型
|
||||
*/
|
||||
class TrafficTag extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $name = 'traffic_tag';
|
||||
|
||||
/**
|
||||
* 获取标签列表,支持分页和搜索
|
||||
*
|
||||
* @param array $where 查询条件
|
||||
* @param string $order 排序方式
|
||||
* @param int $page 页码
|
||||
* @param int $limit 每页数量
|
||||
* @return \think\Paginator 分页对象
|
||||
*/
|
||||
public static function getTagsByCompany($where = [], $order = 'id desc', $page = 1, $limit = 200)
|
||||
{
|
||||
return self::where($where)
|
||||
->where('deleteTime', 0) // 只查询未删除的记录
|
||||
->order($order)
|
||||
->paginate($limit, false, [
|
||||
'page' => $page
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | 获客计划模块路由
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
use think\facade\Route;
|
||||
|
||||
// API路由组
|
||||
Route::group('api/plan', function () {
|
||||
// 任务相关路由
|
||||
Route::resource('tasks', 'plan/Task');
|
||||
Route::get('tasks/:id/execute', 'plan/Task/execute');
|
||||
Route::get('tasks/:id/start', 'plan/Task/start');
|
||||
Route::get('tasks/:id/stop', 'plan/Task/stop');
|
||||
Route::get('cron', 'plan/Task/cron');
|
||||
|
||||
// 流量相关路由
|
||||
Route::resource('traffic', 'plan/Traffic');
|
||||
Route::get('traffic/stats', 'plan/Traffic/sourceStats');
|
||||
Route::post('traffic/import', 'plan/Traffic/importTraffic');
|
||||
Route::post('traffic/external', 'plan/Traffic/handleExternalTraffic');
|
||||
|
||||
// 标签相关路由
|
||||
Route::resource('tags', 'plan/Tag');
|
||||
Route::post('tags/batch', 'plan/Tag/batchCreate');
|
||||
Route::get('tags/names', 'plan/Tag/getNames');
|
||||
});
|
||||
|
||||
// 返回空数组,避免路由注册冲突
|
||||
return [];
|
||||
@@ -1,239 +0,0 @@
|
||||
<?php
|
||||
namespace app\plan\service;
|
||||
|
||||
use app\plan\model\TrafficPool;
|
||||
use app\plan\model\TrafficSource;
|
||||
use app\plan\model\PlanScene;
|
||||
use think\Exception;
|
||||
use think\facade\Log;
|
||||
|
||||
/**
|
||||
* 场景处理服务
|
||||
*/
|
||||
class SceneHandler
|
||||
{
|
||||
/**
|
||||
* 获取场景处理器
|
||||
* @param int $sceneId 场景ID
|
||||
* @return object 场景处理器
|
||||
*/
|
||||
public static function getHandler($sceneId)
|
||||
{
|
||||
$scene = PlanScene::getSceneInfo($sceneId);
|
||||
if (empty($scene)) {
|
||||
throw new Exception('场景不存在');
|
||||
}
|
||||
|
||||
$handlerMap = [
|
||||
// 场景ID => 处理器名称
|
||||
1 => 'PosterScene',
|
||||
2 => 'OrderScene',
|
||||
3 => 'DouyinScene',
|
||||
4 => 'XiaohongshuScene',
|
||||
5 => 'PhoneScene',
|
||||
6 => 'WechatScene',
|
||||
7 => 'GroupScene',
|
||||
8 => 'PaymentScene',
|
||||
9 => 'ApiScene',
|
||||
];
|
||||
|
||||
if (!isset($handlerMap[$sceneId])) {
|
||||
throw new Exception('未找到场景处理器');
|
||||
}
|
||||
|
||||
$handlerClass = '\\app\\plan\\scene\\' . $handlerMap[$sceneId];
|
||||
if (!class_exists($handlerClass)) {
|
||||
throw new Exception('场景处理器不存在');
|
||||
}
|
||||
|
||||
return new $handlerClass($scene);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理海报扫码获客
|
||||
* @param string $mobile 手机号
|
||||
* @param int $sceneId 场景ID
|
||||
* @param int $planId 计划ID
|
||||
* @param array $extra 额外数据
|
||||
* @return array 处理结果
|
||||
*/
|
||||
public static function handlePosterScan($mobile, $sceneId, $planId = null, $extra = [])
|
||||
{
|
||||
if (empty($mobile)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => '手机号不能为空'
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
// 添加或更新流量信息
|
||||
$trafficId = TrafficPool::addOrUpdateTraffic($mobile, [
|
||||
'name' => $extra['name'] ?? '',
|
||||
'gender' => $extra['gender'] ?? 0,
|
||||
'region' => $extra['region'] ?? ''
|
||||
]);
|
||||
|
||||
// 添加流量来源记录
|
||||
TrafficSource::addSource($trafficId, 'poster', [
|
||||
'plan_id' => $planId,
|
||||
'scene_id' => $sceneId,
|
||||
'source_detail' => json_encode($extra)
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => '海报扫码获客处理成功',
|
||||
'data' => [
|
||||
'traffic_id' => $trafficId
|
||||
]
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
Log::error('海报扫码获客处理失败', [
|
||||
'mobile' => $mobile,
|
||||
'scene_id' => $sceneId,
|
||||
'plan_id' => $planId,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => '处理失败:' . $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理订单导入获客
|
||||
* @param array $orders 订单数据
|
||||
* @param int $sceneId 场景ID
|
||||
* @param int $planId 计划ID
|
||||
* @return array 处理结果
|
||||
*/
|
||||
public static function handleOrderImport($orders, $sceneId, $planId = null)
|
||||
{
|
||||
if (empty($orders) || !is_array($orders)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => '订单数据格式不正确'
|
||||
];
|
||||
}
|
||||
|
||||
$success = 0;
|
||||
$failed = 0;
|
||||
$errors = [];
|
||||
|
||||
foreach ($orders as $order) {
|
||||
if (empty($order['mobile'])) {
|
||||
$failed++;
|
||||
$errors[] = '订单缺少手机号';
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// 添加或更新流量信息
|
||||
$trafficId = TrafficPool::addOrUpdateTraffic($order['mobile'], [
|
||||
'name' => $order['name'] ?? '',
|
||||
'gender' => $order['gender'] ?? 0,
|
||||
'region' => $order['region'] ?? ''
|
||||
]);
|
||||
|
||||
// 添加流量来源记录
|
||||
TrafficSource::addSource($trafficId, 'order', [
|
||||
'plan_id' => $planId,
|
||||
'scene_id' => $sceneId,
|
||||
'source_detail' => json_encode($order),
|
||||
'sub_channel' => $order['order_source'] ?? ''
|
||||
]);
|
||||
|
||||
$success++;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$failed++;
|
||||
$errors[] = '处理订单失败:' . $e->getMessage();
|
||||
|
||||
Log::error('订单导入获客处理失败', [
|
||||
'order' => $order,
|
||||
'scene_id' => $sceneId,
|
||||
'plan_id' => $planId,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => $success > 0,
|
||||
'message' => "导入完成,成功{$success}条,失败{$failed}条",
|
||||
'data' => [
|
||||
'success_count' => $success,
|
||||
'failed_count' => $failed,
|
||||
'errors' => $errors
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用渠道获客处理
|
||||
* @param string $mobile 手机号
|
||||
* @param string $channel 渠道
|
||||
* @param int $sceneId 场景ID
|
||||
* @param int $planId 计划ID
|
||||
* @param array $extra 额外数据
|
||||
* @return array 处理结果
|
||||
*/
|
||||
public static function handleChannelTraffic($mobile, $channel, $sceneId, $planId = null, $extra = [])
|
||||
{
|
||||
if (empty($mobile)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => '手机号不能为空'
|
||||
];
|
||||
}
|
||||
|
||||
if (empty($channel)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => '渠道不能为空'
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
// 添加或更新流量信息
|
||||
$trafficId = TrafficPool::addOrUpdateTraffic($mobile, [
|
||||
'name' => $extra['name'] ?? '',
|
||||
'gender' => $extra['gender'] ?? 0,
|
||||
'region' => $extra['region'] ?? ''
|
||||
]);
|
||||
|
||||
// 添加流量来源记录
|
||||
TrafficSource::addSource($trafficId, $channel, [
|
||||
'plan_id' => $planId,
|
||||
'scene_id' => $sceneId,
|
||||
'source_detail' => json_encode($extra),
|
||||
'sub_channel' => $extra['sub_channel'] ?? ''
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => $channel . '获客处理成功',
|
||||
'data' => [
|
||||
'traffic_id' => $trafficId
|
||||
]
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
Log::error($channel . '获客处理失败', [
|
||||
'mobile' => $mobile,
|
||||
'scene_id' => $sceneId,
|
||||
'plan_id' => $planId,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => '处理失败:' . $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,675 +0,0 @@
|
||||
<?php
|
||||
namespace app\plan\service;
|
||||
|
||||
use app\plan\model\PlanTask;
|
||||
use app\plan\model\PlanExecution;
|
||||
use app\plan\model\TrafficPool;
|
||||
use app\plan\model\TrafficSource;
|
||||
use app\plan\model\Tag;
|
||||
use think\Db;
|
||||
use think\facade\Log;
|
||||
use think\Exception;
|
||||
|
||||
/**
|
||||
* 任务运行器服务
|
||||
*/
|
||||
class TaskRunner
|
||||
{
|
||||
protected $task;
|
||||
protected $stepHandlers = [];
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param PlanTask|int $task 任务对象或ID
|
||||
*/
|
||||
public function __construct($task)
|
||||
{
|
||||
if (is_numeric($task)) {
|
||||
$this->task = PlanTask::getTaskDetail($task);
|
||||
} else {
|
||||
$this->task = $task;
|
||||
}
|
||||
|
||||
if (empty($this->task)) {
|
||||
throw new Exception('任务不存在');
|
||||
}
|
||||
|
||||
// 注册步骤处理器
|
||||
$this->registerStepHandlers();
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册步骤处理器
|
||||
*/
|
||||
protected function registerStepHandlers()
|
||||
{
|
||||
// 基础配置
|
||||
$this->stepHandlers[1] = function() {
|
||||
return $this->handleBasicConfig();
|
||||
};
|
||||
|
||||
// 加友计划
|
||||
$this->stepHandlers[2] = function() {
|
||||
return $this->handleAddFriend();
|
||||
};
|
||||
|
||||
// API调用
|
||||
$this->stepHandlers[3] = function() {
|
||||
return $this->handleApiCall();
|
||||
};
|
||||
|
||||
// 标签处理
|
||||
$this->stepHandlers[4] = function() {
|
||||
return $this->handleTagging();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行任务
|
||||
* @return array 执行结果
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
if ($this->task['status'] != 1) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => '任务未启用,无法运行'
|
||||
];
|
||||
}
|
||||
|
||||
// 获取当前步骤
|
||||
$currentStep = $this->task['current_step'];
|
||||
|
||||
// 检查是否需要初始化第一步
|
||||
if ($currentStep == 0) {
|
||||
$currentStep = 1;
|
||||
PlanTask::updateTaskStatus($this->task['id'], 1, $currentStep);
|
||||
$this->task['current_step'] = $currentStep;
|
||||
}
|
||||
|
||||
// 执行当前步骤
|
||||
if (isset($this->stepHandlers[$currentStep])) {
|
||||
try {
|
||||
$result = call_user_func($this->stepHandlers[$currentStep]);
|
||||
|
||||
if ($result['success']) {
|
||||
// 检查是否需要进入下一步
|
||||
if ($result['completed'] && $currentStep < 4) {
|
||||
$nextStep = $currentStep + 1;
|
||||
PlanTask::updateTaskStatus($this->task['id'], 1, $nextStep);
|
||||
} else if ($result['completed'] && $currentStep == 4) {
|
||||
// 所有步骤已完成,标记任务为完成状态
|
||||
PlanTask::updateTaskStatus($this->task['id'], 2, $currentStep);
|
||||
}
|
||||
} else {
|
||||
// 如果步骤执行失败,记录错误并可能更新任务状态
|
||||
Log::error('任务执行失败:', [
|
||||
'task_id' => $this->task['id'],
|
||||
'step' => $currentStep,
|
||||
'error' => $result['message']
|
||||
]);
|
||||
|
||||
// 视情况决定是否将任务标记为失败
|
||||
if ($result['fatal']) {
|
||||
PlanTask::updateTaskStatus($this->task['id'], 3, $currentStep);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
} catch (Exception $e) {
|
||||
// 捕获并记录异常
|
||||
Log::error('任务执行异常:', [
|
||||
'task_id' => $this->task['id'],
|
||||
'step' => $currentStep,
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => '任务执行异常:' . $e->getMessage(),
|
||||
'fatal' => true
|
||||
];
|
||||
}
|
||||
} else {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => '未知的任务步骤:' . $currentStep,
|
||||
'fatal' => true
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理基础配置步骤
|
||||
* @return array 处理结果
|
||||
*/
|
||||
protected function handleBasicConfig()
|
||||
{
|
||||
// 创建执行记录
|
||||
$executionId = PlanExecution::createExecution($this->task['id'], 1, [
|
||||
'status' => 1 // 设置为进行中
|
||||
]);
|
||||
|
||||
try {
|
||||
// 检查设备状态
|
||||
if (empty($this->task['device_id'])) {
|
||||
PlanExecution::updateExecution($executionId, 3, [
|
||||
'error' => '未设置设备'
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => '未设置设备',
|
||||
'fatal' => true
|
||||
];
|
||||
}
|
||||
|
||||
// 检查场景配置
|
||||
if (empty($this->task['scene_id'])) {
|
||||
PlanExecution::updateExecution($executionId, 3, [
|
||||
'error' => '未设置获客场景'
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => '未设置获客场景',
|
||||
'fatal' => true
|
||||
];
|
||||
}
|
||||
|
||||
// 检查场景配置
|
||||
if (empty($this->task['scene_config'])) {
|
||||
PlanExecution::updateExecution($executionId, 3, [
|
||||
'error' => '场景配置为空'
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => '场景配置为空',
|
||||
'fatal' => true
|
||||
];
|
||||
}
|
||||
|
||||
// 标记基础配置步骤为完成
|
||||
PlanExecution::updateExecution($executionId, 2, [
|
||||
'result' => [
|
||||
'device_id' => $this->task['device_id'],
|
||||
'scene_id' => $this->task['scene_id'],
|
||||
'config_valid' => true
|
||||
]
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => '基础配置验证通过',
|
||||
'completed' => true
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
PlanExecution::updateExecution($executionId, 3, [
|
||||
'error' => '基础配置异常:' . $e->getMessage()
|
||||
]);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理加友计划步骤
|
||||
* @return array 处理结果
|
||||
*/
|
||||
protected function handleAddFriend()
|
||||
{
|
||||
// 创建执行记录
|
||||
$executionId = PlanExecution::createExecution($this->task['id'], 2, [
|
||||
'status' => 1 // 设置为进行中
|
||||
]);
|
||||
|
||||
try {
|
||||
// 从流量池中选择符合条件的流量
|
||||
$trafficConditions = $this->getTrafficConditions();
|
||||
$trafficData = TrafficPool::getAvailableTraffic($trafficConditions, 'last_used_time ASC', 1, 1);
|
||||
|
||||
if (empty($trafficData['list'])) {
|
||||
// 没有符合条件的流量,标记为等待状态
|
||||
PlanExecution::updateExecution($executionId, 0, [
|
||||
'error' => '没有符合条件的流量'
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => '没有符合条件的流量,等待下次执行',
|
||||
'completed' => false // 不算失败,但也不进入下一步
|
||||
];
|
||||
}
|
||||
|
||||
$traffic = $trafficData['list'][0];
|
||||
|
||||
// 调用设备服务执行加好友操作
|
||||
$addFriendResult = $this->callDeviceAddFriend($traffic);
|
||||
|
||||
if ($addFriendResult['success']) {
|
||||
// 更新流量使用状态
|
||||
TrafficPool::setTrafficUsed($traffic['id']);
|
||||
|
||||
// 标记执行记录为成功
|
||||
PlanExecution::updateExecution($executionId, 2, [
|
||||
'traffic_id' => $traffic['id'],
|
||||
'result' => $addFriendResult['data']
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => '加友成功:' . $traffic['mobile'],
|
||||
'completed' => true,
|
||||
'traffic' => $traffic
|
||||
];
|
||||
} else {
|
||||
// 标记执行记录为失败
|
||||
PlanExecution::updateExecution($executionId, 3, [
|
||||
'traffic_id' => $traffic['id'],
|
||||
'error' => $addFriendResult['message'],
|
||||
'result' => $addFriendResult['data'] ?? null
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => '加友失败:' . $addFriendResult['message'],
|
||||
'fatal' => false // 加友失败不算致命错误,可以下次继续
|
||||
];
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
PlanExecution::updateExecution($executionId, 3, [
|
||||
'error' => '加友计划异常:' . $e->getMessage()
|
||||
]);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理API调用步骤
|
||||
* @return array 处理结果
|
||||
*/
|
||||
protected function handleApiCall()
|
||||
{
|
||||
// 创建执行记录
|
||||
$executionId = PlanExecution::createExecution($this->task['id'], 3, [
|
||||
'status' => 1 // 设置为进行中
|
||||
]);
|
||||
|
||||
try {
|
||||
// 获取上一步成功处理的流量信息
|
||||
$lastExecution = PlanExecution::getLatestExecution($this->task['id'], 2);
|
||||
|
||||
if (empty($lastExecution) || $lastExecution['status'] != 2) {
|
||||
PlanExecution::updateExecution($executionId, 3, [
|
||||
'error' => '上一步未成功完成'
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => '上一步未成功完成,无法进行API调用',
|
||||
'fatal' => true
|
||||
];
|
||||
}
|
||||
|
||||
$trafficId = $lastExecution['traffic_id'];
|
||||
if (empty($trafficId)) {
|
||||
PlanExecution::updateExecution($executionId, 3, [
|
||||
'error' => '未找到有效的流量ID'
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => '未找到有效的流量ID',
|
||||
'fatal' => true
|
||||
];
|
||||
}
|
||||
|
||||
// 获取流量详情
|
||||
$traffic = TrafficPool::getTrafficDetail($trafficId);
|
||||
if (empty($traffic)) {
|
||||
PlanExecution::updateExecution($executionId, 3, [
|
||||
'error' => '未找到流量信息'
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => '未找到流量信息',
|
||||
'fatal' => true
|
||||
];
|
||||
}
|
||||
|
||||
// 根据场景配置调用相应的API
|
||||
$apiCallResult = $this->callSceneApi($traffic);
|
||||
|
||||
if ($apiCallResult['success']) {
|
||||
// 标记执行记录为成功
|
||||
PlanExecution::updateExecution($executionId, 2, [
|
||||
'traffic_id' => $trafficId,
|
||||
'result' => $apiCallResult['data']
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => 'API调用成功',
|
||||
'completed' => true,
|
||||
'traffic' => $traffic
|
||||
];
|
||||
} else {
|
||||
// 标记执行记录为失败
|
||||
PlanExecution::updateExecution($executionId, 3, [
|
||||
'traffic_id' => $trafficId,
|
||||
'error' => $apiCallResult['message'],
|
||||
'result' => $apiCallResult['data'] ?? null
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'API调用失败:' . $apiCallResult['message'],
|
||||
'fatal' => $apiCallResult['fatal'] ?? false
|
||||
];
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
PlanExecution::updateExecution($executionId, 3, [
|
||||
'error' => 'API调用异常:' . $e->getMessage()
|
||||
]);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理标签步骤
|
||||
* @return array 处理结果
|
||||
*/
|
||||
protected function handleTagging()
|
||||
{
|
||||
// 创建执行记录
|
||||
$executionId = PlanExecution::createExecution($this->task['id'], 4, [
|
||||
'status' => 1 // 设置为进行中
|
||||
]);
|
||||
|
||||
try {
|
||||
// 获取上一步成功处理的流量信息
|
||||
$lastExecution = PlanExecution::getLatestExecution($this->task['id'], 3);
|
||||
|
||||
if (empty($lastExecution) || $lastExecution['status'] != 2) {
|
||||
PlanExecution::updateExecution($executionId, 3, [
|
||||
'error' => '上一步未成功完成'
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => '上一步未成功完成,无法进行标签处理',
|
||||
'fatal' => true
|
||||
];
|
||||
}
|
||||
|
||||
$trafficId = $lastExecution['traffic_id'];
|
||||
if (empty($trafficId)) {
|
||||
PlanExecution::updateExecution($executionId, 3, [
|
||||
'error' => '未找到有效的流量ID'
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => '未找到有效的流量ID',
|
||||
'fatal' => true
|
||||
];
|
||||
}
|
||||
|
||||
// 获取流量详情
|
||||
$traffic = TrafficPool::getTrafficDetail($trafficId);
|
||||
if (empty($traffic)) {
|
||||
PlanExecution::updateExecution($executionId, 3, [
|
||||
'error' => '未找到流量信息'
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => '未找到流量信息',
|
||||
'fatal' => true
|
||||
];
|
||||
}
|
||||
|
||||
// 获取并应用标签
|
||||
$taggingResult = $this->applyTags($traffic);
|
||||
|
||||
if ($taggingResult['success']) {
|
||||
// 标记执行记录为成功
|
||||
PlanExecution::updateExecution($executionId, 2, [
|
||||
'traffic_id' => $trafficId,
|
||||
'result' => $taggingResult['data']
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => '标签处理成功',
|
||||
'completed' => true,
|
||||
'traffic' => $traffic
|
||||
];
|
||||
} else {
|
||||
// 标记执行记录为失败
|
||||
PlanExecution::updateExecution($executionId, 3, [
|
||||
'traffic_id' => $trafficId,
|
||||
'error' => $taggingResult['message'],
|
||||
'result' => $taggingResult['data'] ?? null
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => '标签处理失败:' . $taggingResult['message'],
|
||||
'fatal' => $taggingResult['fatal'] ?? false
|
||||
];
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
PlanExecution::updateExecution($executionId, 3, [
|
||||
'error' => '标签处理异常:' . $e->getMessage()
|
||||
]);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取流量筛选条件
|
||||
* @return array 条件数组
|
||||
*/
|
||||
protected function getTrafficConditions()
|
||||
{
|
||||
$conditions = [];
|
||||
|
||||
// 根据场景配置获取筛选条件
|
||||
if (isset($this->task['scene_config']) && is_array($this->task['scene_config'])) {
|
||||
$config = $this->task['scene_config'];
|
||||
|
||||
// 添加性别筛选
|
||||
if (isset($config['gender']) && in_array($config['gender'], [0, 1, 2])) {
|
||||
$conditions[] = ['gender', '=', $config['gender']];
|
||||
}
|
||||
|
||||
// 添加年龄筛选
|
||||
if (isset($config['age_min']) && is_numeric($config['age_min'])) {
|
||||
$conditions[] = ['age', '>=', intval($config['age_min'])];
|
||||
}
|
||||
|
||||
if (isset($config['age_max']) && is_numeric($config['age_max'])) {
|
||||
$conditions[] = ['age', '<=', intval($config['age_max'])];
|
||||
}
|
||||
|
||||
// 添加区域筛选
|
||||
if (isset($config['region']) && !empty($config['region'])) {
|
||||
$conditions[] = ['region', 'like', '%' . $config['region'] . '%'];
|
||||
}
|
||||
}
|
||||
|
||||
return $conditions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用设备加好友操作
|
||||
* @param array $traffic 流量信息
|
||||
* @return array 调用结果
|
||||
*/
|
||||
protected function callDeviceAddFriend($traffic)
|
||||
{
|
||||
// 模拟调用设备操作
|
||||
// 实际项目中应该调用实际的设备API
|
||||
|
||||
// 记录设备调用日志
|
||||
Log::info('设备加好友操作', [
|
||||
'task_id' => $this->task['id'],
|
||||
'device_id' => $this->task['device_id'],
|
||||
'mobile' => $traffic['mobile']
|
||||
]);
|
||||
|
||||
// 模拟成功率
|
||||
$success = mt_rand(0, 10) > 2;
|
||||
|
||||
if ($success) {
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => '加好友操作成功',
|
||||
'data' => [
|
||||
'add_time' => date('Y-m-d H:i:s'),
|
||||
'device_id' => $this->task['device_id'],
|
||||
'mobile' => $traffic['mobile']
|
||||
]
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => '加好友操作失败:' . ['设备繁忙', '用户拒绝', '网络异常'][mt_rand(0, 2)],
|
||||
'data' => [
|
||||
'attempt_time' => date('Y-m-d H:i:s'),
|
||||
'device_id' => $this->task['device_id'],
|
||||
'mobile' => $traffic['mobile']
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据场景调用相应API
|
||||
* @param array $traffic 流量信息
|
||||
* @return array 调用结果
|
||||
*/
|
||||
protected function callSceneApi($traffic)
|
||||
{
|
||||
// 根据场景类型调用不同API
|
||||
if (empty($this->task['scene_id'])) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => '场景未设置',
|
||||
'fatal' => true
|
||||
];
|
||||
}
|
||||
|
||||
// 记录API调用日志
|
||||
Log::info('场景API调用', [
|
||||
'task_id' => $this->task['id'],
|
||||
'scene_id' => $this->task['scene_id'],
|
||||
'traffic_id' => $traffic['id']
|
||||
]);
|
||||
|
||||
// 模拟成功率
|
||||
$success = mt_rand(0, 10) > 1;
|
||||
|
||||
if ($success) {
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => 'API调用成功',
|
||||
'data' => [
|
||||
'call_time' => date('Y-m-d H:i:s'),
|
||||
'scene_id' => $this->task['scene_id'],
|
||||
'traffic_id' => $traffic['id']
|
||||
]
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'API调用失败:' . ['参数错误', 'API超时', '系统异常'][mt_rand(0, 2)],
|
||||
'data' => [
|
||||
'attempt_time' => date('Y-m-d H:i:s'),
|
||||
'scene_id' => $this->task['scene_id'],
|
||||
'traffic_id' => $traffic['id']
|
||||
],
|
||||
'fatal' => false // API调用失败通常不算致命错误
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用标签
|
||||
* @param array $traffic 流量信息
|
||||
* @return array 处理结果
|
||||
*/
|
||||
protected function applyTags($traffic)
|
||||
{
|
||||
// 获取需要应用的标签
|
||||
$tags = [];
|
||||
|
||||
// 从场景配置中获取标签
|
||||
if (isset($this->task['scene_config']) && is_array($this->task['scene_config']) && isset($this->task['scene_config']['tags'])) {
|
||||
$configTags = $this->task['scene_config']['tags'];
|
||||
if (is_array($configTags)) {
|
||||
$tags = array_merge($tags, $configTags);
|
||||
} else if (is_string($configTags)) {
|
||||
$tags[] = $configTags;
|
||||
}
|
||||
}
|
||||
|
||||
// 从场景获取标签
|
||||
if (!empty($this->task['scene_id'])) {
|
||||
$tags[] = '场景_' . $this->task['scene_id'];
|
||||
}
|
||||
|
||||
// 如果没有标签,返回成功
|
||||
if (empty($tags)) {
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => '没有需要应用的标签',
|
||||
'data' => []
|
||||
];
|
||||
}
|
||||
|
||||
// 处理标签
|
||||
$tagIds = [];
|
||||
foreach ($tags as $tagName) {
|
||||
$tagId = Tag::getOrCreate($tagName, 'friend');
|
||||
$tagIds[] = $tagId;
|
||||
Tag::updateCount($tagId);
|
||||
}
|
||||
|
||||
// 记录标签应用日志
|
||||
Log::info('应用标签', [
|
||||
'task_id' => $this->task['id'],
|
||||
'traffic_id' => $traffic['id'],
|
||||
'tag_ids' => $tagIds
|
||||
]);
|
||||
|
||||
// 更新流量标签
|
||||
$existingTags = empty($traffic['tag_ids']) ? [] : explode(',', $traffic['tag_ids']);
|
||||
$allTags = array_unique(array_merge($existingTags, $tagIds));
|
||||
|
||||
TrafficPool::where('id', $traffic['id'])->update([
|
||||
'tag_ids' => implode(',', $allTags)
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => '标签应用成功',
|
||||
'data' => [
|
||||
'tag_ids' => $tagIds,
|
||||
'tag_names' => $tags
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
-- 获客计划主表
|
||||
CREATE TABLE `tk_plan_task` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
|
||||
`name` varchar(100) NOT NULL COMMENT '计划名称',
|
||||
`device_id` int(10) unsigned DEFAULT NULL COMMENT '关联设备ID',
|
||||
`scene_id` int(10) unsigned DEFAULT NULL COMMENT '获客场景ID',
|
||||
`scene_config` text DEFAULT NULL COMMENT '场景配置(JSON格式)',
|
||||
`status` tinyint(3) unsigned DEFAULT 0 COMMENT '状态:0=停用,1=启用,2=完成,3=失败',
|
||||
`current_step` tinyint(3) unsigned DEFAULT 0 COMMENT '当前执行步骤',
|
||||
`priority` tinyint(3) unsigned DEFAULT 5 COMMENT '优先级:1-10,数字越大优先级越高',
|
||||
`created_by` int(10) unsigned NOT NULL COMMENT '创建人ID',
|
||||
`createTime` int(11) DEFAULT NULL COMMENT '创建时间',
|
||||
`updateTime` int(11) DEFAULT NULL COMMENT '更新时间',
|
||||
`deleteTime` int(11) DEFAULT NULL COMMENT '删除时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_status` (`status`),
|
||||
KEY `idx_device` (`device_id`),
|
||||
KEY `idx_scene` (`scene_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='获客计划主表';
|
||||
|
||||
-- 流量池表
|
||||
CREATE TABLE `tk_traffic_pool` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
|
||||
`mobile` varchar(20) NOT NULL COMMENT '手机号',
|
||||
`name` varchar(50) DEFAULT NULL COMMENT '姓名',
|
||||
`gender` tinyint(1) DEFAULT NULL COMMENT '性别:0=未知,1=男,2=女',
|
||||
`age` int(3) DEFAULT NULL COMMENT '年龄',
|
||||
`region` varchar(100) DEFAULT NULL COMMENT '区域',
|
||||
`status` tinyint(3) unsigned DEFAULT 1 COMMENT '状态:0=无效,1=有效',
|
||||
`tag_ids` varchar(255) DEFAULT NULL COMMENT '标签ID,逗号分隔',
|
||||
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
|
||||
`last_used_time` int(11) DEFAULT NULL COMMENT '最后使用时间',
|
||||
`createTime` int(11) DEFAULT NULL COMMENT '创建时间',
|
||||
`updateTime` int(11) DEFAULT NULL COMMENT '更新时间',
|
||||
`deleteTime` int(11) DEFAULT NULL COMMENT '删除时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_mobile` (`mobile`),
|
||||
KEY `idx_status` (`status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='流量池表';
|
||||
|
||||
-- 流量来源表
|
||||
CREATE TABLE `tk_traffic_source` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
|
||||
`traffic_id` int(10) unsigned NOT NULL COMMENT '关联流量池ID',
|
||||
`plan_id` int(10) unsigned DEFAULT NULL COMMENT '关联计划ID',
|
||||
`scene_id` int(10) unsigned DEFAULT NULL COMMENT '场景ID',
|
||||
`channel` varchar(50) NOT NULL COMMENT '渠道:poster=海报, order=订单, douyin=抖音, xiaohongshu=小红书, phone=电话, wechat=公众号, group=微信群, payment=付款码, api=API接口',
|
||||
`sub_channel` varchar(50) DEFAULT NULL COMMENT '子渠道',
|
||||
`source_detail` text DEFAULT NULL COMMENT '来源详情(JSON格式)',
|
||||
`ip` varchar(50) DEFAULT NULL COMMENT '来源IP',
|
||||
`user_agent` varchar(255) DEFAULT NULL COMMENT '用户代理',
|
||||
`createTime` int(11) DEFAULT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_traffic` (`traffic_id`),
|
||||
KEY `idx_plan` (`plan_id`),
|
||||
KEY `idx_scene` (`scene_id`),
|
||||
KEY `idx_channel` (`channel`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='流量来源表';
|
||||
|
||||
-- 计划执行记录表
|
||||
CREATE TABLE `tk_plan_execution` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
|
||||
`plan_id` int(10) unsigned NOT NULL COMMENT '关联计划ID',
|
||||
`traffic_id` int(10) unsigned DEFAULT NULL COMMENT '关联流量ID',
|
||||
`step` tinyint(3) unsigned NOT NULL COMMENT '执行步骤:1=基础配置,2=加友计划,3=API调用,4=标签处理',
|
||||
`sub_step` varchar(50) DEFAULT NULL COMMENT '子步骤标识',
|
||||
`status` tinyint(3) unsigned DEFAULT 0 COMMENT '状态:0=等待,1=进行中,2=成功,3=失败',
|
||||
`result` text DEFAULT NULL COMMENT '执行结果(JSON格式)',
|
||||
`error` varchar(255) DEFAULT NULL COMMENT '错误信息',
|
||||
`start_time` int(11) DEFAULT NULL COMMENT '开始时间',
|
||||
`end_time` int(11) DEFAULT NULL COMMENT '结束时间',
|
||||
`createTime` int(11) DEFAULT NULL COMMENT '创建时间',
|
||||
`updateTime` int(11) DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_plan` (`plan_id`),
|
||||
KEY `idx_traffic` (`traffic_id`),
|
||||
KEY `idx_status` (`status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='计划执行记录表';
|
||||
|
||||
-- 标签表
|
||||
CREATE TABLE `tk_tag` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
|
||||
`name` varchar(50) NOT NULL COMMENT '标签名称',
|
||||
`color` varchar(20) DEFAULT NULL COMMENT '标签颜色',
|
||||
`type` varchar(20) DEFAULT 'traffic' COMMENT '标签类型:traffic=流量标签,friend=好友标签',
|
||||
`count` int(11) DEFAULT 0 COMMENT '使用次数',
|
||||
`status` tinyint(3) unsigned DEFAULT 1 COMMENT '状态:0=停用,1=启用',
|
||||
`createTime` int(11) DEFAULT NULL COMMENT '创建时间',
|
||||
`updateTime` int(11) DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_name_type` (`name`, `type`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='标签表';
|
||||
Reference in New Issue
Block a user