存客宝应用接口初始化
This commit is contained in:
707
application/cunkebao/controller/AiKnowledgeBaseController.php
Normal file
707
application/cunkebao/controller/AiKnowledgeBaseController.php
Normal file
@@ -0,0 +1,707 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller;
|
||||
|
||||
use app\ai\controller\CozeAI;
|
||||
use app\chukebao\model\AiKnowledgeBaseType;
|
||||
use app\chukebao\model\AiKnowledgeBase;
|
||||
use app\chukebao\model\AiSettings as AiSettingsModel;
|
||||
use library\ResponseHelper;
|
||||
|
||||
/**
|
||||
* AI知识库管理控制器
|
||||
* 负责管理AI知识库类型和知识库内容
|
||||
*/
|
||||
class AiKnowledgeBaseController extends BaseController
|
||||
{
|
||||
// ==================== 知识库类型管理 ====================
|
||||
|
||||
/**
|
||||
* 获取知识库类型列表
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function typeList()
|
||||
{
|
||||
try {
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
// 获取分页参数
|
||||
$page = $this->request->param('page', 1);
|
||||
$pageSize = $this->request->param('pageSize', 20);
|
||||
$includeSystem = $this->request->param('includeSystem', 1); // 是否包含系统类型
|
||||
|
||||
// 构建查询条件
|
||||
$where = [['isDel', '=', 0]];
|
||||
|
||||
if ($includeSystem == 1) {
|
||||
// 包含系统类型和本公司创建的类型
|
||||
$where[] = ['companyId', 'in', [$companyId, 0]];
|
||||
} else {
|
||||
// 只显示本公司创建的类型
|
||||
$where[] = ['companyId', '=', $companyId];
|
||||
$where[] = ['type', '=', AiKnowledgeBaseType::TYPE_USER];
|
||||
}
|
||||
|
||||
// 统计开启的类型总数
|
||||
$enabledCountWhere = $where;
|
||||
$enabledCountWhere[] = ['status', '=', 1];
|
||||
$enabledCount = AiKnowledgeBaseType::where($enabledCountWhere)->count();
|
||||
|
||||
// 查询数据
|
||||
$list = AiKnowledgeBaseType::where($where)
|
||||
->order('type', 'asc') // 系统类型排在前面
|
||||
->order('createTime', 'desc')
|
||||
->paginate($pageSize, false, ['page' => $page]);
|
||||
|
||||
// 为每个类型添加素材数量统计
|
||||
$listData = $list->toArray();
|
||||
foreach ($listData['data'] as &$item) {
|
||||
// 统计该类型下的知识库数量(素材数量)
|
||||
$item['materialCount'] = AiKnowledgeBase::where([
|
||||
['typeId', '=', $item['id']],
|
||||
['isDel', '=', 0]
|
||||
])->count();
|
||||
}
|
||||
|
||||
// 重新构造返回数据
|
||||
$result = [
|
||||
'total' => $listData['total'],
|
||||
'data' => $listData['data'],
|
||||
'enabledCount' => $enabledCount, // 开启的类型总数
|
||||
];
|
||||
|
||||
return ResponseHelper::success($result, '获取成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加知识库类型
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function addType()
|
||||
{
|
||||
try {
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
// 获取参数
|
||||
$name = $this->request->param('name', '');
|
||||
$description = $this->request->param('description', '');
|
||||
$label = $this->request->param('label', []);
|
||||
$prompt = $this->request->param('prompt', '');
|
||||
$status = $this->request->param('status', 1); // 默认启用
|
||||
|
||||
// 参数验证
|
||||
if (empty($name)) {
|
||||
return ResponseHelper::error('类型名称不能为空');
|
||||
}
|
||||
|
||||
// 检查名称是否重复
|
||||
$exists = AiKnowledgeBaseType::where([
|
||||
['companyId', '=', $companyId],
|
||||
['name', '=', $name],
|
||||
['isDel', '=', 0]
|
||||
])->find();
|
||||
|
||||
if ($exists) {
|
||||
return ResponseHelper::error('该类型名称已存在');
|
||||
}
|
||||
|
||||
// 创建类型
|
||||
$typeModel = new AiKnowledgeBaseType();
|
||||
$data = [
|
||||
'type' => AiKnowledgeBaseType::TYPE_USER,
|
||||
'name' => $name,
|
||||
'description' => $description,
|
||||
'label' => json_encode($label,256),
|
||||
'prompt' => $prompt,
|
||||
'status' => $status,
|
||||
'companyId' => $companyId,
|
||||
'userId' => $userId,
|
||||
'createTime' => time(),
|
||||
'updateTime' => time(),
|
||||
'isDel' => 0
|
||||
];
|
||||
|
||||
if ($typeModel->save($data)) {
|
||||
return ResponseHelper::success(['id' => $typeModel->id], '添加成功');
|
||||
} else {
|
||||
return ResponseHelper::error('添加失败');
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑知识库类型
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function editType()
|
||||
{
|
||||
try {
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
// 获取参数
|
||||
$id = $this->request->param('id', 0);
|
||||
$name = $this->request->param('name', '');
|
||||
$description = $this->request->param('description', '');
|
||||
$label = $this->request->param('label', []);
|
||||
$prompt = $this->request->param('prompt', '');
|
||||
$status = $this->request->param('status', '');
|
||||
|
||||
// 参数验证
|
||||
if (empty($id)) {
|
||||
return ResponseHelper::error('类型ID不能为空');
|
||||
}
|
||||
|
||||
if (empty($name)) {
|
||||
return ResponseHelper::error('类型名称不能为空');
|
||||
}
|
||||
|
||||
// 查找类型
|
||||
$typeModel = AiKnowledgeBaseType::where([
|
||||
['id', '=', $id],
|
||||
['isDel', '=', 0]
|
||||
])->find();
|
||||
|
||||
if (!$typeModel) {
|
||||
return ResponseHelper::error('类型不存在');
|
||||
}
|
||||
|
||||
// 检查是否为系统类型
|
||||
if ($typeModel->isSystemType()) {
|
||||
return ResponseHelper::error('系统类型不允许编辑');
|
||||
}
|
||||
|
||||
// 检查权限(只能编辑本公司的类型)
|
||||
if ($typeModel->companyId != $companyId) {
|
||||
return ResponseHelper::error('无权限编辑该类型');
|
||||
}
|
||||
|
||||
// 检查名称是否重复(排除自己)
|
||||
$exists = AiKnowledgeBaseType::where([
|
||||
['companyId', '=', $companyId],
|
||||
['name', '=', $name],
|
||||
['id', '<>', $id],
|
||||
['isDel', '=', 0]
|
||||
])->find();
|
||||
|
||||
if ($exists) {
|
||||
return ResponseHelper::error('该类型名称已存在');
|
||||
}
|
||||
|
||||
// 更新数据
|
||||
$typeModel->name = $name;
|
||||
$typeModel->description = $description;
|
||||
$typeModel->label = json_encode($label,256);
|
||||
$typeModel->prompt = $prompt;
|
||||
if ($status !== '') {
|
||||
$typeModel->status = $status;
|
||||
}
|
||||
$typeModel->updateTime = time();
|
||||
|
||||
if ($typeModel->save()) {
|
||||
return ResponseHelper::success([], '更新成功');
|
||||
} else {
|
||||
return ResponseHelper::error('更新失败');
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改知识库类型状态
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function updateTypeStatus()
|
||||
{
|
||||
try {
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
// 获取参数
|
||||
$id = $this->request->param('id', 0);
|
||||
$status = $this->request->param('status', -1);
|
||||
|
||||
// 参数验证
|
||||
if (empty($id)) {
|
||||
return ResponseHelper::error('类型ID不能为空');
|
||||
}
|
||||
|
||||
if ($status != 0 && $status != 1) {
|
||||
return ResponseHelper::error('状态参数错误');
|
||||
}
|
||||
|
||||
// 查找类型
|
||||
$typeModel = AiKnowledgeBaseType::where([
|
||||
['id', '=', $id],
|
||||
['isDel', '=', 0]
|
||||
])->find();
|
||||
|
||||
if (!$typeModel) {
|
||||
return ResponseHelper::error('类型不存在');
|
||||
}
|
||||
|
||||
// 检查是否为系统类型
|
||||
if ($typeModel->isSystemType()) {
|
||||
return ResponseHelper::error('系统类型不允许修改状态');
|
||||
}
|
||||
|
||||
// 检查权限(只能修改本公司的类型)
|
||||
if ($typeModel->companyId != $companyId) {
|
||||
return ResponseHelper::error('无权限修改该类型');
|
||||
}
|
||||
|
||||
// 更新状态
|
||||
$typeModel->status = $status;
|
||||
$typeModel->updateTime = time();
|
||||
|
||||
if ($typeModel->save()) {
|
||||
$message = $status == 0 ? '禁用成功' : '启用成功';
|
||||
return ResponseHelper::success([], $message);
|
||||
} else {
|
||||
return ResponseHelper::error('操作失败');
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取知识库类型详情
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function detailType()
|
||||
{
|
||||
try {
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
// 获取参数
|
||||
$id = $this->request->param('id', 0);
|
||||
|
||||
// 参数验证
|
||||
if (empty($id)) {
|
||||
return ResponseHelper::error('类型ID不能为空');
|
||||
}
|
||||
|
||||
// 查找类型
|
||||
$typeModel = AiKnowledgeBaseType::where([
|
||||
['id', '=', $id],
|
||||
['isDel', '=', 0]
|
||||
])->find();
|
||||
|
||||
if (!$typeModel) {
|
||||
return ResponseHelper::error('类型不存在');
|
||||
}
|
||||
|
||||
// 检查权限(系统类型或本公司的类型都可以查看)
|
||||
if ($typeModel->companyId != 0 && $typeModel->companyId != $companyId) {
|
||||
return ResponseHelper::error('无权限查看该类型');
|
||||
}
|
||||
|
||||
return ResponseHelper::success($typeModel, '获取成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除知识库类型
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function deleteType()
|
||||
{
|
||||
try {
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
// 获取参数
|
||||
$id = $this->request->param('id', 0);
|
||||
|
||||
// 参数验证
|
||||
if (empty($id)) {
|
||||
return ResponseHelper::error('类型ID不能为空');
|
||||
}
|
||||
|
||||
// 查找类型
|
||||
$typeModel = AiKnowledgeBaseType::where([
|
||||
['id', '=', $id],
|
||||
['isDel', '=', 0]
|
||||
])->find();
|
||||
|
||||
if (!$typeModel) {
|
||||
return ResponseHelper::error('类型不存在');
|
||||
}
|
||||
|
||||
// 检查是否为系统类型
|
||||
if ($typeModel->isSystemType()) {
|
||||
return ResponseHelper::error('系统类型不允许删除');
|
||||
}
|
||||
|
||||
// 检查权限(只能删除本公司的类型)
|
||||
if ($typeModel->companyId != $companyId) {
|
||||
return ResponseHelper::error('无权限删除该类型');
|
||||
}
|
||||
|
||||
// 检查是否有关联的知识库
|
||||
$hasKnowledge = AiKnowledgeBase::where([
|
||||
['typeId', '=', $id],
|
||||
['isDel', '=', 0]
|
||||
])->count();
|
||||
|
||||
if ($hasKnowledge > 0) {
|
||||
return ResponseHelper::error('该类型下存在知识库,无法删除');
|
||||
}
|
||||
|
||||
// 软删除
|
||||
$typeModel->isDel = 1;
|
||||
$typeModel->delTime = time();
|
||||
|
||||
if ($typeModel->save()) {
|
||||
return ResponseHelper::success([], '删除成功');
|
||||
} else {
|
||||
return ResponseHelper::error('删除失败');
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 知识库管理 ====================
|
||||
|
||||
/**
|
||||
* 获取知识库列表
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getList()
|
||||
{
|
||||
try {
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
// 获取分页参数
|
||||
$page = $this->request->param('page', 1);
|
||||
$pageSize = $this->request->param('pageSize', 20);
|
||||
$typeId = $this->request->param('typeId', 0); // 类型筛选
|
||||
$keyword = $this->request->param('keyword', ''); // 关键词搜索
|
||||
|
||||
// 构建查询条件
|
||||
$where = [
|
||||
['isDel', '=', 0],
|
||||
['companyId', '=', $companyId]
|
||||
];
|
||||
|
||||
if ($typeId > 0) {
|
||||
$where[] = ['typeId', '=', $typeId];
|
||||
}
|
||||
|
||||
if (!empty($keyword)) {
|
||||
$where[] = ['name', 'like', '%' . $keyword . '%'];
|
||||
}
|
||||
|
||||
// 查询数据
|
||||
$list = AiKnowledgeBase::where($where)
|
||||
->with(['type'])
|
||||
->order('createTime', 'desc')
|
||||
->paginate($pageSize, false, ['page' => $page]);
|
||||
|
||||
foreach ($list as &$v){
|
||||
$v['size'] = 0;
|
||||
}
|
||||
unset($v);
|
||||
|
||||
return ResponseHelper::success($list, '获取成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加知识库
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
try {
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
$datasetId = AiSettingsModel::where(['companyId' => $companyId])->value('datasetId');
|
||||
|
||||
|
||||
// 获取参数
|
||||
$typeId = $this->request->param('typeId', 0);
|
||||
$name = $this->request->param('name', '');
|
||||
$label = $this->request->param('label', []);
|
||||
$fileUrl = $this->request->param('fileUrl', '');
|
||||
|
||||
// 参数验证
|
||||
if (empty($typeId)) {
|
||||
return ResponseHelper::error('请选择知识库类型');
|
||||
}
|
||||
|
||||
if (empty($name)) {
|
||||
return ResponseHelper::error('知识库名称不能为空');
|
||||
}
|
||||
|
||||
if (empty($fileUrl)) {
|
||||
return ResponseHelper::error('文件地址不能为空');
|
||||
}
|
||||
|
||||
// 检查类型是否存在
|
||||
$typeExists = AiKnowledgeBaseType::where([
|
||||
['id', '=', $typeId],
|
||||
['isDel', '=', 0]
|
||||
])->find();
|
||||
|
||||
if (!$typeExists) {
|
||||
return ResponseHelper::error('知识库类型不存在');
|
||||
}
|
||||
|
||||
// 创建知识库
|
||||
$knowledgeModel = new AiKnowledgeBase();
|
||||
$data = [
|
||||
'typeId' => $typeId,
|
||||
'name' => $name,
|
||||
'label' => json_encode($label, 256),
|
||||
'fileUrl' => $fileUrl,
|
||||
'companyId' => $companyId,
|
||||
'userId' => $userId,
|
||||
'createTime' => time(),
|
||||
'updateTime' => time(),
|
||||
'isDel' => 0
|
||||
];
|
||||
|
||||
if ($knowledgeModel->save($data)) {
|
||||
if (!empty($datasetId)) {
|
||||
$createDocumentData = [
|
||||
'filePath' => $fileUrl,
|
||||
'fileName' => $name,
|
||||
'dataset_id' => $datasetId
|
||||
];
|
||||
$cozeAI = new CozeAI();
|
||||
$result = $cozeAI->createDocument($createDocumentData);
|
||||
$result = json_decode($result, true);
|
||||
if ($result['code'] == 200) {
|
||||
$documentId = $result['data'][0]['document_id'];
|
||||
AiKnowledgeBase::where('id', $knowledgeModel->id)->update(['documentId' => $documentId, 'updateTime' => time()]);
|
||||
AiSettingsModel::where(['companyId' => $companyId])->update(['isRelease' => 0,'updateTime' => time()]);
|
||||
}
|
||||
}
|
||||
return ResponseHelper::success(['id' => $knowledgeModel->id], '添加成功');
|
||||
} else {
|
||||
return ResponseHelper::error('添加失败');
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑知识库
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function edit()
|
||||
{
|
||||
try {
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
// 获取参数
|
||||
$id = $this->request->param('id', 0);
|
||||
$typeId = $this->request->param('typeId', 0);
|
||||
$name = $this->request->param('name', '');
|
||||
$label = $this->request->param('label', []);
|
||||
$fileUrl = $this->request->param('fileUrl', '');
|
||||
|
||||
// 参数验证
|
||||
if (empty($id)) {
|
||||
return ResponseHelper::error('知识库ID不能为空');
|
||||
}
|
||||
|
||||
if (empty($typeId)) {
|
||||
return ResponseHelper::error('请选择知识库类型');
|
||||
}
|
||||
|
||||
if (empty($name)) {
|
||||
return ResponseHelper::error('知识库名称不能为空');
|
||||
}
|
||||
|
||||
// 查找知识库
|
||||
$knowledgeModel = AiKnowledgeBase::where([
|
||||
['id', '=', $id],
|
||||
['companyId', '=', $companyId],
|
||||
['isDel', '=', 0]
|
||||
])->find();
|
||||
|
||||
if (!$knowledgeModel) {
|
||||
return ResponseHelper::error('知识库不存在或无权限编辑');
|
||||
}
|
||||
|
||||
// 检查类型是否存在
|
||||
$typeExists = AiKnowledgeBaseType::where([
|
||||
['id', '=', $typeId],
|
||||
['isDel', '=', 0]
|
||||
])->find();
|
||||
|
||||
if (!$typeExists) {
|
||||
return ResponseHelper::error('知识库类型不存在');
|
||||
}
|
||||
|
||||
// 更新数据
|
||||
$knowledgeModel->typeId = $typeId;
|
||||
$knowledgeModel->name = $name;
|
||||
$knowledgeModel->label = json_encode($label, 256);
|
||||
if (!empty($fileUrl)) {
|
||||
$knowledgeModel->fileUrl = $fileUrl;
|
||||
}
|
||||
$knowledgeModel->updateTime = time();
|
||||
|
||||
if ($knowledgeModel->save()) {
|
||||
return ResponseHelper::success([], '更新成功');
|
||||
} else {
|
||||
return ResponseHelper::error('更新失败');
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除知识库
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
try {
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
// 获取参数
|
||||
$id = $this->request->param('id', 0);
|
||||
|
||||
// 参数验证
|
||||
if (empty($id)) {
|
||||
return ResponseHelper::error('知识库ID不能为空');
|
||||
}
|
||||
|
||||
// 查找知识库
|
||||
$knowledgeModel = AiKnowledgeBase::where([
|
||||
['id', '=', $id],
|
||||
['companyId', '=', $companyId],
|
||||
['isDel', '=', 0]
|
||||
])->find();
|
||||
|
||||
if (!$knowledgeModel) {
|
||||
return ResponseHelper::error('知识库不存在或无权限删除');
|
||||
}
|
||||
|
||||
// 软删除
|
||||
$knowledgeModel->isDel = 1;
|
||||
$knowledgeModel->delTime = time();
|
||||
|
||||
if ($knowledgeModel->save()) {
|
||||
if (!empty($knowledgeModel->documentId)){
|
||||
$cozeAI = new CozeAI();
|
||||
$cozeAI->deleteDocument([$knowledgeModel->documentId]);
|
||||
AiSettingsModel::where(['companyId' => $companyId])->update(['isRelease' => 0,'updateTime' => time()]);
|
||||
}
|
||||
return ResponseHelper::success([], '删除成功');
|
||||
} else {
|
||||
return ResponseHelper::error('删除失败');
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取知识库详情
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function detail()
|
||||
{
|
||||
try {
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
// 获取参数
|
||||
$id = $this->request->param('id', 0);
|
||||
|
||||
// 参数验证
|
||||
if (empty($id)) {
|
||||
return ResponseHelper::error('知识库ID不能为空');
|
||||
}
|
||||
|
||||
// 查找知识库
|
||||
$knowledge = AiKnowledgeBase::where([
|
||||
['id', '=', $id],
|
||||
['companyId', '=', $companyId],
|
||||
['isDel', '=', 0]
|
||||
])->with(['type'])->find();
|
||||
|
||||
if (!$knowledge) {
|
||||
return ResponseHelper::error('知识库不存在或无权限查看');
|
||||
}
|
||||
|
||||
return ResponseHelper::success($knowledge, '获取成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
433
application/cunkebao/controller/AiSettingsController.php
Normal file
433
application/cunkebao/controller/AiSettingsController.php
Normal file
@@ -0,0 +1,433 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller;
|
||||
|
||||
use app\ai\controller\CozeAI;
|
||||
use app\api\model\CompanyModel;
|
||||
use app\chukebao\model\AiSettings as AiSettingsModel;
|
||||
use library\ResponseHelper;
|
||||
|
||||
/**
|
||||
* AI设置控制器
|
||||
* 负责管理公司的AI智能体配置,包括创建智能体、知识库等
|
||||
*/
|
||||
class AiSettingsController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 初始化AI设置
|
||||
* 检查公司是否已有AI配置,如果没有则创建默认配置
|
||||
* 自动创建智能体和知识库
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
try {
|
||||
// 获取当前用户信息
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
// 查找公司AI设置
|
||||
$settings = $this->getOrCreateAiSettings($companyId, $userId);
|
||||
|
||||
if (!$settings) {
|
||||
return ResponseHelper::error('AI设置初始化失败');
|
||||
}
|
||||
|
||||
// 确保智能体已创建
|
||||
if (empty($settings->botId)) {
|
||||
$settings->releaseTime = 0;
|
||||
$botCreated = $this->createBot($settings);
|
||||
if (!$botCreated) {
|
||||
return ResponseHelper::error('智能体创建失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 确保知识库已创建
|
||||
if (empty($settings->datasetId)) {
|
||||
$settings->releaseTime = 0;
|
||||
$knowledgeCreated = $this->createKnowledge($settings);
|
||||
if (!$knowledgeCreated) {
|
||||
return ResponseHelper::error('知识库创建失败');
|
||||
}
|
||||
}
|
||||
if (!empty($settings->botId) && !empty($settings->datasetId) && $settings->releaseTime <= 0) {
|
||||
$cozeAI = new CozeAI();
|
||||
$config = json_decode($settings->config,true);
|
||||
$config['bot_id'] = $settings->botId;
|
||||
$config['dataset_ids'] = [$settings->datasetId];
|
||||
$cozeAI->updateBot($config);
|
||||
}
|
||||
|
||||
// 解析配置信息
|
||||
$settings->config = json_decode($settings->config, true);
|
||||
|
||||
return ResponseHelper::success($settings, 'AI设置初始化成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取或创建AI设置
|
||||
*
|
||||
* @param int $companyId 公司ID
|
||||
* @param int $userId 用户ID
|
||||
* @return AiSettingsModel|false
|
||||
*/
|
||||
private function getOrCreateAiSettings($companyId, $userId)
|
||||
{
|
||||
// 查找现有设置
|
||||
$settings = AiSettingsModel::where(['companyId' => $companyId])->find();
|
||||
|
||||
if (empty($settings)) {
|
||||
// 获取公司信息
|
||||
$company = CompanyModel::where('id', $companyId)->find();
|
||||
if (empty($company)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建默认配置
|
||||
$config = $this->getDefaultConfig($company['name']);
|
||||
|
||||
// 保存AI设置
|
||||
$settings = $this->saveAiSettings($companyId, $userId, $config);
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认AI配置
|
||||
*
|
||||
* @param string $companyName 公司名称
|
||||
* @return array
|
||||
*/
|
||||
private function getDefaultConfig($companyName)
|
||||
{
|
||||
return [
|
||||
'name' => $companyName,
|
||||
'model_id' => '1737521813', // 默认模型ID
|
||||
'prompt_info' => $this->getDefaultPrompt()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认提示词
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getDefaultPrompt()
|
||||
{
|
||||
return '# 角色
|
||||
你是一位全能知识客服,作为专业的客服智能体,具备全面的知识储备,能够回答用户提出的各类问题。在回答问题前,会仔细查阅知识库内容,并且始终严格遵守中国法律法规。
|
||||
|
||||
## 技能
|
||||
### 技能 1: 回答用户问题
|
||||
1. 当用户提出问题时,首先在知识库中进行搜索查找相关信息。
|
||||
2. 依据知识库中的内容,为用户提供准确、清晰、完整的回答。
|
||||
|
||||
## 限制
|
||||
- 仅依据知识库内容回答问题,对于知识库中没有的信息,如实告知用户无法回答。
|
||||
- 回答必须严格遵循中国法律法规,不得出现任何违法违规内容。
|
||||
- 回答需简洁明了,避免冗长复杂的表述(尽量在100字内)。
|
||||
- 适当加些表情点缀。';
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存AI设置到数据库
|
||||
*
|
||||
* @param int $companyId 公司ID
|
||||
* @param int $userId 用户ID
|
||||
* @param array $config 配置信息
|
||||
* @return AiSettingsModel|false
|
||||
*/
|
||||
private function saveAiSettings($companyId, $userId, $config)
|
||||
{
|
||||
$data = [
|
||||
'companyId' => $companyId,
|
||||
'userId' => $userId,
|
||||
'config' => json_encode($config, JSON_UNESCAPED_UNICODE),
|
||||
'createTime' => time(),
|
||||
'updateTime' => time(),
|
||||
'botId' => 0,
|
||||
'datasetId' => 0,
|
||||
];
|
||||
|
||||
$aiSettingsModel = new AiSettingsModel();
|
||||
$result = $aiSettingsModel->save($data);
|
||||
|
||||
if ($result) {
|
||||
return AiSettingsModel::where(['companyId' => $companyId])->find();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建AI智能体
|
||||
*
|
||||
* @param AiSettingsModel $settings AI设置对象
|
||||
* @return bool
|
||||
*/
|
||||
private function createBot($settings)
|
||||
{
|
||||
if (empty($settings)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$config = json_decode($settings->config, true);
|
||||
if (empty($config)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 调用CozeAI创建智能体
|
||||
$cozeAI = new CozeAI();
|
||||
$result = $cozeAI->createBot($config);
|
||||
$result = json_decode($result, true);
|
||||
|
||||
if ($result['code'] != 200) {
|
||||
\think\facade\Log::error('智能体创建失败:' . ($result['msg'] ?? '未知错误'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 更新智能体ID
|
||||
$settings->botId = $result['data']['bot_id'];
|
||||
$settings->updateTime = time();
|
||||
|
||||
return $settings->save();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\think\facade\Log::error('创建智能体异常:' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建知识库
|
||||
*
|
||||
* @param AiSettingsModel $settings AI设置对象
|
||||
* @return bool
|
||||
*/
|
||||
private function createKnowledge($settings)
|
||||
{
|
||||
if (empty($settings)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$config = json_decode($settings->config, true);
|
||||
if (empty($config)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 调用CozeAI创建知识库
|
||||
$cozeAI = new CozeAI();
|
||||
$result = $cozeAI->createKnowledge(['name' => $config['name']]);
|
||||
$result = json_decode($result, true);
|
||||
|
||||
if ($result['code'] != 200) {
|
||||
\think\facade\Log::error('知识库创建失败:' . ($result['msg'] ?? '未知错误'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 更新知识库ID
|
||||
$settings->datasetId = $result['data']['dataset_id'];
|
||||
$settings->updateTime = time();
|
||||
|
||||
return $settings->save();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\think\facade\Log::error('创建知识库异常:' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新AI配置
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function updateConfig()
|
||||
{
|
||||
try {
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
// 获取请求参数
|
||||
$config = $this->request->param('config', []);
|
||||
if (empty($config)) {
|
||||
return ResponseHelper::error('配置参数不能为空');
|
||||
}
|
||||
|
||||
// 查找现有设置
|
||||
$settings = AiSettingsModel::where(['companyId' => $companyId])->find();
|
||||
if (empty($settings)) {
|
||||
return ResponseHelper::error('AI设置不存在,请先初始化');
|
||||
}
|
||||
|
||||
// 更新配置
|
||||
$settings->config = json_encode($config, JSON_UNESCAPED_UNICODE);
|
||||
$settings->updateTime = time();
|
||||
|
||||
if ($settings->save()) {
|
||||
return ResponseHelper::success([], '配置更新成功');
|
||||
} else {
|
||||
return ResponseHelper::error('配置更新失败');
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取AI设置详情
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getSettings()
|
||||
{
|
||||
try {
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
$settings = AiSettingsModel::where(['companyId' => $companyId])->find();
|
||||
if (empty($settings)) {
|
||||
return ResponseHelper::error('AI设置不存在');
|
||||
}
|
||||
|
||||
// 解析配置信息
|
||||
$settings->config = json_decode($settings->config, true);
|
||||
|
||||
return ResponseHelper::success($settings, '获取成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发布智能体
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function release()
|
||||
{
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$settings = AiSettingsModel::where(['companyId' => $companyId])->find();
|
||||
if (!empty($settings->isRelease)) {
|
||||
return ResponseHelper::success('', '已发布,无需重复发布');
|
||||
}
|
||||
|
||||
$cozeAI = new CozeAI();
|
||||
$res = $cozeAI->botPublish(['bot_id' => $settings->botId]);
|
||||
$res = json_decode($res, true);
|
||||
|
||||
if ($res['code'] != 200) {
|
||||
$msg = '发布失败失败:' . ($res['msg'] ?? '未知错误');
|
||||
return ResponseHelper::error($msg);
|
||||
}
|
||||
$settings->isRelease = 1;
|
||||
$settings->releaseTime = time();
|
||||
$settings->save();
|
||||
return ResponseHelper::success('', '发布成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存统一提示词
|
||||
* 先更新数据库,再调用CozeAI接口更新智能体
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function savePrompt()
|
||||
{
|
||||
try {
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
// 获取提示词参数
|
||||
$promptInfo = $this->request->param('promptInfo', '');
|
||||
if (empty($promptInfo)) {
|
||||
return ResponseHelper::error('提示词内容不能为空');
|
||||
}
|
||||
|
||||
// 查找AI设置
|
||||
$settings = AiSettingsModel::where(['companyId' => $companyId])->find();
|
||||
if (empty($settings)) {
|
||||
return ResponseHelper::error('AI设置不存在,请先初始化');
|
||||
}
|
||||
|
||||
// 检查智能体是否已创建
|
||||
if (empty($settings->botId)) {
|
||||
return ResponseHelper::error('智能体未创建,请先初始化AI设置');
|
||||
}
|
||||
|
||||
// 解析现有配置
|
||||
$config = json_decode($settings->config, true);
|
||||
if (!is_array($config)) {
|
||||
$config = [];
|
||||
}
|
||||
|
||||
// 更新提示词
|
||||
$config['prompt_info'] = $promptInfo;
|
||||
|
||||
// 第一步:更新数据库
|
||||
$settings->config = json_encode($config, JSON_UNESCAPED_UNICODE);
|
||||
$settings->isRelease = 0; // 标记为未发布状态
|
||||
$settings->updateTime = time();
|
||||
|
||||
if (!$settings->save()) {
|
||||
return ResponseHelper::error('数据库更新失败');
|
||||
}
|
||||
|
||||
// 第二步:调用CozeAI接口更新智能体
|
||||
try {
|
||||
$cozeAI = new CozeAI();
|
||||
|
||||
// 参考 init 方法的参数格式,传递完整的 config
|
||||
$updateData = $config;
|
||||
$updateData['bot_id'] = $settings->botId;
|
||||
|
||||
// 如果有知识库,也一并传入
|
||||
if (!empty($settings->datasetId)) {
|
||||
$updateData['dataset_ids'] = [$settings->datasetId];
|
||||
}
|
||||
|
||||
$result = $cozeAI->updateBot($updateData);
|
||||
$result = json_decode($result, true);
|
||||
|
||||
if ($result['code'] != 200) {
|
||||
\think\facade\Log::error('更新智能体提示词失败:' . json_encode($result));
|
||||
return ResponseHelper::error('更新智能体失败:' . ($result['msg'] ?? '未知错误'));
|
||||
}
|
||||
|
||||
return ResponseHelper::success([
|
||||
'prompt_info' => $promptInfo,
|
||||
'isRelease' => 0
|
||||
], '提示词保存成功,请重新发布智能体');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\think\facade\Log::error('调用CozeAI更新接口异常:' . $e->getMessage());
|
||||
return ResponseHelper::error('更新智能体接口调用失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\think\facade\Log::error('保存提示词异常:' . $e->getMessage());
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
163
application/cunkebao/controller/BaseController.php
Normal file
163
application/cunkebao/controller/BaseController.php
Normal file
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller;
|
||||
|
||||
use app\api\controller\AccountController;
|
||||
use app\api\controller\UserController;
|
||||
use app\common\service\ClassTableService;
|
||||
use library\ResponseHelper;
|
||||
use think\Controller;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 设备管理控制器
|
||||
*/
|
||||
class BaseController extends Controller
|
||||
{
|
||||
/**
|
||||
* 用户信息
|
||||
* @var object
|
||||
*/
|
||||
protected $user;
|
||||
|
||||
/**
|
||||
* @var ClassTableService
|
||||
*/
|
||||
protected $classTable;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function __construct(ClassTableService $classTable)
|
||||
{
|
||||
$this->classTable = $classTable;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
protected function initialize()
|
||||
{
|
||||
parent::initialize();
|
||||
|
||||
date_default_timezone_set('Asia/Shanghai');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*
|
||||
* @param string $column
|
||||
* @return mixed
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getUserInfo(?string $column = null)
|
||||
{
|
||||
$user = $this->request->userInfo;
|
||||
|
||||
if (!$user) {
|
||||
throw new \Exception('未授权访问,缺少有效的身份凭证', 401);
|
||||
}
|
||||
|
||||
return $column ? $user[$column] : $user;
|
||||
}
|
||||
|
||||
|
||||
public function editUserInfo()
|
||||
{
|
||||
$userId = $this->request->param('userId', '');
|
||||
$nickname = $this->request->param('nickname', '');
|
||||
$avatar = $this->request->param('avatar', '');
|
||||
$phone = $this->request->param('phone', '');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($userId)) {
|
||||
return ResponseHelper::error('用户id不能为空');
|
||||
}
|
||||
|
||||
if (empty($nickname) && empty($avatar) && empty($phone)) {
|
||||
return ResponseHelper::error('修改的用户信息不能为空');
|
||||
}
|
||||
|
||||
$user = Db::name('users')->where(['id' => $userId, 'companyId' => $companyId])->find();
|
||||
if (empty($user)) {
|
||||
return ResponseHelper::error('用户不存在');
|
||||
}
|
||||
|
||||
$user2 = Db::name('users')->where(['phone' => $phone])->find();
|
||||
if (!empty($user2) && $user2['id'] != $userId) {
|
||||
return ResponseHelper::error('修改的手机号已存在');
|
||||
}
|
||||
|
||||
$data = [
|
||||
'id' => $user['s2_accountId'],
|
||||
];
|
||||
|
||||
if (!empty($nickname)) {
|
||||
$data['nickname'] = $nickname;
|
||||
}
|
||||
if (!empty($avatar)) {
|
||||
$data['avatar'] = $avatar;
|
||||
}
|
||||
if (!empty($phone)) {
|
||||
$data['phone'] = $phone;
|
||||
}
|
||||
|
||||
$AccountControllel = new AccountController();
|
||||
$res = $AccountControllel->accountModify($data);
|
||||
$res = json_decode($res, true);
|
||||
if ($res['code'] == 200) {
|
||||
unset($data['id']);
|
||||
if (!empty($nickname)) {
|
||||
$data['username'] = $nickname;
|
||||
unset($data['nickname']);
|
||||
}
|
||||
Db::name('users')->where(['id' => $userId, 'companyId' => $companyId])->update($data);
|
||||
return ResponseHelper::success('更新成功');
|
||||
} else {
|
||||
return ResponseHelper::error($res['msg']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function editPassWord()
|
||||
{
|
||||
$userId = $this->request->param('userId', '');
|
||||
$passWord = $this->request->param('passWord', '');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($userId)) {
|
||||
return ResponseHelper::error('用户id不能为空');
|
||||
}
|
||||
|
||||
if (empty($passWord)) {
|
||||
return ResponseHelper::error('密码不能为空');
|
||||
}
|
||||
|
||||
$user = Db::name('users')->where(['id' => $userId, 'companyId' => $companyId])->find();
|
||||
if (empty($user)) {
|
||||
return ResponseHelper::error('用户不存在');
|
||||
}
|
||||
if ($user['passwordMd5'] == md5($passWord)) {
|
||||
return ResponseHelper::error('新密码与旧密码一致');
|
||||
}
|
||||
|
||||
$data = [
|
||||
'passwordMd5' => md5($passWord),
|
||||
'passwordLocal' => localEncrypt($passWord),
|
||||
'updateTime' => time()
|
||||
];
|
||||
|
||||
$res = Db::name('users')->where(['id' => $userId, 'companyId' => $companyId])->update($data);
|
||||
if (!empty($res)) {
|
||||
if ($user['typeId'] == 1 && !empty($user['s2_accountId'])) {
|
||||
$UserController = new UserController();
|
||||
$UserController->modifyPwd(['id' => $user['s2_accountId'],'pwd' => $passWord]);
|
||||
}
|
||||
|
||||
|
||||
return ResponseHelper::success('密码修改成功');
|
||||
} else {
|
||||
return ResponseHelper::error('密码修改失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
2913
application/cunkebao/controller/ContentLibraryController.php
Normal file
2913
application/cunkebao/controller/ContentLibraryController.php
Normal file
File diff suppressed because it is too large
Load Diff
27
application/cunkebao/controller/Pay.php
Normal file
27
application/cunkebao/controller/Pay.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller;
|
||||
|
||||
|
||||
use app\common\controller\PaymentService;
|
||||
|
||||
class Pay
|
||||
{
|
||||
|
||||
public function createOrder()
|
||||
{
|
||||
$order = [
|
||||
'companyId' => 111,
|
||||
'userId' => 111,
|
||||
'orderNo' => date('YmdHis') . rand(100000, 999999),
|
||||
'goodsId' => 34,
|
||||
'goodsName' => '测试测试',
|
||||
'orderType' => 1,
|
||||
'money' => 1
|
||||
];
|
||||
|
||||
$paymentService = new PaymentService();
|
||||
$res = $paymentService->createOrder($order);
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
254
application/cunkebao/controller/Plan.php
Normal file
254
application/cunkebao/controller/Plan.php
Normal file
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller;
|
||||
|
||||
use think\Controller;
|
||||
use think\Db;
|
||||
use think\facade\Request;
|
||||
use library\ResponseHelper;
|
||||
|
||||
/**
|
||||
* 获客场景控制器
|
||||
*/
|
||||
class Plan extends Controller
|
||||
{
|
||||
/**
|
||||
* 添加计划任务
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
// 获取表单数据
|
||||
$data = [
|
||||
'name' => Request::post('name', ''),
|
||||
'sceneId' => Request::post('sceneId', 0),
|
||||
'status' => Request::post('status', 0),
|
||||
'reqConf' => Request::post('reqConf', ''),
|
||||
'msgConf' => Request::post('msgConf', ''),
|
||||
'tagConf' => Request::post('tagConf', ''),
|
||||
'createTime' => time(),
|
||||
'updateTime' => time()
|
||||
];
|
||||
|
||||
// 验证必填字段
|
||||
if (empty($data['name'])) {
|
||||
return ResponseHelper::error('计划名称不能为空', 400);
|
||||
}
|
||||
|
||||
if (empty($data['sceneId'])) {
|
||||
return ResponseHelper::error('场景ID不能为空', 400);
|
||||
}
|
||||
|
||||
// 验证数据格式
|
||||
if (!$this->validateJson($data['reqConf'])) {
|
||||
return ResponseHelper::error('好友申请设置格式不正确', 400);
|
||||
}
|
||||
|
||||
if (!$this->validateJson($data['msgConf'])) {
|
||||
return ResponseHelper::error('消息设置格式不正确', 400);
|
||||
}
|
||||
|
||||
if (!$this->validateJson($data['tagConf'])) {
|
||||
return ResponseHelper::error('标签设置格式不正确', 400);
|
||||
}
|
||||
|
||||
// 插入数据库
|
||||
$result = Db::name('friend_plan')->insert($data);
|
||||
|
||||
if ($result) {
|
||||
return ResponseHelper::success([], '添加计划任务成功');
|
||||
} else {
|
||||
return ResponseHelper::error('添加计划任务失败', 500);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取计划任务列表
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getList()
|
||||
{
|
||||
try {
|
||||
// 获取分页参数
|
||||
$id = Request::param('id', 1);
|
||||
$page = Request::param('page', 1);
|
||||
$pageSize = Request::param('pageSize', 10);
|
||||
|
||||
// 构建查询条件
|
||||
$where = [];
|
||||
|
||||
// 过滤已删除的记录
|
||||
$where[] = ['deleteTime', 'null'];
|
||||
|
||||
// 查询总数
|
||||
$total = Db::name('friend_plan')->where('sceneId', $id)->count();
|
||||
|
||||
// 查询列表数据
|
||||
$list = Db::name('friend_plan')
|
||||
->where('sceneId', $id)
|
||||
->field('id, name, status, createTime, updateTime, sceneId')
|
||||
->order('createTime desc')
|
||||
->page($page, $pageSize)
|
||||
->select();
|
||||
// 遍历列表,获取每个计划的统计信息
|
||||
foreach ($list as &$item) {
|
||||
// 获取计划的统计信息
|
||||
$stats = $this->getPlanStats($item['id']);
|
||||
|
||||
// 合并统计信息到结果中
|
||||
$item = array_merge($item, $stats);
|
||||
|
||||
// 格式化状态为文字描述
|
||||
$item['statusText'] = $item['status'] == 1 ? '进行中' : '已暂停';
|
||||
|
||||
// 格式化时间
|
||||
$item['createTimeFormat'] = date('Y-m-d H:i', $item['createTime']);
|
||||
|
||||
// 获取最近一次执行时间
|
||||
$lastExecution = $this->getLastExecution($item['id']);
|
||||
$item['lastExecutionTime'] = $lastExecution['lastTime'] ?? '';
|
||||
$item['nextExecutionTime'] = $lastExecution['nextTime'] ?? '';
|
||||
}
|
||||
|
||||
// 返回结果
|
||||
$result = [
|
||||
'total' => $total,
|
||||
'list' => $list,
|
||||
'page' => $page,
|
||||
'pageSize' => $pageSize
|
||||
];
|
||||
|
||||
return ResponseHelper::success($result);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('获取数据失败: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取计划的统计信息
|
||||
*
|
||||
* @param int $planId 计划ID
|
||||
* @return array
|
||||
*/
|
||||
private function getPlanStats($planId)
|
||||
{
|
||||
try {
|
||||
// 获取设备数
|
||||
$deviceCount = $this->getDeviceCount($planId);
|
||||
|
||||
// 获取已获客数
|
||||
$customerCount = $this->getCustomerCount($planId);
|
||||
|
||||
// 获取已添加数
|
||||
$addedCount = 1; //$this->getAddedCount($planId);
|
||||
|
||||
// 计算通过率
|
||||
$passRate = $customerCount > 0 ? round(($addedCount / $customerCount) * 100) : 0;
|
||||
|
||||
return [
|
||||
'deviceCount' => $deviceCount,
|
||||
'customerCount' => $customerCount,
|
||||
'addedCount' => $addedCount,
|
||||
'passRate' => $passRate
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
return [
|
||||
'deviceCount' => 0,
|
||||
'customerCount' => 0,
|
||||
'addedCount' => 0,
|
||||
'passRate' => 0
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取计划使用的设备数
|
||||
*
|
||||
* @param int $planId 计划ID
|
||||
* @return int
|
||||
*/
|
||||
private function getDeviceCount($planId)
|
||||
{
|
||||
try {
|
||||
// 获取计划
|
||||
$plan = Db::name('friend_plan')->where('id', $planId)->find();
|
||||
if (!$plan) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 解析reqConf
|
||||
$reqConf = json_decode($plan['reqConf'], true);
|
||||
|
||||
// 返回设备数量
|
||||
return isset($reqConf['selectedDevices']) ? count($reqConf['selectedDevices']) : 0;
|
||||
} catch (\Exception $e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取计划的已获客数
|
||||
*
|
||||
* @param int $planId 计划ID
|
||||
* @return int
|
||||
*/
|
||||
private function getCustomerCount($planId)
|
||||
{
|
||||
// 模拟数据,实际应从相关表获取
|
||||
return rand(10, 50);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取计划的已添加数
|
||||
*
|
||||
* @param int $planId 计划ID
|
||||
* @return int
|
||||
*/
|
||||
private function getAddedCount($planId)
|
||||
{
|
||||
// 模拟数据,实际应从相关表获取
|
||||
$customerCount = $this->getCustomerCount($planId);
|
||||
return rand(5, $customerCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取计划的最近一次执行时间
|
||||
*
|
||||
* @param int $planId 计划ID
|
||||
* @return array
|
||||
*/
|
||||
private function getLastExecution($planId)
|
||||
{
|
||||
// 模拟数据,实际应从执行记录表获取
|
||||
$now = time();
|
||||
$lastTime = $now - rand(3600, 86400);
|
||||
$nextTime = $now + rand(3600, 86400);
|
||||
|
||||
return [
|
||||
'lastTime' => date('Y-m-d H:i', $lastTime),
|
||||
'nextTime' => date('Y-m-d H:i:s', $nextTime)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证JSON格式是否正确
|
||||
*
|
||||
* @param string $string
|
||||
* @return bool
|
||||
*/
|
||||
private function validateJson($string)
|
||||
{
|
||||
if (empty($string)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
json_decode($string);
|
||||
return (json_last_error() == JSON_ERROR_NONE);
|
||||
}
|
||||
}
|
||||
401
application/cunkebao/controller/RFMController.php
Normal file
401
application/cunkebao/controller/RFMController.php
Normal file
@@ -0,0 +1,401 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller;
|
||||
|
||||
use think\Db;
|
||||
use app\store\model\TrafficOrderModel;
|
||||
use app\common\model\TrafficSource;
|
||||
use app\store\model\WechatFriendModel;
|
||||
|
||||
/**
|
||||
* RFM 客户价值评分控制器
|
||||
* 基于 RFM 客户价值评分体系技术实施文档实现
|
||||
*/
|
||||
class RFMController extends BaseController
|
||||
{
|
||||
// 默认配置参数
|
||||
const DEFAULT_CYCLE_DAYS = 180; // 默认统计周期(天)
|
||||
const DEFAULT_WEIGHT_R = 0.4; // R维度权重
|
||||
const DEFAULT_WEIGHT_F = 0.3; // F维度权重
|
||||
const DEFAULT_WEIGHT_M = 0.3; // M维度权重
|
||||
const DEFAULT_ABNORMAL_MONEY_RATIO = 3.0; // 异常金额阈值倍数
|
||||
const DEFAULT_SCORE_SCALE = 5; // 默认5分制
|
||||
|
||||
/**
|
||||
* 从 traffic_order 表计算客户 RFM 评分
|
||||
*
|
||||
* @param string|null $identifier 流量池用户标识
|
||||
* @param string|null $ownerWechatId 微信ID,为空则统计所有数据
|
||||
* @param array $config 配置参数
|
||||
* - cycle_days: 统计周期(天),默认180
|
||||
* - weight_R: R维度权重,默认0.4
|
||||
* - weight_F: F维度权重,默认0.3
|
||||
* - weight_M: M维度权重,默认0.3
|
||||
* - abnormal_money_ratio: 异常金额阈值倍数,默认3.0
|
||||
* - score_scale: 评分分制(5或100),默认5
|
||||
* - missing_strategy: 缺失值处理策略('score_1'或'exclude'),默认'score_1'
|
||||
* @return array
|
||||
*/
|
||||
public function calculateRfmFromTrafficOrder($identifier = null, $ownerWechatId = null, $config = [])
|
||||
{
|
||||
try {
|
||||
// 合并配置参数
|
||||
$cycleDays = isset($config['cycle_days']) ? (int)$config['cycle_days'] : self::DEFAULT_CYCLE_DAYS;
|
||||
$weightR = isset($config['weight_R']) ? (float)$config['weight_R'] : self::DEFAULT_WEIGHT_R;
|
||||
$weightF = isset($config['weight_F']) ? (float)$config['weight_F'] : self::DEFAULT_WEIGHT_F;
|
||||
$weightM = isset($config['weight_M']) ? (float)$config['weight_M'] : self::DEFAULT_WEIGHT_M;
|
||||
$abnormalMoneyRatio = isset($config['abnormal_money_ratio']) ? (float)$config['abnormal_money_ratio'] : self::DEFAULT_ABNORMAL_MONEY_RATIO;
|
||||
$scoreScale = isset($config['score_scale']) ? (int)$config['score_scale'] : self::DEFAULT_SCORE_SCALE;
|
||||
$missingStrategy = isset($config['missing_strategy']) ? $config['missing_strategy'] : 'score_1';
|
||||
|
||||
// 权重归一化处理
|
||||
$weightSum = $weightR + $weightF + $weightM;
|
||||
if ($weightSum != 1.0) {
|
||||
$weightR = $weightR / $weightSum;
|
||||
$weightF = $weightF / $weightSum;
|
||||
$weightM = $weightM / $weightSum;
|
||||
}
|
||||
|
||||
// 计算时间范围
|
||||
$endTime = time(); // 统计截止时间(当前时间)
|
||||
$startTime = $endTime - ($cycleDays * 24 * 3600); // 统计起始时间
|
||||
|
||||
// 构建查询条件
|
||||
$where = [
|
||||
['isDel', '=', 0],
|
||||
['createTime', '>=', $startTime],
|
||||
['createTime', '<', $endTime],
|
||||
];
|
||||
|
||||
// identifier 条件
|
||||
if (!empty($identifier)) {
|
||||
$where[] = ['identifier', '=', $identifier];
|
||||
}
|
||||
|
||||
// ownerWechatId 条件
|
||||
if (!empty($ownerWechatId)) {
|
||||
$where[] = ['ownerWechatId', '=', $ownerWechatId];
|
||||
}
|
||||
|
||||
// 1. 数据过滤和聚合 - 获取每个客户的R、F、M原始值
|
||||
$orderModel = new TrafficOrderModel();
|
||||
$customers = $orderModel
|
||||
->where($where)
|
||||
->where(function ($query) {
|
||||
// 只统计有效订单(actualPay大于0)
|
||||
$query->where('actualPay', '>', 0);
|
||||
})
|
||||
->field('identifier, MAX(createTime) as lastOrderTime, COUNT(DISTINCT id) as orderCount, SUM(CAST(actualPay AS DECIMAL(18,2))) as totalAmount')
|
||||
->group('identifier')
|
||||
->select();
|
||||
|
||||
if (empty($customers)) {
|
||||
return [
|
||||
'code' => 200,
|
||||
'msg' => '暂无数据',
|
||||
'data' => []
|
||||
];
|
||||
}
|
||||
|
||||
// 2. 计算每个客户的R值(最近消费天数)
|
||||
$customerData = [];
|
||||
foreach ($customers as $customer) {
|
||||
$recencyDays = floor(($endTime - $customer['lastOrderTime']) / (24 * 3600));
|
||||
$customerData[] = [
|
||||
'identifier' => $customer['identifier'],
|
||||
'R' => $recencyDays,
|
||||
'F' => (int)$customer['orderCount'],
|
||||
'M' => (float)$customer['totalAmount'],
|
||||
];
|
||||
}
|
||||
|
||||
// 3. 异常值处理 - 剔除大额异常订单
|
||||
$mValues = array_column($customerData, 'M');
|
||||
if (!empty($mValues)) {
|
||||
sort($mValues);
|
||||
$m99Percentile = $this->percentile($mValues, 0.99);
|
||||
$abnormalThreshold = $m99Percentile * $abnormalMoneyRatio;
|
||||
|
||||
// 标记异常客户(但不删除,仅在计算M维度区间时考虑)
|
||||
foreach ($customerData as &$customer) {
|
||||
$customer['isAbnormal'] = $customer['M'] > $abnormalThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 使用五分位法计算各维度的区间阈值
|
||||
$rThresholds = $this->calculatePercentiles(array_column($customerData, 'R'), true); // R是反向的
|
||||
$fThresholds = $this->calculatePercentiles(array_column($customerData, 'F'), false);
|
||||
// M维度排除异常值计算区间
|
||||
$mValuesForPercentile = array_filter(array_column($customerData, 'M'), function($m) use ($abnormalThreshold) {
|
||||
return isset($abnormalThreshold) ? $m <= $abnormalThreshold : true;
|
||||
});
|
||||
$mThresholds = $this->calculatePercentiles(array_values($mValuesForPercentile), false);
|
||||
|
||||
// 5. 计算每个客户的RFM分项得分
|
||||
$results = [];
|
||||
foreach ($customerData as $customer) {
|
||||
$rScore = $this->scoreByPercentile($customer['R'], $rThresholds, true); // R是反向的
|
||||
$fScore = $this->scoreByPercentile($customer['F'], $fThresholds, false);
|
||||
$mScore = $customer['isAbnormal'] ? 5 : $this->scoreByPercentile($customer['M'], $mThresholds, false); // 异常值给最高分
|
||||
|
||||
// 计算RFM总分(加权求和)
|
||||
$rfmScore = $rScore * $weightR + $fScore * $weightF + $mScore * $weightM;
|
||||
|
||||
// 可选:标准化为1-100分
|
||||
$standardScore = null;
|
||||
if ($scoreScale == 100) {
|
||||
$rfmMin = $weightR * 1 + $weightF * 1 + $weightM * 1;
|
||||
$rfmMax = $weightR * 5 + $weightF * 5 + $weightM * 5;
|
||||
$standardScore = (int)round(($rfmScore - $rfmMin) / ($rfmMax - $rfmMin) * 99 + 1);
|
||||
}
|
||||
|
||||
$results[] = [
|
||||
'identifier' => $customer['identifier'],
|
||||
'R_raw' => $customer['R'],
|
||||
'R_score' => $rScore,
|
||||
'F_raw' => $customer['F'],
|
||||
'F_score' => $fScore,
|
||||
'M_raw' => round($customer['M'], 2),
|
||||
'M_score' => $mScore,
|
||||
'RFM_score' => round($rfmScore, 2),
|
||||
'RFM_standard_score' => $standardScore,
|
||||
'cycle_start' => date('Y-m-d H:i:s', $startTime),
|
||||
'cycle_end' => date('Y-m-d H:i:s', $endTime),
|
||||
'calculate_time' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
}
|
||||
|
||||
// 按RFM总分降序排序
|
||||
usort($results, function($a, $b) {
|
||||
return $b['RFM_score'] <=> $a['RFM_score'];
|
||||
});
|
||||
|
||||
// 6. 更新 ck_traffic_source 和 s2_wechat_friend 表的RFM值
|
||||
$this->updateRfmToTables($results, $ownerWechatId);
|
||||
|
||||
return [
|
||||
'code' => 200,
|
||||
'msg' => '计算成功',
|
||||
'data' => [
|
||||
'results' => $results,
|
||||
'config' => [
|
||||
'cycle_days' => $cycleDays,
|
||||
'weight_R' => $weightR,
|
||||
'weight_F' => $weightF,
|
||||
'weight_M' => $weightM,
|
||||
'score_scale' => $scoreScale,
|
||||
],
|
||||
'statistics' => [
|
||||
'total_customers' => count($results),
|
||||
'avg_rfm_score' => round(array_sum(array_column($results, 'RFM_score')) / count($results), 2),
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return [
|
||||
'code' => 500,
|
||||
'msg' => '计算失败:' . $e->getMessage(),
|
||||
'data' => []
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算 RFM 评分(兼容旧方法,使用固定阈值)
|
||||
* @param int|null $recencyDays 最近购买天数
|
||||
* @param int $frequency 购买次数
|
||||
* @param float $monetary 购买金额
|
||||
* @return array{R:int,F:int,M:int}
|
||||
*/
|
||||
public static function calcRfmScores($recencyDays = 30, $frequency, $monetary)
|
||||
{
|
||||
$recencyDays = is_numeric($recencyDays) ? (int)$recencyDays : 9999;
|
||||
$frequency = max(0, (int)$frequency);
|
||||
$monetary = max(0, (float)$monetary);
|
||||
return [
|
||||
'R' => self::scoreR_Default($recencyDays),
|
||||
'F' => self::scoreF_Default($frequency),
|
||||
'M' => self::scoreM_Default($monetary),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用固定阈值计算R得分(保留兼容性)
|
||||
*/
|
||||
protected static function scoreR_Default(int $days): int
|
||||
{
|
||||
if ($days <= 30) return 5;
|
||||
if ($days <= 60) return 4;
|
||||
if ($days <= 90) return 3;
|
||||
if ($days <= 120) return 2;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用固定阈值计算F得分(保留兼容性)
|
||||
*/
|
||||
protected static function scoreF_Default(int $times): int
|
||||
{
|
||||
if ($times >= 10) return 5;
|
||||
if ($times >= 6) return 4;
|
||||
if ($times >= 3) return 3;
|
||||
if ($times >= 2) return 2;
|
||||
if ($times >= 1) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用固定阈值计算M得分(保留兼容性)
|
||||
*/
|
||||
protected static function scoreM_Default(float $amount): int
|
||||
{
|
||||
if ($amount >= 2000) return 5;
|
||||
if ($amount >= 1000) return 4;
|
||||
if ($amount >= 500) return 3;
|
||||
if ($amount >= 200) return 2;
|
||||
if ($amount > 0) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算百分位数(五分位法)
|
||||
* @param array $values 数值数组
|
||||
* @param bool $reverse 是否反向(R维度需要反向,值越小得分越高)
|
||||
* @return array 返回[0.2, 0.4, 0.6, 0.8]分位数的阈值数组
|
||||
*/
|
||||
private function calculatePercentiles($values, $reverse = false)
|
||||
{
|
||||
if (empty($values)) {
|
||||
return [0, 0, 0, 0];
|
||||
}
|
||||
|
||||
// 去重并排序
|
||||
$uniqueValues = array_unique($values);
|
||||
sort($uniqueValues);
|
||||
|
||||
// 如果所有值相同,强制均分5个区间
|
||||
if (count($uniqueValues) == 1) {
|
||||
$singleValue = $uniqueValues[0];
|
||||
if ($reverse) {
|
||||
return [$singleValue, $singleValue, $singleValue, $singleValue];
|
||||
} else {
|
||||
return [$singleValue, $singleValue, $singleValue, $singleValue];
|
||||
}
|
||||
}
|
||||
|
||||
$percentiles = [0.2, 0.4, 0.6, 0.8];
|
||||
$thresholds = [];
|
||||
|
||||
foreach ($percentiles as $p) {
|
||||
$thresholds[] = $this->percentile($uniqueValues, $p);
|
||||
}
|
||||
|
||||
return $thresholds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算百分位数
|
||||
* @param array $sortedArray 已排序的数组
|
||||
* @param float $percentile 百分位数(0-1之间)
|
||||
* @return float
|
||||
*/
|
||||
private function percentile($sortedArray, $percentile)
|
||||
{
|
||||
if (empty($sortedArray)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$count = count($sortedArray);
|
||||
$index = ($count - 1) * $percentile;
|
||||
$floor = floor($index);
|
||||
$ceil = ceil($index);
|
||||
|
||||
if ($floor == $ceil) {
|
||||
return $sortedArray[(int)$index];
|
||||
}
|
||||
|
||||
$weight = $index - $floor;
|
||||
return $sortedArray[(int)$floor] * (1 - $weight) + $sortedArray[(int)$ceil] * $weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据五分位法阈值计算得分
|
||||
* @param float $value 当前值
|
||||
* @param array $thresholds 阈值数组[T1, T2, T3, T4]
|
||||
* @param bool $reverse 是否反向(R维度反向:值越小得分越高)
|
||||
* @return int 得分1-5
|
||||
*/
|
||||
private function scoreByPercentile($value, $thresholds, $reverse = false)
|
||||
{
|
||||
if (empty($thresholds) || count($thresholds) < 4) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
list($t1, $t2, $t3, $t4) = $thresholds;
|
||||
|
||||
if ($reverse) {
|
||||
// R维度:值越小得分越高
|
||||
if ($value <= $t1) return 5;
|
||||
if ($value <= $t2) return 4;
|
||||
if ($value <= $t3) return 3;
|
||||
if ($value <= $t4) return 2;
|
||||
return 1;
|
||||
} else {
|
||||
// F和M维度:值越大得分越高
|
||||
if ($value >= $t4) return 5;
|
||||
if ($value >= $t3) return 4;
|
||||
if ($value >= $t2) return 3;
|
||||
if ($value >= $t1) return 2;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新RFM值到 ck_traffic_source 和 s2_wechat_friend 表
|
||||
*
|
||||
* @param array $results RFM计算结果数组
|
||||
* @param string|null $ownerWechatId 微信ID,用于过滤更新范围
|
||||
*/
|
||||
private function updateRfmToTables($results, $ownerWechatId = null)
|
||||
{
|
||||
try {
|
||||
foreach ($results as $result) {
|
||||
$identifier = $result['identifier'];
|
||||
$rScore = (string)$result['R_score'];
|
||||
$fScore = (string)$result['F_score'];
|
||||
$mScore = (string)$result['M_score'];
|
||||
|
||||
// 更新 ck_traffic_source 表
|
||||
// 根据 identifier 更新所有匹配的记录
|
||||
$trafficSourceUpdate = [
|
||||
'R' => $rScore,
|
||||
'F' => $fScore,
|
||||
'M' => $mScore,
|
||||
'updateTime' => time()
|
||||
];
|
||||
TrafficSource::where('identifier', $identifier)->update($trafficSourceUpdate);
|
||||
|
||||
// 更新 s2_wechat_friend 表
|
||||
// wechatId 对应 identifier
|
||||
$wechatFriendUpdate = [
|
||||
'R' => $rScore,
|
||||
'F' => $fScore,
|
||||
'M' => $mScore,
|
||||
'updateTime' => time()
|
||||
];
|
||||
$wechatFriendWhere = ['wechatId' => $identifier];
|
||||
if (!empty($ownerWechatId)) {
|
||||
$wechatFriendWhere['ownerWechatId'] = $ownerWechatId;
|
||||
}
|
||||
WechatFriendModel::where($wechatFriendWhere)->update($wechatFriendUpdate);
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// 记录错误但不影响主流程
|
||||
\think\Log::error('更新RFM值失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
438
application/cunkebao/controller/StatsController.php
Normal file
438
application/cunkebao/controller/StatsController.php
Normal file
@@ -0,0 +1,438 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller;
|
||||
|
||||
use think\Db;
|
||||
use think\Controller;
|
||||
|
||||
class StatsController extends Controller
|
||||
{
|
||||
|
||||
|
||||
const WEEK = [
|
||||
0 => '周日',
|
||||
1 => '周一',
|
||||
2 => '周二',
|
||||
3 => '周三',
|
||||
4 => '周四',
|
||||
5 => '周五',
|
||||
6 => '周六',
|
||||
];
|
||||
|
||||
/**
|
||||
* 基础信息
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function baseInfoStats()
|
||||
{
|
||||
|
||||
$where = [
|
||||
['departmentId','=',$this->request->userInfo['companyId']]
|
||||
];
|
||||
if (empty($this->request->userInfo['isAdmin'])){
|
||||
$where[] = ['id','=',$this->request->userInfo['s2_accountId']];
|
||||
}
|
||||
$accounts = Db::table('s2_company_account')->where($where)->column('id');
|
||||
|
||||
$deviceNum = Db::table('s2_device')->whereIn('currentAccountId',$accounts)->where(['isDeleted' => 0])->count();
|
||||
$wechatNum = Db::table('s2_wechat_account')->whereIn('deviceAccountId',$accounts)->count();
|
||||
$aliveWechatNum = Db::table('s2_wechat_account')->whereIn('deviceAccountId',$accounts)->where(['wechatAlive' => 1])->count();
|
||||
$data = [
|
||||
'deviceNum' => $deviceNum,
|
||||
'wechatNum' => $wechatNum,
|
||||
'aliveWechatNum' => $aliveWechatNum,
|
||||
];
|
||||
return successJson($data, '获取成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景获客统计
|
||||
* @return \think\response\Json
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @throws \think\exception\DbException
|
||||
*/
|
||||
public function planStats()
|
||||
{
|
||||
|
||||
$num = $this->request->param('num', 4);
|
||||
$planScene = Db::name('plan_scene')
|
||||
->field('id,name,image')
|
||||
->where(['status' => 1])
|
||||
->order('sort DESC')
|
||||
->page(1, $num)
|
||||
->select();
|
||||
|
||||
if (empty($planScene)) {
|
||||
return successJson([], '获取成功');
|
||||
}
|
||||
|
||||
$sceneIds = array_column($planScene, 'id');
|
||||
$companyId = $this->request->userInfo['companyId'];
|
||||
|
||||
$stats = Db::name('customer_acquisition_task')->alias('ac')
|
||||
->join('task_customer tc', 'tc.task_id = ac.id')
|
||||
->where([
|
||||
['ac.companyId', '=', $companyId],
|
||||
['ac.deleteTime', '=', 0],
|
||||
['ac.sceneId', 'in', $sceneIds],
|
||||
])
|
||||
->field([
|
||||
'ac.sceneId',
|
||||
Db::raw('COUNT(1) as allNum'),
|
||||
Db::raw("SUM(CASE WHEN tc.status IN (1,2,3,4) THEN 1 ELSE 0 END) as addNum"),
|
||||
Db::raw("SUM(CASE WHEN tc.status = 4 THEN 1 ELSE 0 END) as passNum"),
|
||||
])
|
||||
->group('ac.sceneId')
|
||||
->select();
|
||||
|
||||
$statsMap = [];
|
||||
foreach ($stats as $row) {
|
||||
$sceneId = is_array($row) ? ($row['sceneId'] ?? 0) : ($row->sceneId ?? 0);
|
||||
if (!$sceneId) {
|
||||
continue;
|
||||
}
|
||||
$statsMap[$sceneId] = [
|
||||
'allNum' => (int)(is_array($row) ? ($row['allNum'] ?? 0) : ($row->allNum ?? 0)),
|
||||
'addNum' => (int)(is_array($row) ? ($row['addNum'] ?? 0) : ($row->addNum ?? 0)),
|
||||
'passNum' => (int)(is_array($row) ? ($row['passNum'] ?? 0) : ($row->passNum ?? 0)),
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($planScene as &$item) {
|
||||
$sceneStats = $statsMap[$item['id']] ?? ['allNum' => 0, 'addNum' => 0, 'passNum' => 0];
|
||||
$item['allNum'] = $sceneStats['allNum'];
|
||||
$item['addNum'] = $sceneStats['addNum'];
|
||||
$item['passNum'] = $sceneStats['passNum'];
|
||||
}
|
||||
unset($item);
|
||||
|
||||
return successJson($planScene, '获取成功');
|
||||
}
|
||||
|
||||
|
||||
public function todayStats()
|
||||
{
|
||||
$date = date('Y-m-d',time());
|
||||
$start = strtotime($date . ' 00:00:00');
|
||||
$end = strtotime($date . ' 23:59:59');
|
||||
$companyId = $this->request->userInfo['companyId'];
|
||||
|
||||
|
||||
$momentsNum = Db::name('workbench')->alias('w')
|
||||
->join('workbench_moments_sync_item wi', 'w.id = wi.workbenchId')
|
||||
->where(['w.companyId' => $companyId])
|
||||
->where('wi.createTime', 'between', [$start, $end])
|
||||
->count();
|
||||
|
||||
$groupPushNum = Db::name('workbench')->alias('w')
|
||||
->join('workbench_group_push_item wi', 'w.id = wi.workbenchId')
|
||||
->where(['w.companyId' => $companyId])
|
||||
->where('wi.createTime', 'between', [$start, $end])
|
||||
->count();
|
||||
|
||||
|
||||
$addNum = Db::name('customer_acquisition_task')->alias('ac')
|
||||
->join('task_customer tc', 'tc.task_id = ac.id')
|
||||
->where(['ac.companyId' => $companyId, 'ac.deleteTime' => 0])
|
||||
->where('tc.updateTime', 'between', [$start, $end])
|
||||
->whereIn('tc.status', [1, 2, 3, 4])
|
||||
->count();
|
||||
|
||||
// 通过量
|
||||
$passNum = Db::name('customer_acquisition_task')->alias('ac')
|
||||
->join('task_customer tc', 'tc.task_id = ac.id')
|
||||
->where(['ac.companyId' => $companyId, 'ac.deleteTime' => 0])
|
||||
->where('tc.updateTime', 'between', [$start, $end])
|
||||
->whereIn('tc.status', [4])
|
||||
->count();
|
||||
|
||||
if (!empty($passNum)){
|
||||
$passRate = number_format(($addNum / $passNum) * 100,2) ;
|
||||
}else{
|
||||
$passRate = '0%';
|
||||
}
|
||||
|
||||
$sysActive = '90%';
|
||||
$data = [
|
||||
'momentsNum' => $momentsNum,
|
||||
'groupPushNum' => $groupPushNum,
|
||||
'addNum' => $addNum,
|
||||
'passNum' => $passNum,
|
||||
'passRate' => $passRate,
|
||||
'sysActive' => $sysActive,
|
||||
];
|
||||
return successJson($data, '获取成功');
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 近7天获客统计
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function customerAcquisitionStats7Days()
|
||||
{
|
||||
$companyId = $this->request->userInfo['companyId'];
|
||||
$days = 7;
|
||||
|
||||
$endTime = strtotime(date('Y-m-d 23:59:59'));
|
||||
$startTime = strtotime(date('Y-m-d 00:00:00', strtotime('-' . ($days - 1) . ' day')));
|
||||
|
||||
$dateMap = [];
|
||||
$dateLabels = [];
|
||||
for ($i = 0; $i < $days; $i++) {
|
||||
$currentDate = date('Y-m-d', strtotime("-" . ($days - 1 - $i) . " day"));
|
||||
$weekIndex = date("w", strtotime($currentDate));
|
||||
$dateMap[$currentDate] = self::WEEK[$weekIndex];
|
||||
$dateLabels[] = self::WEEK[$weekIndex];
|
||||
}
|
||||
|
||||
$baseWhere = [
|
||||
['ac.companyId', '=', $companyId],
|
||||
['ac.deleteTime', '=', 0],
|
||||
];
|
||||
|
||||
$fetchCounts = function (string $timeField, array $status = []) use ($baseWhere, $startTime, $endTime) {
|
||||
$query = Db::name('customer_acquisition_task')->alias('ac')
|
||||
->join('task_customer tc', 'tc.task_id = ac.id')
|
||||
->where($baseWhere)
|
||||
->whereBetween('tc.' . $timeField, [$startTime, $endTime]);
|
||||
if (!empty($status)) {
|
||||
$query->whereIn('tc.status', $status);
|
||||
}
|
||||
$rows = $query->field([
|
||||
"FROM_UNIXTIME(tc.{$timeField}, '%Y-%m-%d')" => 'day',
|
||||
'COUNT(1)' => 'total'
|
||||
])->group('day')->select();
|
||||
|
||||
$result = [];
|
||||
foreach ($rows as $row) {
|
||||
$day = is_array($row) ? ($row['day'] ?? '') : ($row->day ?? '');
|
||||
$total = (int)(is_array($row) ? ($row['total'] ?? 0) : ($row->total ?? 0));
|
||||
if ($day) {
|
||||
$result[$day] = $total;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
};
|
||||
|
||||
$allNumDict = $fetchCounts('createTime');
|
||||
$addNumDict = $fetchCounts('updateTime', [1, 2, 3, 4]);
|
||||
$passNumDict = $fetchCounts('updateTime', [4]);
|
||||
|
||||
$allNum = [];
|
||||
$addNum = [];
|
||||
$passNum = [];
|
||||
foreach (array_keys($dateMap) as $dateKey) {
|
||||
$allNum[] = $allNumDict[$dateKey] ?? 0;
|
||||
$addNum[] = $addNumDict[$dateKey] ?? 0;
|
||||
$passNum[] = $passNumDict[$dateKey] ?? 0;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'date' => $dateLabels,
|
||||
'allNum' => $allNum,
|
||||
'addNum' => $addNum,
|
||||
'passNum' => $passNum,
|
||||
];
|
||||
|
||||
return successJson($data, '获取成功');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 场景获客数据统计
|
||||
* @return \think\response\Json
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @throws \think\exception\DbException
|
||||
*/
|
||||
public function getFriendRequestTaskStats()
|
||||
{
|
||||
$companyId = $this->request->userInfo['companyId'];
|
||||
$taskId = $this->request->param('taskId', '');
|
||||
if(empty($taskId)){
|
||||
return errorJson('任务id不能为空');
|
||||
}
|
||||
|
||||
$task = Db::name('customer_acquisition_task')->where(['id' => $taskId, 'companyId' => $companyId,'deleteTime' => 0])->find();
|
||||
if(empty($task)){
|
||||
return errorJson('任务不存在或已删除');
|
||||
}
|
||||
|
||||
|
||||
// 1. 获取startTime和endTime,格式是日期
|
||||
$startTime = $this->request->param('startTime', '');
|
||||
$endTime = $this->request->param('endTime', '');
|
||||
|
||||
// 如果获取不到则默认为7天的跨度
|
||||
if (empty($startTime)) {
|
||||
$startTime = date('Y-m-d', time() - 86400 * 6);
|
||||
}
|
||||
if (empty($endTime)) {
|
||||
$endTime = date('Y-m-d', time());
|
||||
}
|
||||
|
||||
// 转换成时间戳格式
|
||||
$startTimestamp = strtotime($startTime . ' 00:00:00');
|
||||
$endTimestamp = strtotime($endTime . ' 23:59:59');
|
||||
|
||||
// 同时生成日期数组和时间戳二维数组
|
||||
$dateArray = [];
|
||||
$timestampArray = [];
|
||||
$currentTimestamp = $startTimestamp;
|
||||
|
||||
while ($currentTimestamp <= $endTimestamp) {
|
||||
// 生成日期格式数组
|
||||
$dateArray[] = date('m-d', $currentTimestamp);
|
||||
|
||||
// 生成时间戳二维数组
|
||||
$dayStart = $currentTimestamp;
|
||||
$dayEnd = strtotime('+1 day', $currentTimestamp) - 1; // 23:59:59
|
||||
$timestampArray[] = [$dayStart, $dayEnd];
|
||||
|
||||
$currentTimestamp = strtotime('+1 day', $currentTimestamp);
|
||||
}
|
||||
|
||||
|
||||
// 使用分组聚合统计,减少 SQL 次数
|
||||
$allRows = Db::name('task_customer')
|
||||
->field("FROM_UNIXTIME(createTime, '%m-%d') AS d, COUNT(*) AS c")
|
||||
->where(['task_id' => $taskId])
|
||||
->where('createTime', 'between', [$startTimestamp, $endTimestamp])
|
||||
->group('d')
|
||||
->select();
|
||||
|
||||
$successRows = Db::name('task_customer')
|
||||
->field("FROM_UNIXTIME(addTime, '%m-%d') AS d, COUNT(*) AS c")
|
||||
->where(['task_id' => $taskId])
|
||||
->where('addTime', 'between', [$startTimestamp, $endTimestamp])
|
||||
->whereIn('status', [1, 2, 4, 5])
|
||||
->group('d')
|
||||
->select();
|
||||
|
||||
$passRows = Db::name('task_customer')
|
||||
->field("FROM_UNIXTIME(passTime, '%m-%d') AS d, COUNT(*) AS c")
|
||||
->where(['task_id' => $taskId])
|
||||
->where('passTime', 'between', [$startTimestamp, $endTimestamp])
|
||||
->group('d')
|
||||
->select();
|
||||
|
||||
$errorRows = Db::name('task_customer')
|
||||
->field("FROM_UNIXTIME(updateTime, '%m-%d') AS d, COUNT(*) AS c")
|
||||
->where(['task_id' => $taskId, 'status' => 3])
|
||||
->where('updateTime', 'between', [$startTimestamp, $endTimestamp])
|
||||
->group('d')
|
||||
->select();
|
||||
|
||||
// 将分组结果映射到连续日期数组
|
||||
$mapToSeries = function(array $rows) use ($dateArray) {
|
||||
$dict = [];
|
||||
foreach ($rows as $row) {
|
||||
// 兼容对象/数组两种返回
|
||||
$d = is_array($row) ? ($row['d'] ?? '') : ($row->d ?? '');
|
||||
$c = (int)(is_array($row) ? ($row['c'] ?? 0) : ($row->c ?? 0));
|
||||
if ($d !== '') {
|
||||
$dict[$d] = $c;
|
||||
}
|
||||
}
|
||||
$series = [];
|
||||
foreach ($dateArray as $d) {
|
||||
$series[] = $dict[$d] ?? 0;
|
||||
}
|
||||
return $series;
|
||||
};
|
||||
|
||||
$allNumArray = $mapToSeries($allRows);
|
||||
$successNumArray = $mapToSeries($successRows);
|
||||
$passNumArray = $mapToSeries($passRows);
|
||||
$errorNumArray = $mapToSeries($errorRows);
|
||||
|
||||
// 计算通过率和成功率
|
||||
$passRateArray = [];
|
||||
$successRateArray = [];
|
||||
|
||||
for ($i = 0; $i < count($dateArray); $i++) {
|
||||
// 通过率 = 通过数 / 总数
|
||||
$passRate = ($allNumArray[$i] > 0) ? round(($passNumArray[$i] / $allNumArray[$i]) * 100, 2) : 0;
|
||||
$passRateArray[] = $passRate;
|
||||
|
||||
// 成功率 = 成功数 / 总数
|
||||
$successRate = ($allNumArray[$i] > 0) ? round(($successNumArray[$i] / $allNumArray[$i]) * 100, 2) : 0;
|
||||
$successRateArray[] = $successRate;
|
||||
}
|
||||
|
||||
// 计算总体统计
|
||||
$totalAll = array_sum($allNumArray);
|
||||
$totalSuccess = array_sum($successNumArray);
|
||||
$totalPass = array_sum($passNumArray);
|
||||
$totalError = array_sum($errorNumArray);
|
||||
|
||||
$totalPassRate = ($totalAll > 0) ? round(($totalPass / $totalAll) * 100, 2) : 0;
|
||||
$totalSuccessRate = ($totalAll > 0) ? round(($totalSuccess / $totalAll) * 100, 2) : 0;
|
||||
|
||||
// 返回结果
|
||||
$result = [
|
||||
'startTime' => $startTime,
|
||||
'endTime' => $endTime,
|
||||
'dateArray' => $dateArray,
|
||||
'allNumArray' => $allNumArray,
|
||||
'successNumArray' => $successNumArray,
|
||||
'passNumArray' => $passNumArray,
|
||||
'errorNumArray' => $errorNumArray,
|
||||
'passRateArray' => $passRateArray,
|
||||
'successRateArray' => $successRateArray,
|
||||
'totalStats' => [
|
||||
'totalAll' => $totalAll,
|
||||
'totalSuccess' => $totalSuccess,
|
||||
'totalPass' => $totalPass,
|
||||
'totalError' => $totalError,
|
||||
'totalPassRate' => $totalPassRate,
|
||||
'totalSuccessRate' => $totalSuccessRate
|
||||
]
|
||||
];
|
||||
|
||||
return successJson($result, '获取成功');
|
||||
}
|
||||
|
||||
|
||||
public function userInfoStats()
|
||||
{
|
||||
$companyId = $this->request->userInfo['companyId'];
|
||||
$userId = $this->request->userInfo['id'];
|
||||
$isAdmin = $this->request->userInfo['isAdmin'];
|
||||
|
||||
|
||||
$where = [
|
||||
['departmentId','=',$companyId]
|
||||
];
|
||||
if (empty($this->request->userInfo['isAdmin'])){
|
||||
$where[] = ['id','=',$this->request->userInfo['s2_accountId']];
|
||||
}
|
||||
$accounts = Db::table('s2_company_account')->where($where)->column('id');
|
||||
|
||||
|
||||
$userNum = Db::table('s2_wechat_friend')->whereIn('accountId',$accounts)->where(['isDeleted' => 0])->count();
|
||||
$deviceNum = Db::table('s2_device')->whereIn('currentAccountId',$accounts)->where(['isDeleted' => 0])->count();
|
||||
$wechatNum = Db::table('s2_wechat_account')->whereIn('deviceAccountId',$accounts)->count();
|
||||
|
||||
|
||||
$contentLibrary = Db::name('content_library')->where(['companyId' => $companyId,'isDel' => 0]);
|
||||
if(empty($isAdmin)){
|
||||
$contentLibrary = $contentLibrary->where(['userId' => $userId]);
|
||||
}
|
||||
$contentLibraryNum = $contentLibrary->count();
|
||||
|
||||
|
||||
$data = [
|
||||
'deviceNum' => $deviceNum,
|
||||
'wechatNum' => $wechatNum,
|
||||
'contentLibraryNum' => $contentLibraryNum,
|
||||
'userNum' => $userNum,
|
||||
];
|
||||
return successJson($data, '获取成功');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
404
application/cunkebao/controller/StoreAccountController.php
Normal file
404
application/cunkebao/controller/StoreAccountController.php
Normal file
@@ -0,0 +1,404 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller;
|
||||
|
||||
use app\common\model\Device;
|
||||
use app\common\model\DeviceUser;
|
||||
use app\common\model\User;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 门店端账号管理控制器
|
||||
*/
|
||||
class StoreAccountController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 创建账号
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
try {
|
||||
// 获取参数
|
||||
$account = $this->request->param('account', '');
|
||||
$username = $this->request->param('username', '');
|
||||
$phone = $this->request->param('phone', '');
|
||||
$password = $this->request->param('password', '');
|
||||
$deviceId = $this->request->param('deviceId', 0);
|
||||
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
// 参数验证
|
||||
if (empty($account)) {
|
||||
return ResponseHelper::error('账号不能为空');
|
||||
}
|
||||
if (empty($username)) {
|
||||
return ResponseHelper::error('昵称不能为空');
|
||||
}
|
||||
if (empty($phone)) {
|
||||
return ResponseHelper::error('手机号不能为空');
|
||||
}
|
||||
if (!preg_match('/^1[3-9]\d{9}$/', $phone)) {
|
||||
return ResponseHelper::error('手机号格式不正确');
|
||||
}
|
||||
if (empty($password)) {
|
||||
return ResponseHelper::error('密码不能为空');
|
||||
}
|
||||
if (strlen($password) < 6 || strlen($password) > 20) {
|
||||
return ResponseHelper::error('密码长度必须在6-20个字符之间');
|
||||
}
|
||||
if (empty($deviceId)) {
|
||||
return ResponseHelper::error('请选择设备');
|
||||
}
|
||||
|
||||
// 检查账号是否已存在(同一 typeId 和 companyId 下不能重复)
|
||||
$existUser = Db::name('users')->where(['account' => $account, 'companyId' => $companyId, 'typeId' => 2, 'deleteTime' => 0])
|
||||
->find();
|
||||
if ($existUser) {
|
||||
return ResponseHelper::error('账号已存在');
|
||||
}
|
||||
|
||||
// 检查手机号是否已存在(同一 typeId 和 companyId 下不能重复)
|
||||
$existPhone = Db::name('users')->where(['phone' => $phone, 'companyId' => $companyId, 'typeId' => 2, 'deleteTime' => 0])
|
||||
->find();
|
||||
if ($existPhone) {
|
||||
return ResponseHelper::error('手机号已被使用');
|
||||
}
|
||||
|
||||
// 检查设备是否存在且属于当前公司
|
||||
$device = Device::where('id', $deviceId)
|
||||
->where('companyId', $companyId)
|
||||
->find();
|
||||
if (!$device) {
|
||||
return ResponseHelper::error('设备不存在或没有权限');
|
||||
}
|
||||
|
||||
// 开始事务
|
||||
Db::startTrans();
|
||||
try {
|
||||
// 创建用户
|
||||
$userData = [
|
||||
'account' => $account,
|
||||
'username' => $username,
|
||||
'phone' => $phone,
|
||||
'passwordMd5' => md5($password),
|
||||
'passwordLocal' => localEncrypt($password),
|
||||
'avatar' => '',
|
||||
'isAdmin' => 0,
|
||||
'companyId' => $companyId,
|
||||
'typeId' => 2, // 门店端固定为2
|
||||
'status' => 1, // 默认可用
|
||||
'balance' => 0,
|
||||
'tokens' => 0,
|
||||
'createTime' => time(),
|
||||
];
|
||||
|
||||
$userId = Db::name('users')->insertGetId($userData);
|
||||
|
||||
// 绑定设备
|
||||
Db::name('device_user')->insert([
|
||||
'companyId' => $companyId,
|
||||
'userId' => $userId,
|
||||
'deviceId' => $deviceId,
|
||||
'deleteTime' => 0,
|
||||
]);
|
||||
|
||||
// 提交事务
|
||||
Db::commit();
|
||||
|
||||
return ResponseHelper::success('创建账号成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
throw $e;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode() ?: 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑账号
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
try {
|
||||
$userId = $this->request->param('userId', 0);
|
||||
$account = $this->request->param('account', '');
|
||||
$username = $this->request->param('username', '');
|
||||
$phone = $this->request->param('phone', '');
|
||||
$password = $this->request->param('password', '');
|
||||
$deviceId = $this->request->param('deviceId', 0);
|
||||
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
// 参数验证
|
||||
if (empty($userId)) {
|
||||
return ResponseHelper::error('用户ID不能为空');
|
||||
}
|
||||
|
||||
// 检查用户是否存在且属于当前公司
|
||||
$user = Db::name('users')->where(['id' => $userId, 'companyId' => $companyId, 'typeId' => 2])->find();
|
||||
if (!$user) {
|
||||
return ResponseHelper::error('用户不存在或没有权限');
|
||||
}
|
||||
|
||||
$updateData = [];
|
||||
|
||||
// 更新账号
|
||||
if (!empty($account)) {
|
||||
// 检查账号是否已被其他用户使用(同一 typeId 下)
|
||||
$existUser = Db::name('users')->where(['account' => $account, 'companyId' => $companyId, 'typeId' => 2, 'deleteTime' => 0])
|
||||
->where('id', '<>', $userId)
|
||||
->find();
|
||||
if ($existUser) {
|
||||
return ResponseHelper::error('账号已被使用');
|
||||
}
|
||||
$updateData['account'] = $account;
|
||||
}
|
||||
|
||||
// 更新昵称
|
||||
if (!empty($username)) {
|
||||
$updateData['username'] = $username;
|
||||
}
|
||||
|
||||
// 更新手机号
|
||||
if (!empty($phone)) {
|
||||
if (!preg_match('/^1[3-9]\d{9}$/', $phone)) {
|
||||
return ResponseHelper::error('手机号格式不正确');
|
||||
}
|
||||
// 检查手机号是否已被其他用户使用(同一 typeId 下)
|
||||
$existPhone = Db::name('users')->where(['phone' => $phone, 'companyId' => $companyId, 'typeId' => 2, 'deleteTime' => 0])
|
||||
->where('id', '<>', $userId)
|
||||
->find();
|
||||
if ($existPhone) {
|
||||
return ResponseHelper::error('手机号已被使用');
|
||||
}
|
||||
$updateData['phone'] = $phone;
|
||||
}
|
||||
|
||||
// 更新密码
|
||||
if (!empty($password)) {
|
||||
if (strlen($password) < 6 || strlen($password) > 20) {
|
||||
return ResponseHelper::error('密码长度必须在6-20个字符之间');
|
||||
}
|
||||
$updateData['passwordMd5'] = md5($password);
|
||||
$updateData['passwordLocal'] = localEncrypt($password);
|
||||
}
|
||||
|
||||
// 更新设备绑定
|
||||
if (!empty($deviceId)) {
|
||||
// 检查设备是否存在且属于当前公司
|
||||
$device = Device::where('id', $deviceId)
|
||||
->where('companyId', $companyId)
|
||||
->find();
|
||||
if (!$device) {
|
||||
return ResponseHelper::error('设备不存在或没有权限');
|
||||
}
|
||||
}
|
||||
|
||||
// 开始事务
|
||||
Db::startTrans();
|
||||
try {
|
||||
// 更新用户信息
|
||||
if (!empty($updateData)) {
|
||||
$updateData['updateTime'] = time();
|
||||
Db::name('users')->where(['id' => $userId])->update($updateData);
|
||||
}
|
||||
|
||||
// 更新设备绑定
|
||||
if (!empty($deviceId)) {
|
||||
// 删除旧的设备绑定
|
||||
Db::name('device_user')->where(['userId' => $userId, 'companyId' => $companyId])->delete();
|
||||
|
||||
// 添加新的设备绑定
|
||||
Db::name('device_user')->insert([
|
||||
'companyId' => $companyId,
|
||||
'userId' => $userId,
|
||||
'deviceId' => $deviceId,
|
||||
'deleteTime' => 0,
|
||||
]);
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
Db::commit();
|
||||
|
||||
return ResponseHelper::success('更新账号成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
throw $e;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode() ?: 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除账号
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
try {
|
||||
$userId = $this->request->param('userId', 0);
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($userId)) {
|
||||
return ResponseHelper::error('用户ID不能为空');
|
||||
}
|
||||
|
||||
// 检查用户是否存在且属于当前公司
|
||||
$user = Db::name('users')->where(['id' => $userId, 'companyId' => $companyId, 'typeId' => 2])->find();
|
||||
if (!$user) {
|
||||
return ResponseHelper::error('用户不存在或没有权限');
|
||||
}
|
||||
|
||||
// 检查是否是管理账号
|
||||
if ($user['isAdmin'] == 1) {
|
||||
return ResponseHelper::error('管理账号无法删除');
|
||||
}
|
||||
|
||||
// 软删除用户
|
||||
Db::name('users')->where(['id' => $userId])->update([
|
||||
'deleteTime' => time(),
|
||||
'updateTime' => time()
|
||||
]);
|
||||
|
||||
// 软删除设备绑定关系
|
||||
Db::name('device_user')->where(['userId' => $userId, 'companyId' => $companyId])->update([
|
||||
'deleteTime' => time()
|
||||
]);
|
||||
|
||||
return ResponseHelper::success('删除账号成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode() ?: 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用/启用账号
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function disable()
|
||||
{
|
||||
try {
|
||||
$userId = $this->request->param('userId', 0);
|
||||
$status = $this->request->param('status', -1); // 0-禁用 1-启用
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($userId)) {
|
||||
return ResponseHelper::error('用户ID不能为空');
|
||||
}
|
||||
|
||||
if ($status != 0 && $status != 1) {
|
||||
return ResponseHelper::error('状态参数错误');
|
||||
}
|
||||
|
||||
// 检查用户是否存在且属于当前公司
|
||||
$user = Db::name('users')->where(['id' => $userId, 'companyId' => $companyId, 'typeId' => 2])->find();
|
||||
if (!$user) {
|
||||
return ResponseHelper::error('用户不存在或没有权限');
|
||||
}
|
||||
|
||||
// 检查是否是管理账号
|
||||
if ($user['isAdmin'] == 1 && $status == 0) {
|
||||
return ResponseHelper::error('管理账号无法禁用');
|
||||
}
|
||||
|
||||
// 更新状态
|
||||
Db::name('users')->where(['id' => $userId])->update([
|
||||
'status' => $status,
|
||||
'updateTime' => time()
|
||||
]);
|
||||
|
||||
$message = $status == 0 ? '禁用账号成功' : '启用账号成功';
|
||||
return ResponseHelper::success($message);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode() ?: 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取账号列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
$status = $this->request->param('status', '');
|
||||
$page = $this->request->param('page/d', 1);
|
||||
$limit = $this->request->param('limit/d', 10);
|
||||
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
// 构建查询条件
|
||||
$where = [
|
||||
['companyId', '=', $companyId],
|
||||
['typeId', '=', 2], // 只查询门店端账号
|
||||
['deleteTime', '=', 0]
|
||||
];
|
||||
|
||||
// 关键词搜索(账号、昵称、手机号)
|
||||
if (!empty($keyword)) {
|
||||
$where[] = ['account|username|phone', "LIKE", '%'.$keyword.'%'];
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if ($status !== '') {
|
||||
$where[] = ['status', '=', $status];
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
$query = Db::name('users')->where($where);
|
||||
$total = $query->count();
|
||||
|
||||
$list = $query->field('id,account,username,phone,avatar,isAdmin,status,balance,tokens,createTime')
|
||||
->order('id desc')
|
||||
->page($page, $limit)
|
||||
->select();
|
||||
|
||||
|
||||
// 获取每个账号绑定的设备(单个设备)
|
||||
if (!empty($list)) {
|
||||
$userIds = array_column($list, 'id');
|
||||
$deviceBindings = Db::name('device_user')
|
||||
->alias('du')
|
||||
->join('device d', 'd.id = du.deviceId', 'left')
|
||||
->where([
|
||||
['du.userId', 'in', $userIds],
|
||||
['du.companyId', '=', $companyId],
|
||||
['du.deleteTime', '=', 0]
|
||||
])
|
||||
->field('du.userId,du.deviceId,d.imei,d.memo')
|
||||
->order('du.id desc')
|
||||
->select();
|
||||
|
||||
// 组织设备数据(单个设备对象)
|
||||
$deviceMap = [];
|
||||
foreach ($deviceBindings as $binding) {
|
||||
$deviceMap[$binding['userId']] = [
|
||||
'deviceId' => $binding['deviceId'],
|
||||
'imei' => $binding['imei'],
|
||||
'memo' => $binding['memo']
|
||||
];
|
||||
}
|
||||
|
||||
// 将设备信息添加到用户数据中
|
||||
foreach ($list as &$item) {
|
||||
$item['device'] = $deviceMap[$item['id']] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
return ResponseHelper::success([
|
||||
'list' => $list,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'limit' => $limit
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode() ?: 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
369
application/cunkebao/controller/Task.php
Normal file
369
application/cunkebao/controller/Task.php
Normal file
@@ -0,0 +1,369 @@
|
||||
<?php
|
||||
namespace app\cunkebao\controller;
|
||||
|
||||
use app\cunkebao\model\PlanTask;
|
||||
use app\cunkebao\model\PlanExecution;
|
||||
use think\Controller;
|
||||
use think\facade\Log;
|
||||
use think\Request;
|
||||
|
||||
/**
|
||||
* 计划任务控制器
|
||||
*/
|
||||
class Task 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');
|
||||
|
||||
// 构建查询条件
|
||||
$where = [];
|
||||
if (!empty($keyword)) {
|
||||
$where[] = ['name', 'like', "%{$keyword}%"];
|
||||
}
|
||||
|
||||
if ($status !== '') {
|
||||
$where[] = ['status', '=', intval($status)];
|
||||
}
|
||||
|
||||
// 查询列表
|
||||
$result = PlanTask::getTaskList($where, 'id desc', $page, $limit);
|
||||
|
||||
// 查询场景和设备信息
|
||||
foreach ($result['list'] as &$task) {
|
||||
$task['scene'] = $task->scene ? $task->scene->toArray() : null;
|
||||
$task['device'] = $task->device ? $task->device->toArray() : null;
|
||||
}
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => $result
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务详情
|
||||
*
|
||||
* @param int $id
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function read($id)
|
||||
{
|
||||
$task = PlanTask::get($id, ['scene', 'device']);
|
||||
if (!$task) {
|
||||
return json([
|
||||
'code' => 404,
|
||||
'msg' => '任务不存在'
|
||||
]);
|
||||
}
|
||||
|
||||
// 获取执行记录
|
||||
$executions = PlanExecution::where('plan_id', $id)
|
||||
->order('createTime DESC')
|
||||
->select();
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'task' => $task,
|
||||
'executions' => $executions
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建任务
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$data = Request::post();
|
||||
|
||||
// 数据验证
|
||||
$validate = validate('app\cunkebao\validate\Task');
|
||||
if (!$validate->check($data)) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'msg' => $validate->getError()
|
||||
]);
|
||||
}
|
||||
|
||||
// 添加任务
|
||||
$task = new PlanTask;
|
||||
$task->save([
|
||||
'name' => $data['name'],
|
||||
'device_id' => $data['device_id'] ?? null,
|
||||
'scene_id' => $data['scene_id'] ?? null,
|
||||
'scene_config' => $data['scene_config'] ?? [],
|
||||
'status' => $data['status'] ?? 0,
|
||||
'current_step' => 0,
|
||||
'priority' => $data['priority'] ?? 5,
|
||||
'created_by' => $data['created_by'] ?? 0
|
||||
]);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '创建成功',
|
||||
'data' => $task->id
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新任务
|
||||
*
|
||||
* @param int $id
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function update($id)
|
||||
{
|
||||
$data = Request::put();
|
||||
|
||||
// 检查任务是否存在
|
||||
$task = PlanTask::get($id);
|
||||
if (!$task) {
|
||||
return json([
|
||||
'code' => 404,
|
||||
'msg' => '任务不存在'
|
||||
]);
|
||||
}
|
||||
|
||||
// 准备更新数据
|
||||
$updateData = [];
|
||||
|
||||
// 只允许更新特定字段
|
||||
$allowedFields = ['name', 'device_id', 'scene_id', 'scene_config', 'status', 'priority'];
|
||||
foreach ($allowedFields as $field) {
|
||||
if (isset($data[$field])) {
|
||||
$updateData[$field] = $data[$field];
|
||||
}
|
||||
}
|
||||
|
||||
// 更新任务
|
||||
$task->save($updateData);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '更新成功'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除任务
|
||||
*
|
||||
* @param int $id
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function delete($id)
|
||||
{
|
||||
// 检查任务是否存在
|
||||
$task = PlanTask::get($id);
|
||||
if (!$task) {
|
||||
return json([
|
||||
'code' => 404,
|
||||
'msg' => '任务不存在'
|
||||
]);
|
||||
}
|
||||
|
||||
// 软删除任务
|
||||
$task->delete();
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '删除成功'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动任务
|
||||
*
|
||||
* @param int $id
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function start($id)
|
||||
{
|
||||
// 检查任务是否存在
|
||||
$task = PlanTask::get($id);
|
||||
if (!$task) {
|
||||
return json([
|
||||
'code' => 404,
|
||||
'msg' => '任务不存在'
|
||||
]);
|
||||
}
|
||||
|
||||
// 更新状态为启用
|
||||
$task->save([
|
||||
'status' => 1,
|
||||
'current_step' => 0
|
||||
]);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '任务已启动'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止任务
|
||||
*
|
||||
* @param int $id
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function stop($id)
|
||||
{
|
||||
// 检查任务是否存在
|
||||
$task = PlanTask::get($id);
|
||||
if (!$task) {
|
||||
return json([
|
||||
'code' => 404,
|
||||
'msg' => '任务不存在'
|
||||
]);
|
||||
}
|
||||
|
||||
// 更新状态为停用
|
||||
$task->save([
|
||||
'status' => 0
|
||||
]);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '任务已停止'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行定时任务(供外部调用)
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function cron()
|
||||
{
|
||||
// 获取密钥
|
||||
$key = Request::param('key', '');
|
||||
|
||||
// 验证密钥(实际生产环境应当使用更安全的验证方式)
|
||||
if ($key !== config('task.cron_key')) {
|
||||
return json([
|
||||
'code' => 403,
|
||||
'msg' => '访问密钥无效'
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取待执行的任务
|
||||
$tasks = PlanTask::getPendingTasks(5);
|
||||
if ($tasks->isEmpty()) {
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '没有需要执行的任务',
|
||||
'data' => []
|
||||
]);
|
||||
}
|
||||
|
||||
$results = [];
|
||||
|
||||
// 逐一执行任务
|
||||
foreach ($tasks as $task) {
|
||||
$runner = new TaskRunner($task);
|
||||
$result = $runner->run();
|
||||
|
||||
$results[] = [
|
||||
'task_id' => $task->id,
|
||||
'name' => $task->name,
|
||||
'result' => $result
|
||||
];
|
||||
|
||||
// 记录执行信息
|
||||
Log::info('任务执行', [
|
||||
'task_id' => $task->id,
|
||||
'name' => $task->name,
|
||||
'result' => $result
|
||||
]);
|
||||
}
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '任务执行完成',
|
||||
'data' => $results
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('任务执行异常', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return json([
|
||||
'code' => 500,
|
||||
'msg' => '任务执行异常:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动执行任务
|
||||
*
|
||||
* @param int $id
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function execute($id)
|
||||
{
|
||||
// 检查任务是否存在
|
||||
$task = PlanTask::get($id);
|
||||
if (!$task) {
|
||||
return json([
|
||||
'code' => 404,
|
||||
'msg' => '任务不存在'
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
// 执行任务
|
||||
$runner = new TaskRunner($task);
|
||||
$result = $runner->run();
|
||||
|
||||
// 记录执行信息
|
||||
Log::info('手动执行任务', [
|
||||
'task_id' => $task->id,
|
||||
'name' => $task->name,
|
||||
'result' => $result
|
||||
]);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '任务执行完成',
|
||||
'data' => $result
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('手动执行任务异常', [
|
||||
'task_id' => $task->id,
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return json([
|
||||
'code' => 500,
|
||||
'msg' => '任务执行异常:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
535
application/cunkebao/controller/TokensController.php
Normal file
535
application/cunkebao/controller/TokensController.php
Normal file
@@ -0,0 +1,535 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller;
|
||||
|
||||
use app\common\controller\PaymentService;
|
||||
use app\common\model\Order;
|
||||
use app\common\model\User;
|
||||
use app\cunkebao\model\TokensPackage;
|
||||
use app\chukebao\model\TokensCompany;
|
||||
use app\chukebao\model\TokensRecord;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
use think\facade\Env;
|
||||
|
||||
class TokensController extends BaseController
|
||||
{
|
||||
public function getList()
|
||||
{
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$where = [
|
||||
['isDel', '=', 0],
|
||||
['status', '=', 1],
|
||||
];
|
||||
$query = TokensPackage::where($where);
|
||||
$total = $query->count();
|
||||
$list = $query->where($where)->page($page, $limit)->order('sort ASC,id desc')->select();
|
||||
foreach ($list as &$item) {
|
||||
$item['description'] = json_decode($item['description'], true);
|
||||
$item['discount'] = round(((($item['originalPrice'] - $item['price']) / $item['originalPrice']) * 100), 2);
|
||||
$item['price'] = round($item['price'], 2);
|
||||
$item['unitPrice'] = round($item['price'] / $item['tokens'], 6);
|
||||
$item['originalPrice'] = round($item['originalPrice'] / 100, 2);
|
||||
$item['tokens'] = number_format($item['tokens']);
|
||||
}
|
||||
unset($item);
|
||||
return ResponseHelper::success(['list' => $list, 'total' => $total]);
|
||||
}
|
||||
|
||||
|
||||
public function pay()
|
||||
{
|
||||
$id = $this->request->param('id', '');
|
||||
$price = $this->request->param('price', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$payType = $this->request->param('payType', 'qrCode');
|
||||
|
||||
if (!in_array($payType, ['wechat', 'alipay', 'qrCode'])) {
|
||||
return ResponseHelper::error('付款类型不正确');
|
||||
}
|
||||
|
||||
|
||||
if (empty($id) && empty($price)) {
|
||||
return ResponseHelper::error('套餐和自定义购买金额必须选一个');
|
||||
}
|
||||
|
||||
if (!empty($id)) {
|
||||
$package = TokensPackage::where(['id' => $id, 'status' => 1, 'isDel' => 0])->find();
|
||||
if (empty($package)) {
|
||||
return ResponseHelper::error('套餐不存在或者已禁用');
|
||||
}
|
||||
|
||||
if ($package['price'] <= 0) {
|
||||
return ResponseHelper::error('套餐金额异常');
|
||||
}
|
||||
|
||||
$specs = [
|
||||
'id' => $package['id'],
|
||||
'name' => $package['name'],
|
||||
'price' => $package['price'],
|
||||
'tokens' => $package['tokens'],
|
||||
];
|
||||
|
||||
} else {
|
||||
//获取配置的tokens比例
|
||||
$tokens_multiple = Env::get('payment.tokens_multiple', 20);
|
||||
$specs = [
|
||||
'id' => 0,
|
||||
'name' => '自定义购买算力',
|
||||
'price' => intval($price * 100),
|
||||
'tokens' => intval($price * $tokens_multiple),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
$orderNo = date('YmdHis') . rand(100000, 999999);
|
||||
$order = [
|
||||
'companyId' => $companyId,
|
||||
'userId' => $userId,
|
||||
'orderNo' => $orderNo,
|
||||
'goodsId' => $specs['id'],
|
||||
'goodsName' => $specs['name'],
|
||||
'goodsSpecs' => $specs,
|
||||
'orderType' => 1,
|
||||
'money' => $specs['price'],
|
||||
'service' => $payType
|
||||
];
|
||||
$paymentService = new PaymentService();
|
||||
$res = $paymentService->createOrder($order);
|
||||
$res = json_decode($res, true);
|
||||
if ($res['code'] == 200) {
|
||||
return ResponseHelper::success(['orderNo' => $orderNo, 'code_url' => $res['data']], '订单创建成功');
|
||||
} else {
|
||||
return ResponseHelper::error($res['msg']);
|
||||
}
|
||||
}
|
||||
|
||||
public function queryOrder()
|
||||
{
|
||||
$orderNo = $this->request->param('orderNo', '');
|
||||
$order = Order::where('orderNo', $orderNo)->find();
|
||||
if (!$order) {
|
||||
return ResponseHelper::error('该订单不存在');
|
||||
}
|
||||
if ($order->status != 1) {
|
||||
$paymentService = new PaymentService();
|
||||
$res = $paymentService->queryOrder($orderNo);
|
||||
$res = json_decode($res, true);
|
||||
if ($res['code'] == 200) {
|
||||
return ResponseHelper::success($order, '订单已支付');
|
||||
} else {
|
||||
$errorMsg = !empty($order['payInfo']) ? $order['payInfo'] : '订单未支付';
|
||||
return ResponseHelper::success($order,$errorMsg);
|
||||
}
|
||||
} else {
|
||||
return ResponseHelper::success($order, '订单已支付');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取订单列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getOrderList()
|
||||
{
|
||||
try {
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$status = $this->request->param('status', ''); // 订单状态筛选
|
||||
$keyword = $this->request->param('keyword', ''); // 关键词搜索(订单号)
|
||||
$orderType = $this->request->param('orderType', ''); // 订单类型筛选
|
||||
$payType = $this->request->param('payType', ''); // 支付类型筛选
|
||||
$startTime = $this->request->param('startTime', ''); // 开始时间
|
||||
$endTime = $this->request->param('endTime', ''); // 结束时间
|
||||
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
// 构建查询条件
|
||||
$where = [
|
||||
['userId', '=', $userId],
|
||||
['companyId', '=', $companyId]
|
||||
];
|
||||
|
||||
// 关键词搜索(订单号、商品名称)
|
||||
if (!empty($keyword)) {
|
||||
$where[] = ['orderNo|goodsName', 'like', '%' . $keyword . '%'];
|
||||
}
|
||||
|
||||
// 状态筛选 (0-待支付 1-已付款 2-已退款 3-付款失败)
|
||||
if ($status !== '') {
|
||||
$where[] = ['status', '=', $status];
|
||||
}
|
||||
|
||||
// 订单类型筛选
|
||||
if ($orderType !== '') {
|
||||
$where[] = ['orderType', '=', $orderType];
|
||||
}
|
||||
|
||||
// 支付类型筛选
|
||||
if($payType !== '') {
|
||||
$where[] = ['payType', '=', $payType];
|
||||
}
|
||||
|
||||
// 时间范围筛选
|
||||
if (!empty($startTime)) {
|
||||
$where[] = ['createTime', '>=', strtotime($startTime)];
|
||||
}
|
||||
if (!empty($endTime)) {
|
||||
$where[] = ['createTime', '<=', strtotime($endTime . ' 23:59:59')];
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
$query = Order::where($where)
|
||||
->where(function ($query) {
|
||||
$query->whereNull('deleteTime')->whereOr('deleteTime', 0);
|
||||
});
|
||||
$total = $query->count();
|
||||
|
||||
$list = $query->field('id,orderNo,goodsId,goodsName,goodsSpecs,orderType,money,status,payType,payTime,createTime')
|
||||
->order('id desc')
|
||||
->page($page, $limit)
|
||||
->select();
|
||||
|
||||
// 格式化数据
|
||||
foreach ($list as &$item) {
|
||||
// 金额转换(分转元)
|
||||
$item['money'] = round($item['money'] / 100, 2);
|
||||
|
||||
// 解析商品规格
|
||||
if (!empty($item['goodsSpecs'])) {
|
||||
$specs = is_string($item['goodsSpecs']) ? json_decode($item['goodsSpecs'], true) : $item['goodsSpecs'];
|
||||
$item['goodsSpecs'] = $specs;
|
||||
|
||||
// 添加算力数量
|
||||
if (isset($specs['tokens'])) {
|
||||
$item['tokens'] = number_format($specs['tokens']);
|
||||
}
|
||||
}
|
||||
|
||||
// 状态文本
|
||||
$statusText = [
|
||||
0 => '待支付',
|
||||
1 => '已付款',
|
||||
2 => '已退款',
|
||||
3 => '付款失败'
|
||||
];
|
||||
$item['statusText'] = $statusText[$item['status']] ?? '未知';
|
||||
|
||||
// 订单类型文本
|
||||
$orderTypeText = [
|
||||
1 => '购买算力'
|
||||
];
|
||||
$item['orderTypeText'] = $orderTypeText[$item['orderType']] ?? '其他';
|
||||
|
||||
// 支付类型文本
|
||||
$payTypeText = [
|
||||
1 => '微信支付',
|
||||
2 => '支付宝'
|
||||
];
|
||||
$item['payTypeText'] = !empty($item['payType']) ? ($payTypeText[$item['payType']] ?? '未知') : '';
|
||||
|
||||
// 格式化时间
|
||||
$item['createTime'] = $item['createTime'] ? date('Y-m-d H:i:s', $item['createTime']) : '';
|
||||
$item['payTime'] = $item['payTime'] ? date('Y-m-d H:i:s', $item['payTime']) : '';
|
||||
}
|
||||
unset($item);
|
||||
|
||||
return ResponseHelper::success([
|
||||
'list' => $list,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'limit' => $limit
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('获取订单列表失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公司算力统计信息
|
||||
* 包括:总算力、今日使用、本月使用、剩余算力
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getTokensStatistics()
|
||||
{
|
||||
try {
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
// 获取公司算力余额
|
||||
$tokensCompany = TokensCompany::where(['companyId' => $companyId,'userId' => $userId])->find();
|
||||
$remainingTokens = $tokensCompany ? intval($tokensCompany->tokens) : 0;
|
||||
|
||||
// 获取今日开始和结束时间戳
|
||||
$todayStart = strtotime(date('Y-m-d 00:00:00'));
|
||||
$todayEnd = strtotime(date('Y-m-d 23:59:59'));
|
||||
|
||||
// 获取本月开始和结束时间戳
|
||||
$monthStart = strtotime(date('Y-m-01 00:00:00'));
|
||||
$monthEnd = strtotime(date('Y-m-t 23:59:59'));
|
||||
|
||||
// 统计今日消费(type=0表示消费)
|
||||
$todayUsed = TokensRecord::where([
|
||||
['userId', '=', $userId],
|
||||
['companyId', '=', $companyId],
|
||||
['type', '=', 0], // 0为减少(消费)
|
||||
['createTime', '>=', $todayStart],
|
||||
['createTime', '<=', $todayEnd]
|
||||
])->sum('tokens');
|
||||
$todayUsed = intval($todayUsed);
|
||||
|
||||
// 统计本月消费
|
||||
$monthUsed = TokensRecord::where([
|
||||
['userId', '=', $userId],
|
||||
['companyId', '=', $companyId],
|
||||
['type', '=', 0], // 0为减少(消费)
|
||||
['createTime', '>=', $monthStart],
|
||||
['createTime', '<=', $monthEnd]
|
||||
])->sum('tokens');
|
||||
$monthUsed = intval($monthUsed);
|
||||
|
||||
// 计算总算力(当前剩余 + 历史总消费)
|
||||
$totalConsumed = TokensRecord::where([
|
||||
['userId', '=', $userId],
|
||||
['companyId', '=', $companyId],
|
||||
['type', '=', 0]
|
||||
])->sum('tokens');
|
||||
$totalConsumed = intval($totalConsumed);
|
||||
|
||||
// 总充值算力
|
||||
$totalRecharged = TokensRecord::where([
|
||||
['userId', '=', $userId],
|
||||
['companyId', '=', $companyId],
|
||||
['type', '=', 1] // 1为增加(充值)
|
||||
])->sum('tokens');
|
||||
$totalRecharged = intval($totalRecharged);
|
||||
|
||||
// 计算预计可用天数(基于过去一个月的平均消耗)
|
||||
$estimatedDays = $this->calculateEstimatedDays($userId,$companyId, $remainingTokens);
|
||||
|
||||
return ResponseHelper::success([
|
||||
'totalTokens' => $totalRecharged, // 总算力(累计充值)
|
||||
'todayUsed' => $todayUsed, // 今日使用
|
||||
'monthUsed' => $monthUsed, // 本月使用
|
||||
'remainingTokens' => $remainingTokens, // 剩余算力
|
||||
'totalConsumed' => $totalConsumed, // 累计消费
|
||||
'estimatedDays' => $estimatedDays, // 预计可用天数
|
||||
], '获取成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('获取算力统计失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算预计可用天数(基于过去一个月的平均消耗)
|
||||
* @param int $userId 用户ID
|
||||
* @param int $companyId 公司ID
|
||||
* @param int $remainingTokens 当前剩余算力
|
||||
* @return int 预计可用天数,-1表示无法计算(无消耗记录或余额为0)
|
||||
*/
|
||||
private function calculateEstimatedDays($userId,$companyId, $remainingTokens)
|
||||
{
|
||||
// 如果余额为0或负数,无法计算
|
||||
if ($remainingTokens <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 计算过去30天的消耗总量(只统计减少的记录,type=0)
|
||||
$oneMonthAgo = time() - (30 * 24 * 60 * 60); // 30天前的时间戳
|
||||
|
||||
$totalConsumed = TokensRecord::where([
|
||||
['userId', '=', $userId],
|
||||
['companyId', '=', $companyId],
|
||||
['type', '=', 0], // 只统计减少的记录
|
||||
['createTime', '>=', $oneMonthAgo]
|
||||
])->sum('tokens');
|
||||
|
||||
$totalConsumed = intval($totalConsumed);
|
||||
|
||||
// 如果过去30天没有消耗记录,无法计算
|
||||
if ($totalConsumed <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 计算平均每天消耗量
|
||||
$avgDailyConsumption = $totalConsumed / 30;
|
||||
|
||||
// 如果平均每天消耗为0,无法计算
|
||||
if ($avgDailyConsumption <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 计算预计可用天数 = 当前余额 / 平均每天消耗量
|
||||
$estimatedDays = floor($remainingTokens / $avgDailyConsumption);
|
||||
|
||||
return $estimatedDays;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分配token(仅管理员可用)
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function allocateTokens()
|
||||
{
|
||||
try {
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$targetUserId = (int)$this->request->param('targetUserId', 0);
|
||||
$tokens = (int)$this->request->param('tokens', 0);
|
||||
$remarks = $this->request->param('remarks', '');
|
||||
|
||||
// 验证参数
|
||||
if (empty($targetUserId)) {
|
||||
return ResponseHelper::error('目标用户ID不能为空');
|
||||
}
|
||||
|
||||
if ($tokens <= 0) {
|
||||
return ResponseHelper::error('分配的token数量必须大于0');
|
||||
}
|
||||
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('公司信息获取失败');
|
||||
}
|
||||
|
||||
// 验证当前用户是否为管理员
|
||||
$currentUser = User::where([
|
||||
'id' => $userId,
|
||||
'companyId' => $companyId
|
||||
])->find();
|
||||
|
||||
if (empty($currentUser)) {
|
||||
return ResponseHelper::error('用户信息不存在');
|
||||
}
|
||||
|
||||
if (empty($currentUser->isAdmin) || $currentUser->isAdmin != 1) {
|
||||
return ResponseHelper::error('只有管理员才能分配token');
|
||||
}
|
||||
|
||||
// 验证目标用户是否存在且属于同一公司
|
||||
$targetUser = User::where([
|
||||
'id' => $targetUserId,
|
||||
'companyId' => $companyId
|
||||
])->find();
|
||||
|
||||
if (empty($targetUser)) {
|
||||
return ResponseHelper::error('目标用户不存在或不属于同一公司');
|
||||
}
|
||||
|
||||
// 检查分配者的token余额
|
||||
$allocatorTokens = TokensCompany::where([
|
||||
'companyId' => $companyId,
|
||||
'userId' => $userId
|
||||
])->find();
|
||||
|
||||
$allocatorBalance = $allocatorTokens ? intval($allocatorTokens->tokens) : 0;
|
||||
|
||||
if ($allocatorBalance < $tokens) {
|
||||
return ResponseHelper::error('token余额不足,当前余额:' . $allocatorBalance);
|
||||
}
|
||||
|
||||
// 开始事务
|
||||
Db::startTrans();
|
||||
|
||||
try {
|
||||
// 1. 减少分配者的token
|
||||
if (!empty($allocatorTokens)) {
|
||||
$allocatorTokens->tokens = $allocatorBalance - $tokens;
|
||||
$allocatorTokens->updateTime = time();
|
||||
$allocatorTokens->save();
|
||||
$allocatorNewBalance = $allocatorTokens->tokens;
|
||||
} else {
|
||||
// 如果分配者没有记录,创建一条(余额为0)
|
||||
$allocatorTokens = new TokensCompany();
|
||||
$allocatorTokens->userId = $userId;
|
||||
$allocatorTokens->companyId = $companyId;
|
||||
$allocatorTokens->tokens = 0;
|
||||
$allocatorTokens->isAdmin = 1;
|
||||
$allocatorTokens->createTime = time();
|
||||
$allocatorTokens->updateTime = time();
|
||||
$allocatorTokens->save();
|
||||
$allocatorNewBalance = 0;
|
||||
}
|
||||
|
||||
// 2. 记录分配者的减少记录
|
||||
$targetUserAccount = $targetUser->account ?? $targetUser->phone ?? '用户ID[' . $targetUserId . ']';
|
||||
$allocatorRecord = new TokensRecord();
|
||||
$allocatorRecord->companyId = $companyId;
|
||||
$allocatorRecord->userId = $userId;
|
||||
$allocatorRecord->type = 0; // 0为减少
|
||||
$allocatorRecord->form = 1001; // 1001表示分配
|
||||
$allocatorRecord->wechatAccountId = 0;
|
||||
$allocatorRecord->friendIdOrGroupId = $targetUserId;
|
||||
$allocatorRecord->remarks = !empty($remarks) ? $remarks : '分配给' . $targetUserAccount;
|
||||
$allocatorRecord->tokens = $tokens;
|
||||
$allocatorRecord->balanceTokens = $allocatorNewBalance;
|
||||
$allocatorRecord->createTime = time();
|
||||
$allocatorRecord->save();
|
||||
|
||||
// 3. 增加接收者的token
|
||||
$receiverTokens = TokensCompany::where([
|
||||
'companyId' => $companyId,
|
||||
'userId' => $targetUserId
|
||||
])->find();
|
||||
|
||||
if (!empty($receiverTokens)) {
|
||||
$receiverTokens->tokens = intval($receiverTokens->tokens) + $tokens;
|
||||
$receiverTokens->updateTime = time();
|
||||
$receiverTokens->save();
|
||||
$receiverNewBalance = $receiverTokens->tokens;
|
||||
} else {
|
||||
// 如果接收者没有记录,创建一条
|
||||
$receiverTokens = new TokensCompany();
|
||||
$receiverTokens->userId = $targetUserId;
|
||||
$receiverTokens->companyId = $companyId;
|
||||
$receiverTokens->tokens = $tokens;
|
||||
$receiverTokens->isAdmin = (!empty($targetUser->isAdmin) && $targetUser->isAdmin == 1) ? 1 : 0;
|
||||
$receiverTokens->createTime = time();
|
||||
$receiverTokens->updateTime = time();
|
||||
$receiverTokens->save();
|
||||
$receiverNewBalance = $tokens;
|
||||
}
|
||||
|
||||
// 4. 记录接收者的增加记录
|
||||
$adminAccount = $currentUser->account ?? $currentUser->phone ?? '管理员';
|
||||
$receiverRecord = new TokensRecord();
|
||||
$receiverRecord->companyId = $companyId;
|
||||
$receiverRecord->userId = $targetUserId;
|
||||
$receiverRecord->type = 1; // 1为增加
|
||||
$receiverRecord->form = 1001; // 1001表示分配
|
||||
$receiverRecord->wechatAccountId = 0;
|
||||
$receiverRecord->friendIdOrGroupId = $userId;
|
||||
$receiverRecord->remarks = !empty($remarks) ? '管理员分配:' . $remarks : '管理员分配';
|
||||
$receiverRecord->tokens = $tokens;
|
||||
$receiverRecord->balanceTokens = $receiverNewBalance;
|
||||
$receiverRecord->createTime = time();
|
||||
$receiverRecord->save();
|
||||
|
||||
Db::commit();
|
||||
|
||||
return ResponseHelper::success([
|
||||
'allocatorBalance' => $allocatorNewBalance,
|
||||
'receiverBalance' => $receiverNewBalance,
|
||||
'allocatedTokens' => $tokens
|
||||
], '分配成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('分配失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('分配失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
321
application/cunkebao/controller/TrafficController.php
Normal file
321
application/cunkebao/controller/TrafficController.php
Normal file
@@ -0,0 +1,321 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller;
|
||||
|
||||
use app\common\model\TrafficSourcePackage;
|
||||
use app\common\model\TrafficSourcePackageItem;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
use app\cunkebao\controller\RFMController;
|
||||
|
||||
class TrafficController extends BaseController
|
||||
{
|
||||
|
||||
/**
|
||||
* 流量池包
|
||||
* @return \think\response\Json
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @throws \think\exception\DbException
|
||||
*/
|
||||
public function getPackage()
|
||||
{
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$package = Db::name('traffic_source_package')->alias('tsp')
|
||||
->join('traffic_source_package_item tspi', 'tspi.packageId=tsp.id', 'left')
|
||||
->whereIn('tsp.companyId', [$companyId, 0])
|
||||
->field('tsp.id,tsp.name,tsp.description,tsp.pic,tsp.isSys as type,tsp.createTime,count(tspi.id) as num')
|
||||
->group('tsp.id');
|
||||
|
||||
if (!empty($keyword)) {
|
||||
$package->where('tsp.name|tsp.description', 'like', '%' . $keyword . '%');
|
||||
}
|
||||
|
||||
$list = $package->page($page, $limit)->order('isSys ASC,id DESC')->select();
|
||||
$total = $package->count();
|
||||
|
||||
$rfmRule = 'default';
|
||||
foreach ($list as $k => &$v) {
|
||||
if ($v['type'] != 1) {
|
||||
$v['createTime'] = !empty($v['createTime']) ? formatRelativeTime($v['createTime']) : '';
|
||||
} else {
|
||||
$v['createTime'] = '';
|
||||
}
|
||||
|
||||
// RFM 评分(示例:以创建时间近似最近活跃,num 近似频次;金额若无则为 0)
|
||||
$recencyDays = isset($v['createTime']) && is_numeric($v['createTime']) ? floor((time() - (int)$v['createTime']) / 86400) : null;
|
||||
// 如果上方被格式化为文本,则尝试从原始结果集取原值
|
||||
if (!is_numeric($recencyDays) || $recencyDays === null) {
|
||||
$rawCreate = isset($list[$k]['createTime']) ? $list[$k]['createTime'] : null;
|
||||
$recencyDays = is_numeric($rawCreate) ? floor((time() - (int)$rawCreate) / 86400) : 9999;
|
||||
}
|
||||
$frequency = (int)($v['num'] ?? 0);
|
||||
$monetary = (float)($v['monetary'] ?? 0);
|
||||
|
||||
$scores = RFMController::calcRfmScores($recencyDays, $frequency, $monetary);
|
||||
$v['R'] = $scores['R'];
|
||||
$v['F'] = $scores['F'];
|
||||
$v['M'] = $scores['M'];
|
||||
$v['RFM'] = $scores['R'] + $scores['F'] + $scores['M'];
|
||||
}
|
||||
unset($v);
|
||||
|
||||
$data = [
|
||||
'total' => $total,
|
||||
'list' => $list,
|
||||
];
|
||||
|
||||
return ResponseHelper::success($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加流量池
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function addPackage()
|
||||
{
|
||||
$packageName = $this->request->param('packageName', '');
|
||||
$description = $this->request->param('description', '');
|
||||
$pic = $this->request->param('pic', '');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$userId = $this->getUserInfo('id');
|
||||
|
||||
if (empty($packageName)) {
|
||||
return ResponseHelper::error('流量池名称不能为空');
|
||||
}
|
||||
|
||||
$package = TrafficSourcePackage::where(['isDel' => 0, 'name' => $packageName])
|
||||
->whereIn('companyId', [$companyId, 0])
|
||||
->field('id,name')
|
||||
->find();
|
||||
if (!empty($package)) {
|
||||
return ResponseHelper::error('该流量池名称已存在');
|
||||
}
|
||||
$packageId = TrafficSourcePackage::insertGetId([
|
||||
'userId' => $userId,
|
||||
'companyId' => $companyId,
|
||||
'name' => $packageName,
|
||||
'description' => $description,
|
||||
'pic' => $pic,
|
||||
'matchingRules' => json_encode([]),
|
||||
'createTime' => time(),
|
||||
'isDel' => 0,
|
||||
]);
|
||||
|
||||
if (!empty($packageId)) {
|
||||
return ResponseHelper::success($packageId, '该流量添加成功');
|
||||
} else {
|
||||
return ResponseHelper::error('该流量添加失败');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑流量池
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function editPackage()
|
||||
{
|
||||
$packageId = $this->request->param('packageId', '');
|
||||
$packageName = $this->request->param('packageName', '');
|
||||
$description = $this->request->param('description', '');
|
||||
$pic = $this->request->param('pic', '');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$userId = $this->getUserInfo('id');
|
||||
|
||||
if (empty($packageId)) {
|
||||
return ResponseHelper::error('流量池ID不能为空');
|
||||
}
|
||||
|
||||
if (empty($packageName)) {
|
||||
return ResponseHelper::error('流量池名称不能为空');
|
||||
}
|
||||
|
||||
// 检查流量池是否存在且属于当前公司
|
||||
$package = TrafficSourcePackage::where(['id' => $packageId, 'isDel' => 0])
|
||||
->whereIn('companyId', [$companyId, 0])
|
||||
->find();
|
||||
if (empty($package)) {
|
||||
return ResponseHelper::error('流量池不存在或已删除');
|
||||
}
|
||||
|
||||
// 检查系统流量池是否可编辑
|
||||
if ($package['isSys'] == 1) {
|
||||
return ResponseHelper::error('系统流量池不允许编辑');
|
||||
}
|
||||
|
||||
// 检查名称是否重复(排除当前记录)
|
||||
$existPackage = TrafficSourcePackage::where(['isDel' => 0, 'name' => $packageName])
|
||||
->whereIn('companyId', [$companyId, 0])
|
||||
->where('id', '<>', $packageId)
|
||||
->field('id,name')
|
||||
->find();
|
||||
if (!empty($existPackage)) {
|
||||
return ResponseHelper::error('该流量池名称已存在');
|
||||
}
|
||||
|
||||
// 更新流量池信息
|
||||
$updateData = [
|
||||
'name' => $packageName,
|
||||
'updateTime' => time(),
|
||||
];
|
||||
|
||||
// 更新描述字段(允许为空)
|
||||
$updateData['description'] = $description;
|
||||
|
||||
// 更新图片字段(允许为空)
|
||||
$updateData['pic'] = $pic;
|
||||
|
||||
$result = TrafficSourcePackage::where('id', $packageId)->update($updateData);
|
||||
|
||||
if ($result !== false) {
|
||||
return ResponseHelper::success($packageId, '流量池编辑成功');
|
||||
} else {
|
||||
return ResponseHelper::error('流量池编辑失败');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除流量池(假删除)
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function deletePackage()
|
||||
{
|
||||
$packageId = $this->request->param('packageId', '');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($packageId)) {
|
||||
return ResponseHelper::error('流量池ID不能为空');
|
||||
}
|
||||
|
||||
// 检查流量池是否存在且属于当前公司
|
||||
$package = TrafficSourcePackage::where(['id' => $packageId, 'isDel' => 0])
|
||||
->whereIn('companyId', [$companyId, 0])
|
||||
->find();
|
||||
if (empty($package)) {
|
||||
return ResponseHelper::error('流量池不存在或已删除');
|
||||
}
|
||||
|
||||
// 检查系统流量池是否可删除
|
||||
if ($package['isSys'] == 1) {
|
||||
return ResponseHelper::error('系统流量池不允许删除');
|
||||
}
|
||||
|
||||
// 开启事务
|
||||
Db::startTrans();
|
||||
try {
|
||||
// 执行流量池假删除
|
||||
$result = TrafficSourcePackage::where('id', $packageId)->update([
|
||||
'isDel' => 1,
|
||||
'deleteTime' => time()
|
||||
]);
|
||||
|
||||
if ($result === false) {
|
||||
throw new \Exception('流量池删除失败');
|
||||
}
|
||||
|
||||
// 删除流量池内容(TrafficSourcePackageItem)假删除
|
||||
$itemResult = TrafficSourcePackageItem::where([
|
||||
'packageId' => $packageId,
|
||||
'companyId' => $companyId,
|
||||
'isDel' => 0
|
||||
])->update([
|
||||
'isDel' => 1,
|
||||
'deleteTime' => time()
|
||||
]);
|
||||
|
||||
// 提交事务
|
||||
Db::commit();
|
||||
|
||||
return ResponseHelper::success($packageId, '流量池及内容删除成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// 回滚事务
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('删除失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 流量池列表
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getTrafficPoolList()
|
||||
{
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
$packageId = $this->request->param('packageId', '');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$userId = $this->getUserInfo('id');
|
||||
|
||||
if (empty($packageId)) {
|
||||
return ResponseHelper::error('流量包id不能为空');
|
||||
}
|
||||
|
||||
$trafficSourcePackage = TrafficSourcePackage::where(['id' => $packageId, 'isDel' => 0])->whereIn('companyId', [$companyId, 0])->find();
|
||||
if (empty($trafficSourcePackage)) {
|
||||
return ResponseHelper::error('流量包不存在或已删除');
|
||||
}
|
||||
$where = [
|
||||
['tspi.companyId', '=', $companyId],
|
||||
['tspi.packageId', '=', $packageId],
|
||||
];
|
||||
|
||||
if (empty($keyword)) {
|
||||
$where[] = ['wa.nickname|wa.phone|wa.alias|wa.wechatId|p.mobile|p.identifier', 'like', '%' . $keyword . '%'];
|
||||
}
|
||||
|
||||
$query = TrafficSourcePackageItem::alias('tspi')
|
||||
->field(
|
||||
[
|
||||
'p.id', 'p.identifier', 'p.mobile', 'p.wechatId', 'tspi.companyId',
|
||||
'wa.nickname', 'wa.avatar', 'wa.gender', 'wa.phone', 'wa.alias'
|
||||
]
|
||||
)
|
||||
->join('traffic_pool p', 'p.identifier=tspi.identifier', 'left')
|
||||
->join('wechat_account wa', 'tspi.identifier=wa.wechatId', 'left')
|
||||
->where($where);
|
||||
|
||||
$query->order('tspi.id DESC,p.id DESC')->group('p.identifier');
|
||||
|
||||
$list = $query->page($page, $limit)->select()->toArray();
|
||||
$total = $query->count();
|
||||
|
||||
foreach ($list as $k => &$v) {
|
||||
//流量池筛选
|
||||
$package = TrafficSourcePackageItem::alias('tspi')
|
||||
->join('traffic_source_package p', 'tspi.packageId=p.id AND tspi.companyId=p.companyId')
|
||||
->where(['tspi.identifier' => $v['identifier']])
|
||||
->whereIn('tspi.companyId', [0, $v['companyId']])
|
||||
->column('p.name');
|
||||
$v['packages'] = $package;
|
||||
$v['phone'] = !empty($v['phone']) ? $v['phone'] : $v['mobile'];
|
||||
unset($v['mobile']);
|
||||
|
||||
|
||||
$scores = RFMController::calcRfmScores(30, 30, 30);
|
||||
$v['R'] = $scores['R'];
|
||||
$v['F'] = $scores['F'];
|
||||
$v['M'] = $scores['M'];
|
||||
$v['RFM'] = $scores['R'] + $scores['F'] + $scores['M'];
|
||||
$v['money'] = 2222;
|
||||
$v['msgCount'] = 2222;
|
||||
$v['tag'] = ['test', 'test2'];
|
||||
}
|
||||
unset($v);
|
||||
|
||||
|
||||
$data = ['list' => $list, 'total' => $total];
|
||||
return ResponseHelper::success($data);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
namespace app\cunkebao\controller\chatroom;
|
||||
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use app\cunkebao\model\WechatChatroom;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 群聊管理控制器
|
||||
*/
|
||||
class GetChatroomListV1Controller extends BaseController
|
||||
{
|
||||
|
||||
/**
|
||||
* 获取群聊列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 20);
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
try {
|
||||
|
||||
$companyId = (int)$this->getUserInfo('companyId');
|
||||
$wechatIds = Db::name('device')->alias('d')
|
||||
// 仅关联每个设备在 device_wechat_login 中的最新一条记录
|
||||
->join('(SELECT MAX(id) AS id, deviceId FROM ck_device_wechat_login WHERE companyId='.$companyId.' GROUP BY deviceId) dwl_max','dwl_max.deviceId = d.id')
|
||||
->join('device_wechat_login dwl','dwl.id = dwl_max.id')
|
||||
->where(['d.companyId' => $companyId,'d.deleteTime' => 0])
|
||||
->column('dwl.wechatId');
|
||||
|
||||
|
||||
/* $wechatIds = Db::name('device')->alias('d')
|
||||
->join('device_wechat_login dwl','dwl.deviceId=d.id AND dwl.companyId='.$this->getUserInfo('companyId'))
|
||||
->where(['d.companyId' => $this->getUserInfo('companyId'),'d.deleteTime' => 0])
|
||||
->column('dwl.wechatId');*/
|
||||
|
||||
|
||||
$where = [];
|
||||
if ($this->getUserInfo('isAdmin') == 1) {
|
||||
$where[] = ['gg.isDeleted', '=', 0];
|
||||
$where[] = ['g.ownerWechatId', 'in', $wechatIds];
|
||||
} else {
|
||||
$where[] = ['gg.isDeleted', '=', 0];
|
||||
$where[] = ['g.ownerWechatId', 'in', $wechatIds];
|
||||
//$where[] = ['g.userId', '=', $this->getUserInfo('id')];
|
||||
}
|
||||
|
||||
if(!empty($keyword)){
|
||||
$where[] = ['g.name', 'like', '%'.$keyword.'%'];
|
||||
}
|
||||
|
||||
$data = WechatChatroom::alias('g')
|
||||
->field(['g.id', 'g.chatroomId', 'g.name', 'g.avatar','g.ownerWechatId', 'g.identifier', 'g.createTime',
|
||||
'wa.nickname as ownerNickname','wa.avatar as ownerAvatar','wa.alias as ownerAlias'])
|
||||
->join('wechat_account wa', 'g.ownerWechatId = wa.wechatId', 'LEFT')
|
||||
->join(['s2_wechat_chatroom' => 'gg'], 'g.id = gg.id', 'LEFT')
|
||||
->where($where);
|
||||
|
||||
$total = $data->count();
|
||||
$list = $data->page($page, $limit)->order('g.id DESC')->select();
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'list' => $list,
|
||||
'total' => $total,
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return json([
|
||||
'code' => $e->getCode(),
|
||||
'msg' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取群成员列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getMemberList()
|
||||
{
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 20);
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
$groupId = $this->request->param('groupId', 0);
|
||||
|
||||
if (empty($groupId)) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'msg' => '群ID不能为空'
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
$where = [];
|
||||
$where[] = ['m.groupId', '=', $groupId];
|
||||
$where[] = ['m.deleteTime', '<=', 0];
|
||||
|
||||
// 如果有搜索关键词
|
||||
if (!empty($keyword)) {
|
||||
$where[] = ['wa.nickname|m.identifier', 'like', '%'.$keyword.'%'];
|
||||
}
|
||||
|
||||
$data = Db::name('wechat_group_member')
|
||||
->alias('m')
|
||||
->field([
|
||||
'm.id',
|
||||
'm.identifier',
|
||||
'm.customerIs',
|
||||
'wa.nickname',
|
||||
'wa.avatar',
|
||||
'm.groupId',
|
||||
'm.createTime',
|
||||
'g.name as groupName',
|
||||
'g.chatroomId'
|
||||
])
|
||||
->join('wechat_group g', 'm.groupId = g.id', 'LEFT')
|
||||
->join('wechat_account wa', 'wa.wechatId = m.identifier', 'LEFT')
|
||||
->where($where);
|
||||
|
||||
$total = $data->count();
|
||||
$list = $data->page($page, $limit)
|
||||
->order('m.id DESC')
|
||||
->select();
|
||||
|
||||
// 格式化时间
|
||||
foreach ($list as &$item) {
|
||||
if (!empty($item['createTime'])) {
|
||||
$item['createTime'] = date('Y-m-d H:i:s', $item['createTime']);
|
||||
}
|
||||
}
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'list' => $list,
|
||||
'total' => $total,
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return json([
|
||||
'code' => $e->getCode() ?: 500,
|
||||
'msg' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\device;
|
||||
|
||||
use app\common\model\Device as DeviceModel;
|
||||
use app\common\model\DeviceTaskconf as DeviceTaskconfModel;
|
||||
use app\common\model\DeviceUser as DeviceUserModel;
|
||||
use app\common\model\User as UserModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
use app\api\controller\DeviceController as apiDevice;
|
||||
|
||||
/**
|
||||
* 设备管理控制器
|
||||
*/
|
||||
class DeleteDeviceV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 删除设备关联用户信息
|
||||
*
|
||||
* @param int $deviceId
|
||||
* @return void
|
||||
*/
|
||||
protected function deleteDeviceUser(int $deviceId): void
|
||||
{
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$deviceUser = DeviceUserModel::where(compact('companyId', 'deviceId'))->find();
|
||||
|
||||
// 有关联数据则删除
|
||||
if ($deviceUser) {
|
||||
if (!$deviceUser->delete()) {
|
||||
throw new \Exception('设备用户关联数据删除失败', 402);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除设备任务配置记录
|
||||
*
|
||||
* @param int $deviceId
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function deleteDeviceConf(int $deviceId): void
|
||||
{
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$deviceConf = DeviceTaskconfModel::where(compact('companyId', 'deviceId'))->find();
|
||||
|
||||
// 有配置信息则删除
|
||||
if ($deviceConf) {
|
||||
if (!$deviceConf->delete()) {
|
||||
throw new \Exception('设备设置信息删除失败', 402);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除主设备信息
|
||||
*
|
||||
* @param int $id
|
||||
* @return DeviceModel
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function deleteDevice(int $id): void
|
||||
{
|
||||
$device = DeviceModel::where('companyId', $this->getUserInfo('companyId'))->find($id);
|
||||
|
||||
if (!$device) {
|
||||
throw new \Exception('设备不存在或无权限操作', 404);
|
||||
}
|
||||
|
||||
if (!$device->delete()) {
|
||||
throw new \Exception('设备删除失败', 402);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除存客宝设备数据
|
||||
*
|
||||
* @param int $id
|
||||
* @return $this
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function deleteCkbAbout(int $id): self
|
||||
{
|
||||
$apiDevice = new ApiDevice();
|
||||
$res = $apiDevice->delDevice($id);
|
||||
$res = json_decode($res, true);
|
||||
if ($res['code'] == 200){
|
||||
$this->deleteDevice($id);
|
||||
$this->deleteDeviceConf($id);
|
||||
$this->deleteDeviceUser($id);
|
||||
return $this;
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO 删除存客宝设备数据
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
protected function deleteS2About(): self
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户权限,只有操盘手可以删除设备
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function checkPermission(): self
|
||||
{
|
||||
if ($this->getUserInfo('typeId') != UserModel::MASTER_USER) {
|
||||
throw new \Exception('您没有权限删除设备', 403);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除设备
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$id = $this->request->param('id/d');
|
||||
|
||||
Db::startTrans();
|
||||
$this->checkPermission();
|
||||
$this->deleteCkbAbout($id)->deleteS2About($id);
|
||||
Db::commit();
|
||||
|
||||
return ResponseHelper::success();
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\device;
|
||||
|
||||
use app\api\controller\DeviceController as ApiDeviceController;
|
||||
use app\common\model\Device as DeviceModel;
|
||||
use app\common\model\User as UserModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
use think\facade\Cache;
|
||||
|
||||
/**
|
||||
* 设备控制器
|
||||
*/
|
||||
class GetAddResultedV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 通过账号id 获取项目id。
|
||||
*
|
||||
* @param int $accountId
|
||||
* @return int
|
||||
*/
|
||||
protected function getCompanyIdByAccountId(int $accountId): int
|
||||
{
|
||||
return UserModel::where('s2_accountId', $accountId)->value('companyId');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取项目下的所有设备。
|
||||
*
|
||||
* @param int $companyId
|
||||
* @return array
|
||||
*/
|
||||
protected function getAllDevicesIdWithInCompany(int $companyId): array
|
||||
{
|
||||
return DeviceModel::where('companyId', $companyId)->column('id') ?: [0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行数据迁移。
|
||||
*
|
||||
* @param int $accountId
|
||||
* @return void
|
||||
*/
|
||||
protected function migrateData(int $accountId): void
|
||||
{
|
||||
$companyId = $this->getCompanyIdByAccountId($accountId);
|
||||
$deviceIds = $this->getAllDevicesIdWithInCompany($companyId) ?: [0];
|
||||
|
||||
// 从 s2_device 导入数据。
|
||||
$this->getNewDeviceFromS2_device($deviceIds, $companyId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 s2_device 导入数据。
|
||||
*
|
||||
* @param array $ids
|
||||
* @param int $companyId
|
||||
* @return void
|
||||
*/
|
||||
protected function getNewDeviceFromS2_device(array $ids, int $companyId): void
|
||||
{
|
||||
$ids = implode(',', $ids);
|
||||
|
||||
$sql = "INSERT INTO ck_device(`id`, `imei`, `model`, phone, operatingSystem, memo, alive, brand, rooted, xPosed, softwareVersion, extra, createTime, updateTime, deleteTime, companyId)
|
||||
SELECT
|
||||
d.id, d.imei, d.model, d.phone, d.operatingSystem, d.memo, d.alive, d.brand, d.rooted, d.xPosed, d.softwareVersion, d.extra, d.createTime, d.lastUpdateTime, d.deleteTime, a.departmentId AS companyId
|
||||
FROM s2_device d
|
||||
JOIN s2_company_account a ON d.currentAccountId = a.id
|
||||
WHERE isDeleted = 0 AND deletedAndStop = 0 AND d.id NOT IN ({$ids}) AND a.departmentId = {$companyId}
|
||||
ON DUPLICATE KEY UPDATE
|
||||
imei = VALUES(imei),
|
||||
model = VALUES(model),
|
||||
phone = VALUES(phone),
|
||||
operatingSystem = VALUES(operatingSystem),
|
||||
memo = VALUES(memo),
|
||||
alive = VALUES(alive),
|
||||
brand = VALUES(brand),
|
||||
rooted = VALUES(rooted),
|
||||
xPosed = VALUES(xPosed),
|
||||
softwareVersion = VALUES(softwareVersion),
|
||||
extra = VALUES(extra),
|
||||
updateTime = VALUES(updateTime),
|
||||
deleteTime = VALUES(deleteTime),
|
||||
companyId = VALUES(companyId)";
|
||||
|
||||
Db::query($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前设备数量
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function getCkbDeviceCount(): int
|
||||
{
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$cacheKey = 'deviceNum_'.$companyId;
|
||||
$deviceNum = Cache::get($cacheKey);
|
||||
if (empty($deviceNum)) {
|
||||
$deviceNum = DeviceModel::where(['companyId' => $companyId])->count('*');
|
||||
Cache::set($cacheKey,$deviceNum,120);
|
||||
}
|
||||
return $deviceNum;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取添加的关联设备结果。
|
||||
*
|
||||
* @param int $accountId
|
||||
* @return bool
|
||||
*/
|
||||
protected function getAddResulted(int $accountId): bool
|
||||
{
|
||||
$deviceNum = $this->getCkbDeviceCount();
|
||||
$result = (new ApiDeviceController())->getlist(
|
||||
[
|
||||
'accountId' => $accountId,
|
||||
'pageIndex' => 0,
|
||||
'pageSize' => 100
|
||||
],
|
||||
true
|
||||
);
|
||||
$result = json_decode($result, true);
|
||||
$result = $result['data']['results'] ?? false;
|
||||
|
||||
if (empty($result)){
|
||||
return false;
|
||||
}else{
|
||||
if (count($result) > $deviceNum){
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$cacheKey = 'deviceNum_'.$companyId;
|
||||
Cache::rm($cacheKey);
|
||||
return true;
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取基础统计信息
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$accountId = $this->request->param('accountId/d');
|
||||
|
||||
if (empty($accountId)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
$isAdded = $this->getAddResulted($accountId);
|
||||
$isAdded && $this->migrateData($accountId);
|
||||
|
||||
return ResponseHelper::success(
|
||||
[
|
||||
'added' => $isAdded
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\device;
|
||||
|
||||
use app\common\model\Device as DeviceModel;
|
||||
use app\common\model\DeviceTaskconf as DeviceTaskconfModel;
|
||||
use app\common\model\DeviceUser as DeviceUserModel;
|
||||
use app\common\model\DeviceWechatLogin as DeviceWechatLoginModel;
|
||||
use app\common\model\User as UserModel;
|
||||
use app\common\model\WechatCustomer as WechatCustomerModel;
|
||||
use app\common\model\WechatFriendShip as WechatFriendShipModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use Eison\Utils\Helper\ArrHelper;
|
||||
use library\ResponseHelper;
|
||||
|
||||
/**
|
||||
* 设备管理控制器
|
||||
*/
|
||||
class GetDeviceDetailV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 检查用户是否有权限操作指定设备
|
||||
*
|
||||
* @param int $deviceId
|
||||
* @return void
|
||||
*/
|
||||
protected function checkUserDevicePermission(int $deviceId): void
|
||||
{
|
||||
$hasPermission = DeviceUserModel::where(
|
||||
[
|
||||
'deviceId' => $deviceId,
|
||||
'userId' => $this->getUserInfo('id'),
|
||||
'companyId' => $this->getUserInfo('companyId')
|
||||
]
|
||||
)
|
||||
->count() > 0;
|
||||
|
||||
if (!$hasPermission) {
|
||||
throw new \Exception('您没有权限查看该设备', 403);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析设备额外信息
|
||||
*
|
||||
* @param string $extra
|
||||
* @return int
|
||||
*/
|
||||
protected function parseExtraForBattery(string $extra): int
|
||||
{
|
||||
if (!empty($extra)) {
|
||||
$extra = json_decode($extra);
|
||||
|
||||
if ($extra && isset($extra->battery)) {
|
||||
return intval($extra->battery);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备最新登录微信的 wechatId
|
||||
*
|
||||
* @param int $deviceId
|
||||
* @return string|null
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getDeviceLatestWechatLogin(int $deviceId): ?string
|
||||
{
|
||||
return DeviceWechatLoginModel::where(
|
||||
[
|
||||
'companyId' => $this->getUserInfo('companyId'),
|
||||
'deviceId' => $deviceId,
|
||||
'alive' => DeviceWechatLoginModel::ALIVE_WECHAT_ACTIVE
|
||||
]
|
||||
)
|
||||
->value('wechatId');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备绑定的客服信息
|
||||
*
|
||||
* @param int $deviceId
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getWechatCustomerInfo(int $deviceId): array
|
||||
{
|
||||
$curstomer = WechatCustomerModel::field('activity,friendShip')
|
||||
->where(
|
||||
[
|
||||
'companyId' => $this->getUserInfo('companyId'),
|
||||
'wechatId' => $this->getDeviceLatestWechatLogin($deviceId)
|
||||
]
|
||||
)
|
||||
->find();
|
||||
|
||||
return $curstomer ? [
|
||||
'lastUpdateTime' => $curstomer->activity->lastActivityTime ?? '',
|
||||
'thirtyDayMsgCount' => $curstomer->activity->totalMsgCount ?? 0,
|
||||
'totalFriend' => $curstomer->friendShip->totalFriend ?? 0,
|
||||
] : [
|
||||
'lastUpdateTime' => '',
|
||||
'thirtyDayMsgCount' => 0,
|
||||
'totalFriend' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备详情
|
||||
*
|
||||
* @param int $id
|
||||
* @return array
|
||||
*/
|
||||
protected function getDeviceInfo(int $id): array
|
||||
{
|
||||
// 查询设备基础信息与关联的微信账号信息
|
||||
$device = DeviceModel::alias('d')
|
||||
->field([
|
||||
'd.id', 'd.imei', 'd.memo', 'd.alive', 'd.extra'
|
||||
])
|
||||
->find($id);
|
||||
|
||||
if (empty($device)) {
|
||||
throw new \Exception('设备不存在', 404);
|
||||
}
|
||||
|
||||
$device->battery = $this->parseExtraForBattery($device->extra);
|
||||
|
||||
// 删除冗余字段
|
||||
unset($device->extra);
|
||||
|
||||
return $device->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备详情
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$id = $this->request->param('id/d');
|
||||
|
||||
if ($this->getUserInfo('isAdmin') != UserModel::ADMIN_STP) {
|
||||
$this->checkUserDevicePermission($id);
|
||||
}
|
||||
|
||||
return ResponseHelper::success(
|
||||
$this->getDeviceInfo($id) + $this->getWechatCustomerInfo($id)
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function isUpdataWechat()
|
||||
{
|
||||
$id = $this->request->param('id/d');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$newWechat = DeviceWechatLoginModel::alias('a')
|
||||
->field('b.*')
|
||||
->join('wechat_account b', 'a.wechatId = b.wechatId')
|
||||
->where(['a.deviceId' => $id,'a.isTips' => 0,'a.companyId' => $companyId])
|
||||
->order('a.id', 'desc')
|
||||
->find();
|
||||
if (empty($newWechat)){
|
||||
return ResponseHelper::success('','该设备绑定的微信无需迁移',201);
|
||||
}
|
||||
|
||||
$oldWechat = DeviceWechatLoginModel::alias('a')
|
||||
->field('b.*')
|
||||
->join('wechat_account b', 'a.wechatId = b.wechatId')
|
||||
->where(['a.companyId' => $companyId])
|
||||
->where('a.deviceId' ,'<>', $id)
|
||||
->order('a.id', 'desc')
|
||||
->find();
|
||||
if (empty($oldWechat)){
|
||||
return ResponseHelper::success('','该设备绑定的微信无需迁移',201);
|
||||
}else{
|
||||
DeviceWechatLoginModel::where(['deviceId' => $id,'isTips' => 0,'companyId' => $companyId])->update(['isTips' => 1]);;
|
||||
return ResponseHelper::success(['newWechat' => $newWechat,'oldWechat' => $oldWechat]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\device;
|
||||
|
||||
use app\common\model\DeviceHandleLog;
|
||||
use app\common\model\DeviceUser as DeviceUserModel;
|
||||
use app\common\model\User as UserModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
|
||||
/**
|
||||
* 设备管理控制器
|
||||
*/
|
||||
class GetDeviceHandleLogsV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 检查用户是否有权限操作指定设备
|
||||
*
|
||||
* @param int $deviceId
|
||||
* @return void
|
||||
*/
|
||||
protected function checkUserDevicePermission(int $deviceId): void
|
||||
{
|
||||
$where = [
|
||||
'deviceId' => $deviceId,
|
||||
'userId' => $this->getUserInfo('id'),
|
||||
'companyId' => $this->getUserInfo('companyId')
|
||||
];
|
||||
|
||||
$hasPermission = DeviceUserModel::where($where)->count() > 0;
|
||||
|
||||
if (!$hasPermission) {
|
||||
throw new \Exception('您没有权限查看该设备', 403);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询设备操作记录,并关联用户表获取操作人信息
|
||||
*
|
||||
* @param int $deviceId
|
||||
* @return \think\Paginator
|
||||
*/
|
||||
protected function getHandleLogs(int $deviceId): \think\Paginator
|
||||
{
|
||||
return DeviceHandleLog::alias('l')
|
||||
->field([
|
||||
'l.id', 'l.content', 'l.createTime',
|
||||
'u.username'
|
||||
])
|
||||
->leftJoin('users u', 'l.userId = u.id')
|
||||
->where('l.deviceId', $deviceId)
|
||||
->order('l.createTime desc')
|
||||
->paginate($this->request->param('limit/d', 10), false, ['page' => $this->request->param('page/d', 1)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备操作记录
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$deviceId = $this->request->param('id/d');
|
||||
|
||||
if ($this->getUserInfo('isAdmin') != UserModel::ADMIN_STP) {
|
||||
$this->checkUserDevicePermission($deviceId);
|
||||
}
|
||||
|
||||
$logs = $this->getHandleLogs($deviceId);
|
||||
|
||||
return ResponseHelper::success(
|
||||
[
|
||||
'total' => $logs->total(),
|
||||
'list' => $logs->items()
|
||||
]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\device;
|
||||
|
||||
use app\common\model\Device as DeviceModel;
|
||||
use app\common\model\DeviceUser as DeviceUserModel;
|
||||
use app\common\model\DeviceWechatLogin as DeviceWechatLoginModel;
|
||||
use app\common\model\User as UserModel;
|
||||
use app\common\model\WechatCustomer as WechatCustomerModel;
|
||||
use app\common\model\WechatFriendShip as WechatFriendShipModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
|
||||
/**
|
||||
* 设备管理控制器
|
||||
*/
|
||||
class GetDeviceListV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 构建查询条件
|
||||
*
|
||||
* @param array $params
|
||||
* @return array
|
||||
*/
|
||||
protected function makeWhere(array $params = []): array
|
||||
{
|
||||
// 关键词搜索(同时搜索IMEI和备注)
|
||||
if (!empty($keyword = $this->request->param('keyword'))) {
|
||||
$where[] = ['exp', "d.imei LIKE '%{$keyword}%' OR d.memo LIKE '%{$keyword}%'"];
|
||||
}
|
||||
|
||||
// 设备在线状态
|
||||
if (is_numeric($alive = $this->request->param('alive'))) {
|
||||
$where['d.alive'] = $alive;
|
||||
}
|
||||
|
||||
$where['d.companyId'] = $this->getUserInfo('companyId');
|
||||
|
||||
return array_merge($params, $where);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定用户的所有设备ID
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function makeDeviceIdsWhere(): array
|
||||
{
|
||||
$deviceIds = DeviceUserModel::where(
|
||||
[
|
||||
'userId' => $this->getUserInfo('id'),
|
||||
'companyId' => $this->getUserInfo('companyId')
|
||||
]
|
||||
)
|
||||
->column('deviceId');
|
||||
|
||||
if (empty($deviceIds)) {
|
||||
throw new \Exception('请联系管理员绑定设备', 403);
|
||||
}
|
||||
|
||||
$where['d.id'] = array('in', $deviceIds);
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备列表
|
||||
*
|
||||
* @param array $where 查询条件
|
||||
* @return \think\Paginator 分页对象
|
||||
*/
|
||||
protected function getDeviceList(array $where): \think\Paginator
|
||||
{
|
||||
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$query = DeviceModel::alias('d')
|
||||
->field([
|
||||
'd.id', 'd.imei', 'd.memo', 'd.alive',
|
||||
'l.wechatId',
|
||||
'a.nickname', 'a.alias', 'a.avatar', '0 totalFriend'
|
||||
])
|
||||
->join('(SELECT MAX(id) AS id, deviceId FROM ck_device_wechat_login WHERE companyId='.$companyId.' GROUP BY deviceId) dwl_max','dwl_max.deviceId = d.id')
|
||||
->join('device_wechat_login l','l.id = dwl_max.id')
|
||||
->join('wechat_account a', 'l.wechatId = a.wechatId')
|
||||
->order('d.id desc');
|
||||
|
||||
foreach ($where as $key => $value) {
|
||||
if (is_numeric($key) && is_array($value) && isset($value[0]) && $value[0] === 'exp') {
|
||||
$query->whereExp('', $value[1]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$query->where($key, $value);
|
||||
}
|
||||
|
||||
return $query->paginate($this->request->param('limit/d', 10), false, ['page' => $this->request->param('page/d', 1)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备最新登录微信的 wechatId
|
||||
*
|
||||
* @param int $deviceId
|
||||
* @return string|null
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getDeviceLatestWechatLogin(int $deviceId): ?string
|
||||
{
|
||||
return DeviceWechatLoginModel::where(
|
||||
[
|
||||
'companyId' => $this->getUserInfo('companyId'),
|
||||
'deviceId' => $deviceId,
|
||||
'alive' => DeviceWechatLoginModel::ALIVE_WECHAT_ACTIVE
|
||||
]
|
||||
)
|
||||
->value('wechatId');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备绑定的客服信息
|
||||
*
|
||||
* @param string $wechatId
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getWechatCustomerInfo(string $wechatId): int
|
||||
{
|
||||
$curstomer = WechatCustomerModel::field('friendShip')
|
||||
->where(
|
||||
[
|
||||
//'companyId' => $this->getUserInfo('companyId'),
|
||||
'wechatId' => $wechatId
|
||||
]
|
||||
)
|
||||
->find();
|
||||
|
||||
return $curstomer->friendShip->totalFriend ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计微信好友
|
||||
*
|
||||
* @param \think\Paginator $list
|
||||
* @return array
|
||||
*/
|
||||
protected function countFriend(\think\Paginator $list): array
|
||||
{
|
||||
$resultSets = [];
|
||||
|
||||
foreach ($list->items() as $item) {
|
||||
$wechatId = $this->getDeviceLatestWechatLogin($item->id);
|
||||
|
||||
$item->totalFriend = $wechatId ? $this->getWechatCustomerInfo($wechatId) : 0;
|
||||
|
||||
array_push($resultSets, $item->toArray());
|
||||
}
|
||||
|
||||
return $resultSets;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
if ($this->getUserInfo('isAdmin') == UserModel::ADMIN_STP) {
|
||||
$where = $this->makeWhere();
|
||||
$result = $this->getDeviceList($where);
|
||||
}else {
|
||||
//$where = $this->makeWhere( $this->makeDeviceIdsWhere() );
|
||||
$where = $this->makeWhere();
|
||||
$result = $this->getDeviceList($where);
|
||||
}
|
||||
|
||||
return ResponseHelper::success(
|
||||
[
|
||||
'list' => $this->countFriend($result),
|
||||
'total' => $result->total(),
|
||||
]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\device;
|
||||
|
||||
use app\common\model\DeviceTaskconf as DeviceTaskconfModel;
|
||||
use app\common\model\DeviceUser as DeviceUserModel;
|
||||
use app\common\model\User as UserModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use Eison\Utils\Helper\ArrHelper;
|
||||
use library\ResponseHelper;
|
||||
|
||||
/**
|
||||
* 设备管理控制器
|
||||
*/
|
||||
class GetDeviceTaskConfigV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 检查用户是否有权限操作指定设备
|
||||
*
|
||||
* @param int $deviceId
|
||||
* @return void
|
||||
*/
|
||||
protected function checkUserDevicePermission(int $deviceId): void
|
||||
{
|
||||
$hasPermission = DeviceUserModel::where(
|
||||
[
|
||||
'deviceId' => $deviceId,
|
||||
'userId' => $this->getUserInfo('id'),
|
||||
'companyId' => $this->getUserInfo('companyId')
|
||||
]
|
||||
)
|
||||
->count() > 0;
|
||||
|
||||
if (!$hasPermission) {
|
||||
throw new \Exception('您没有权限查看该设备', 403);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析taskConfig字段获取功能开关
|
||||
*
|
||||
* @param int $deviceId
|
||||
* @return int[]
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getTaskConfig(int $deviceId): array
|
||||
{
|
||||
$conf = DeviceTaskconfModel::alias('c')
|
||||
->field([
|
||||
'c.autoAddFriend', 'c.autoReply', 'c.momentsSync', 'c.aiChat'
|
||||
])
|
||||
->where(
|
||||
[
|
||||
'companyId' => $this->getUserInfo('companyId'),
|
||||
'deviceId' => $deviceId
|
||||
]
|
||||
)
|
||||
->find();
|
||||
|
||||
// 未配置时赋予默认关闭的状态
|
||||
return !is_null($conf) ? $conf->toArray() : ArrHelper::getValue('autoAddFriend,autoReply,momentsSync,aiChat', [], 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备详情
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$id = $this->request->param('id/d');
|
||||
|
||||
if ($this->getUserInfo('isAdmin') != UserModel::ADMIN_STP) {
|
||||
$this->checkUserDevicePermission($id);
|
||||
}
|
||||
|
||||
return ResponseHelper::success(
|
||||
$this->getTaskConfig($id)
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\device;
|
||||
|
||||
use app\cunkebao\controller\BaseController;
|
||||
|
||||
/**
|
||||
* 设备管理控制器
|
||||
*/
|
||||
class PostAddDeviceV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 添加设备
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
exit('暂未支持');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\device;
|
||||
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
|
||||
/**
|
||||
* 设备管理控制器
|
||||
*/
|
||||
class RefreshDeviceDetailV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 刷新设备
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
// TODO: 实现实际刷新设备状态的功能
|
||||
return ResponseHelper::success();
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\device;
|
||||
|
||||
use app\common\model\Device as DeviceModel;
|
||||
use app\common\model\DeviceHandleLog as DeviceHandleLogModel;
|
||||
use app\common\model\DeviceTaskconf;
|
||||
use app\common\model\DeviceUser as DeviceUserModel;
|
||||
use app\common\model\User as UserModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 设备管理控制器
|
||||
*/
|
||||
class UpdateDeviceTaskConfigV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 先获取设备信息,确认设备存在且未删除
|
||||
*
|
||||
* @param int $deviceId
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function checkDeviceExists(int $deviceId)
|
||||
{
|
||||
$where = [
|
||||
'deviceId' => $deviceId,
|
||||
'companyId' => $this->getUserInfo('companyId'),
|
||||
];
|
||||
|
||||
$device = DeviceModel::find($where);
|
||||
|
||||
if (!$device) {
|
||||
throw new \Exception('设备不存在或已删除', 404);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否有权限操作指定设备
|
||||
*
|
||||
* @param int $deviceId
|
||||
* @return void
|
||||
*/
|
||||
protected function checkUserDevicePermission(int $deviceId): void
|
||||
{
|
||||
$where = [
|
||||
'deviceId' => $deviceId,
|
||||
'userId' => $this->getUserInfo('id'),
|
||||
'companyId' => $this->getUserInfo('companyId')
|
||||
];
|
||||
|
||||
$hasPermission = DeviceUserModel::where($where)->count() > 0;
|
||||
|
||||
if (!$hasPermission) {
|
||||
throw new \Exception('您没有权限操作该设备', 403);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加设备操作日志
|
||||
*
|
||||
* @param int $deviceId
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function addHandleLog(int $deviceId): void
|
||||
{
|
||||
$data = $this->request->post();
|
||||
$content = null;
|
||||
|
||||
if (isset($data['autoAddFriend']))/**/ $content = $data['autoAddFriend'] ? '开启自动添加好友' : '关闭自动添加好友';
|
||||
if (isset($data['autoReply']))/* */ $content = $data['autoReply'] ? '开启自动回复' : '关闭自动回复';
|
||||
if (isset($data['momentsSync']))/* */ $content = $data['momentsSync'] ? '开启朋友圈同步' : '关闭朋友圈同步';
|
||||
if (isset($data['aiChat']))/* */ $content = $data['aiChat'] ? '开启AI会话' : '关闭AI会话';
|
||||
|
||||
if (empty($content)) {
|
||||
throw new \Exception('参数错误', 400);
|
||||
}
|
||||
|
||||
DeviceHandleLogModel::addLog(
|
||||
[
|
||||
'deviceId' => $deviceId,
|
||||
'content' => $content,
|
||||
'userId' => $this->getUserInfo('id'),
|
||||
'companyId' => $this->getUserInfo('companyId'),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新设备taskConfig字段
|
||||
*
|
||||
* @param int $deviceId
|
||||
* @return void
|
||||
*/
|
||||
protected function setTaskconf(int $deviceId): void
|
||||
{
|
||||
$data = $this->request->post();
|
||||
$conf = DeviceTaskconf::where('deviceId', $deviceId)->find();
|
||||
|
||||
if ($conf) {
|
||||
DeviceTaskconf::where('deviceId', $deviceId)->update($data);
|
||||
} else {
|
||||
DeviceTaskconf::create(array_merge($data, [
|
||||
'companyId' => $this->getUserInfo('companyId'),
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新设备任务配置
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$id = $this->request->param('deviceId/d');
|
||||
|
||||
$this->checkDeviceExists($id);
|
||||
|
||||
if ($this->getUserInfo('isAdmin') != UserModel::ADMIN_STP) {
|
||||
$this->checkUserDevicePermission($id);
|
||||
}
|
||||
|
||||
try {
|
||||
Db::startTrans();
|
||||
|
||||
$this->addHandleLog($id);
|
||||
$this->setTaskconf($id);
|
||||
|
||||
Db::commit();
|
||||
|
||||
return ResponseHelper::success();
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
1454
application/cunkebao/controller/distribution/ChannelController.php
Normal file
1454
application/cunkebao/controller/distribution/ChannelController.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,722 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\distribution;
|
||||
|
||||
use app\cunkebao\model\DistributionChannel;
|
||||
use app\cunkebao\model\DistributionWithdrawal;
|
||||
use app\common\util\JwtUtil;
|
||||
use think\Controller;
|
||||
use think\Db;
|
||||
use think\Exception;
|
||||
|
||||
/**
|
||||
* 分销渠道用户端控制器
|
||||
* 用户通过渠道编码访问,无需JWT认证
|
||||
*/
|
||||
class ChannelUserController extends Controller
|
||||
{
|
||||
/**
|
||||
* 初始化方法,设置跨域响应头
|
||||
*/
|
||||
protected function initialize()
|
||||
{
|
||||
parent::initialize();
|
||||
|
||||
// 处理OPTIONS预检请求
|
||||
if ($this->request->method(true) == 'OPTIONS') {
|
||||
$origin = $this->request->header('origin', '*');
|
||||
header("Access-Control-Allow-Origin: " . $origin);
|
||||
header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Authorization, Cookie");
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS, PATCH');
|
||||
header("Access-Control-Allow-Credentials: true");
|
||||
header("Access-Control-Max-Age: 86400");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置跨域响应头
|
||||
* @param \think\response\Json $response
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
protected function setCorsHeaders($response)
|
||||
{
|
||||
$origin = $this->request->header('origin', '*');
|
||||
$response->header([
|
||||
'Access-Control-Allow-Origin' => $origin,
|
||||
'Access-Control-Allow-Headers' => 'Origin, X-Requested-With, Content-Type, Accept, Authorization, Cookie',
|
||||
'Access-Control-Allow-Methods' => 'GET, POST, PUT, DELETE, OPTIONS, PATCH',
|
||||
'Access-Control-Allow-Credentials' => 'true',
|
||||
'Access-Control-Max-Age' => '86400',
|
||||
]);
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渠道登录
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function login()
|
||||
{
|
||||
try {
|
||||
// 获取参数
|
||||
$phone = $this->request->param('phone', '');
|
||||
$password = $this->request->param('password', '');
|
||||
$companyId = $this->request->param('companyId', 0);
|
||||
// 参数验证
|
||||
if (empty($phone)) {
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => 400,
|
||||
'success' => false,
|
||||
'msg' => '手机号不能为空',
|
||||
'data' => null
|
||||
]));
|
||||
}
|
||||
|
||||
if (empty($password)) {
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => 400,
|
||||
'success' => false,
|
||||
'msg' => '密码不能为空',
|
||||
'data' => null
|
||||
]));
|
||||
}
|
||||
|
||||
// 查询渠道信息(通过手机号)
|
||||
$channel = Db::name('distribution_channel')
|
||||
->where([
|
||||
['phone', '=', $phone],
|
||||
['companyId', '=', $companyId],
|
||||
['deleteTime', '=', 0]
|
||||
])
|
||||
->find();
|
||||
|
||||
|
||||
if (!$channel) {
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => 404,
|
||||
'success' => false,
|
||||
'msg' => '渠道不存在',
|
||||
'data' => null
|
||||
]));
|
||||
}
|
||||
|
||||
// 检查渠道状态
|
||||
if ($channel['status'] !== DistributionChannel::STATUS_ENABLED) {
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => 403,
|
||||
'success' => false,
|
||||
'msg' => '渠道已被禁用',
|
||||
'data' => null
|
||||
]));
|
||||
}
|
||||
|
||||
// 验证密码(MD5加密)
|
||||
$passwordMd5 = md5($password);
|
||||
if ($channel['password'] !== $passwordMd5) {
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => 401,
|
||||
'success' => false,
|
||||
'msg' => '密码错误',
|
||||
'data' => null
|
||||
]));
|
||||
}
|
||||
|
||||
// 准备token载荷(不包含密码)
|
||||
$payload = [
|
||||
'id' => $channel['id'],
|
||||
'channelId' => $channel['id'],
|
||||
'channelCode' => $channel['code'],
|
||||
'channelName' => $channel['name'],
|
||||
'companyId' => $channel['companyId'],
|
||||
'type' => 'channel', // 标识这是渠道登录
|
||||
];
|
||||
|
||||
// 生成JWT令牌(30天有效期)
|
||||
$expire = 86400 * 30;
|
||||
$token = JwtUtil::createToken($payload, $expire);
|
||||
$tokenExpired = time() + $expire;
|
||||
|
||||
// 更新最后登录时间(可选)
|
||||
Db::name('distribution_channel')
|
||||
->where('id', $channel['id'])
|
||||
->update([
|
||||
'updateTime' => time()
|
||||
]);
|
||||
|
||||
// 返回数据(不包含密码)
|
||||
$data = [
|
||||
'token' => $token,
|
||||
'tokenExpired' => $tokenExpired,
|
||||
'channelInfo' => [
|
||||
'id' => (string)$channel['id'],
|
||||
'channelCode' => $channel['code'],
|
||||
'channelName' => $channel['name'],
|
||||
'phone' => $channel['phone'] ?: '',
|
||||
'wechatId' => $channel['wechatId'] ?: '',
|
||||
'companyId' => (int)$channel['companyId'], // 返回companyId,方便小程序自动跳转
|
||||
'status' => $channel['status'],
|
||||
'totalCustomers' => (int)$channel['totalCustomers'],
|
||||
'todayCustomers' => (int)$channel['todayCustomers'],
|
||||
'totalFriends' => (int)$channel['totalFriends'],
|
||||
'todayFriends' => (int)$channel['todayFriends'],
|
||||
'withdrawableAmount' => round(($channel['withdrawableAmount'] ?? 0) / 100, 2), // 分转元
|
||||
]
|
||||
];
|
||||
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => 200,
|
||||
'success' => true,
|
||||
'msg' => '登录成功',
|
||||
'data' => $data
|
||||
]));
|
||||
|
||||
} catch (Exception $e) {
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => $e->getCode() ?: 500,
|
||||
'success' => false,
|
||||
'msg' => '登录失败:' . $e->getMessage(),
|
||||
'data' => null
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取渠道首页数据
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
// 获取参数
|
||||
$channelCode = $this->request->param('channelCode', '');
|
||||
|
||||
// 参数验证
|
||||
if (empty($channelCode)) {
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => 400,
|
||||
'success' => false,
|
||||
'msg' => '渠道编码不能为空',
|
||||
'data' => null
|
||||
]));
|
||||
}
|
||||
|
||||
// 查询渠道信息
|
||||
$channel = Db::name('distribution_channel')
|
||||
->where([
|
||||
['code', '=', $channelCode],
|
||||
['status', '=', DistributionChannel::STATUS_ENABLED],
|
||||
['deleteTime', '=', 0]
|
||||
])
|
||||
->find();
|
||||
|
||||
if (!$channel) {
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => 404,
|
||||
'success' => false,
|
||||
'msg' => '渠道不存在或已被禁用',
|
||||
'data' => null
|
||||
]));
|
||||
}
|
||||
|
||||
$channelId = $channel['id'];
|
||||
$companyId = $channel['companyId'];
|
||||
|
||||
// 1. 渠道基本信息
|
||||
$channelInfo = [
|
||||
'channelName' => $channel['name'] ?? '',
|
||||
'channelCode' => $channel['code'] ?? '',
|
||||
'phone' => $channel['phone'] ?? '',
|
||||
'wechatId' => $channel['wechatId'] ?? '',
|
||||
'remark' => $channel['remark'] ?? '',
|
||||
'createTime' => !empty($channel['createTime']) ? date('Y-m-d H:i:s', $channel['createTime']) : '',
|
||||
'createType' => $channel['createType'] ?? '',
|
||||
];
|
||||
|
||||
// 2. 财务统计
|
||||
// 当前可提现金额
|
||||
$withdrawableAmount = round(($channel['withdrawableAmount'] ?? 0) / 100, 2); // 分转元
|
||||
|
||||
// 已提现金额(已打款的提现申请)
|
||||
$withdrawnAmount = Db::name('distribution_withdrawal')
|
||||
->where([
|
||||
['companyId', '=', $companyId],
|
||||
['channelId', '=', $channelId],
|
||||
['status', '=', DistributionWithdrawal::STATUS_PAID]
|
||||
])
|
||||
->sum('amount');
|
||||
$withdrawnAmount = round(($withdrawnAmount ?? 0) / 100, 2); // 分转元
|
||||
|
||||
// 待审核金额(待审核的提现申请)
|
||||
$pendingReviewAmount = Db::name('distribution_withdrawal')
|
||||
->where([
|
||||
['companyId', '=', $companyId],
|
||||
['channelId', '=', $channelId],
|
||||
['status', '=', DistributionWithdrawal::STATUS_PENDING]
|
||||
])
|
||||
->sum('amount');
|
||||
$pendingReviewAmount = round(($pendingReviewAmount ?? 0) / 100, 2); // 分转元
|
||||
|
||||
// 总收益(所有收益记录的总和)
|
||||
$totalRevenue = Db::name('distribution_revenue_record')
|
||||
->where([
|
||||
['companyId', '=', $companyId],
|
||||
['channelId', '=', $channelId]
|
||||
])
|
||||
->sum('amount');
|
||||
$totalRevenue = round(($totalRevenue ?? 0) / 100, 2); // 分转元
|
||||
|
||||
$financialStats = [
|
||||
'withdrawableAmount' => $withdrawableAmount, // 当前可提现金额
|
||||
'totalRevenue' => $totalRevenue, // 总收益
|
||||
'pendingReview' => $pendingReviewAmount, // 待审核
|
||||
'withdrawn' => $withdrawnAmount, // 已提现
|
||||
];
|
||||
|
||||
// 3. 客户和好友统计
|
||||
$customerStats = [
|
||||
'totalFriends' => (int)($channel['totalFriends'] ?? 0), // 总加好友数
|
||||
'todayFriends' => (int)($channel['todayFriends'] ?? 0), // 今日加好友数
|
||||
'totalCustomers' => (int)($channel['totalCustomers'] ?? 0), // 总获客数
|
||||
'todayCustomers' => (int)($channel['todayCustomers'] ?? 0), // 今日获客数
|
||||
];
|
||||
|
||||
// 返回数据
|
||||
$data = [
|
||||
'channelInfo' => $channelInfo,
|
||||
'financialStats' => $financialStats,
|
||||
'customerStats' => $customerStats,
|
||||
];
|
||||
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => 200,
|
||||
'success' => true,
|
||||
'msg' => '获取成功',
|
||||
'data' => $data
|
||||
]));
|
||||
|
||||
} catch (Exception $e) {
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => $e->getCode() ?: 500,
|
||||
'success' => false,
|
||||
'msg' => '获取数据失败:' . $e->getMessage(),
|
||||
'data' => null
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取收益明细列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function revenueRecords()
|
||||
{
|
||||
try {
|
||||
// 获取参数
|
||||
$channelCode = $this->request->param('channelCode', '');
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$type = $this->request->param('type', 'all'); // all, customer_acquisition, add_friend, order, poster, phone, other
|
||||
$date = $this->request->param('date', ''); // 日期筛选,格式:Y-m-d
|
||||
|
||||
// 参数验证
|
||||
if (empty($channelCode)) {
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => 400,
|
||||
'success' => false,
|
||||
'msg' => '渠道编码不能为空',
|
||||
'data' => null
|
||||
]));
|
||||
}
|
||||
|
||||
$page = max(1, intval($page));
|
||||
$limit = max(1, min(100, intval($limit)));
|
||||
|
||||
// 查询渠道信息
|
||||
$channel = Db::name('distribution_channel')
|
||||
->where([
|
||||
['code', '=', $channelCode],
|
||||
['status', '=', DistributionChannel::STATUS_ENABLED],
|
||||
['deleteTime', '=', 0]
|
||||
])
|
||||
->find();
|
||||
|
||||
if (!$channel) {
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => 404,
|
||||
'success' => false,
|
||||
'msg' => '渠道不存在或已被禁用',
|
||||
'data' => null
|
||||
]));
|
||||
}
|
||||
|
||||
$channelId = $channel['id'];
|
||||
$companyId = $channel['companyId'];
|
||||
|
||||
// 构建查询条件
|
||||
$where = [
|
||||
['companyId', '=', $companyId],
|
||||
['channelId', '=', $channelId]
|
||||
];
|
||||
|
||||
// 类型筛选
|
||||
if ($type !== 'all') {
|
||||
$where[] = ['type', '=', $type];
|
||||
}
|
||||
|
||||
// 日期筛选
|
||||
if (!empty($date)) {
|
||||
$dateStart = strtotime($date . ' 00:00:00');
|
||||
$dateEnd = strtotime($date . ' 23:59:59');
|
||||
if ($dateStart && $dateEnd) {
|
||||
$where[] = ['createTime', 'between', [$dateStart, $dateEnd]];
|
||||
}
|
||||
}
|
||||
|
||||
// 查询总数
|
||||
$total = Db::name('distribution_revenue_record')
|
||||
->where($where)
|
||||
->count();
|
||||
|
||||
// 查询列表(按创建时间倒序)
|
||||
$list = Db::name('distribution_revenue_record')
|
||||
->where($where)
|
||||
->order('createTime DESC')
|
||||
->page($page, $limit)
|
||||
->select();
|
||||
|
||||
// 从活动表(customer_acquisition_task)获取类型标签映射(使用 sourceId 关联活动ID)
|
||||
$formattedList = [];
|
||||
if (!empty($list)) {
|
||||
// 收集本页涉及到的活动ID
|
||||
$taskIds = [];
|
||||
foreach ($list as $row) {
|
||||
if (!empty($row['sourceId'])) {
|
||||
$taskIds[] = (int)$row['sourceId'];
|
||||
}
|
||||
}
|
||||
$taskIds = array_values(array_unique($taskIds));
|
||||
|
||||
// 获取活动名称映射:taskId => name
|
||||
$taskNameMap = [];
|
||||
if (!empty($taskIds)) {
|
||||
$taskNameMap = Db::name('customer_acquisition_task')
|
||||
->whereIn('id', $taskIds)
|
||||
->column('name', 'id');
|
||||
}
|
||||
|
||||
// 格式化数据
|
||||
foreach ($list as $item) {
|
||||
$taskId = !empty($item['sourceId']) ? (int)$item['sourceId'] : 0;
|
||||
$taskName = $taskId && isset($taskNameMap[$taskId]) ? $taskNameMap[$taskId] : null;
|
||||
|
||||
$formattedItem = [
|
||||
'id' => (string)$item['id'],
|
||||
'sourceType' => $item['sourceType'] ?? '其他',
|
||||
'type' => $item['type'] ?? 'other',
|
||||
// 类型标签优先取活动名称,没有则回退为 sourceType 或 “其他”
|
||||
'typeLabel' => $taskName ?: (!empty($item['sourceType']) ? $item['sourceType'] : '其他'),
|
||||
'amount' => round($item['amount'] / 100, 2), // 分转元
|
||||
'remark' => isset($item['remark']) && $item['remark'] !== '' ? $item['remark'] : null,
|
||||
'createTime' => !empty($item['createTime']) ? date('Y-m-d H:i', $item['createTime']) : '',
|
||||
];
|
||||
$formattedList[] = $formattedItem;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => 200,
|
||||
'success' => true,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'list' => $formattedList,
|
||||
'total' => (int)$total,
|
||||
'page' => $page,
|
||||
'limit' => $limit
|
||||
]
|
||||
]));
|
||||
|
||||
} catch (Exception $e) {
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => $e->getCode() ?: 500,
|
||||
'success' => false,
|
||||
'msg' => '获取收益明细失败:' . $e->getMessage(),
|
||||
'data' => null
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取提现明细列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function withdrawalRecords()
|
||||
{
|
||||
try {
|
||||
// 获取参数
|
||||
$channelCode = $this->request->param('channelCode', '');
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$status = $this->request->param('status', 'all'); // all, pending, approved, rejected, paid
|
||||
$payType = $this->request->param('payType', 'all'); // all, wechat, alipay, bankcard
|
||||
$date = $this->request->param('date', ''); // 日期筛选,格式:Y-m-d
|
||||
|
||||
// 参数验证
|
||||
if (empty($channelCode)) {
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => 400,
|
||||
'success' => false,
|
||||
'msg' => '渠道编码不能为空',
|
||||
'data' => null
|
||||
]));
|
||||
}
|
||||
|
||||
$page = max(1, intval($page));
|
||||
$limit = max(1, min(100, intval($limit)));
|
||||
|
||||
// 校验到账方式参数
|
||||
$validPayTypes = ['all', 'wechat', 'alipay', 'bankcard'];
|
||||
if (!in_array($payType, $validPayTypes)) {
|
||||
$payType = 'all';
|
||||
}
|
||||
|
||||
// 查询渠道信息
|
||||
$channel = Db::name('distribution_channel')
|
||||
->where([
|
||||
['code', '=', $channelCode],
|
||||
['status', '=', DistributionChannel::STATUS_ENABLED],
|
||||
['deleteTime', '=', 0]
|
||||
])
|
||||
->find();
|
||||
|
||||
if (!$channel) {
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => 404,
|
||||
'success' => false,
|
||||
'msg' => '渠道不存在或已被禁用',
|
||||
'data' => null
|
||||
]));
|
||||
}
|
||||
|
||||
$channelId = $channel['id'];
|
||||
$companyId = $channel['companyId'];
|
||||
|
||||
// 构建查询条件
|
||||
$where = [
|
||||
['companyId', '=', $companyId],
|
||||
['channelId', '=', $channelId]
|
||||
];
|
||||
|
||||
// 状态筛选
|
||||
if ($status !== 'all') {
|
||||
$where[] = ['status', '=', $status];
|
||||
}
|
||||
|
||||
// 到账方式筛选
|
||||
if ($payType !== 'all') {
|
||||
$where[] = ['payType', '=', $payType];
|
||||
}
|
||||
|
||||
// 日期筛选
|
||||
if (!empty($date)) {
|
||||
$dateStart = strtotime($date . ' 00:00:00');
|
||||
$dateEnd = strtotime($date . ' 23:59:59');
|
||||
if ($dateStart && $dateEnd) {
|
||||
$where[] = ['applyTime', 'between', [$dateStart, $dateEnd]];
|
||||
}
|
||||
}
|
||||
|
||||
// 查询总数
|
||||
$total = Db::name('distribution_withdrawal')
|
||||
->where($where)
|
||||
->count();
|
||||
|
||||
// 查询列表(按申请时间倒序)
|
||||
$list = Db::name('distribution_withdrawal')
|
||||
->where($where)
|
||||
->order('applyTime DESC')
|
||||
->page($page, $limit)
|
||||
->select();
|
||||
|
||||
// 格式化数据
|
||||
$formattedList = [];
|
||||
foreach ($list as $item) {
|
||||
// 状态标签映射
|
||||
$statusLabels = [
|
||||
'pending' => '待审核',
|
||||
'approved' => '已通过',
|
||||
'rejected' => '已拒绝',
|
||||
'paid' => '已打款'
|
||||
];
|
||||
|
||||
// 支付类型标签映射
|
||||
$payTypeLabels = [
|
||||
'wechat' => '微信',
|
||||
'alipay' => '支付宝',
|
||||
'bankcard' => '银行卡'
|
||||
];
|
||||
|
||||
$payType = !empty($item['payType']) ? $item['payType'] : null;
|
||||
|
||||
$formattedItem = [
|
||||
'id' => (string)$item['id'],
|
||||
'amount' => round($item['amount'] / 100, 2), // 分转元
|
||||
'status' => $item['status'] ?? 'pending',
|
||||
'statusLabel' => $statusLabels[$item['status'] ?? 'pending'] ?? '待审核',
|
||||
'payType' => $payType,
|
||||
'payTypeLabel' => $payType && isset($payTypeLabels[$payType]) ? $payTypeLabels[$payType] : null,
|
||||
'applyTime' => !empty($item['applyTime']) ? date('Y-m-d H:i', $item['applyTime']) : '',
|
||||
'reviewTime' => !empty($item['reviewTime']) ? date('Y-m-d H:i', $item['reviewTime']) : null,
|
||||
'reviewer' => !empty($item['reviewer']) ? $item['reviewer'] : null,
|
||||
'remark' => !empty($item['remark']) ? $item['remark'] : null,
|
||||
];
|
||||
$formattedList[] = $formattedItem;
|
||||
}
|
||||
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => 200,
|
||||
'success' => true,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'list' => $formattedList,
|
||||
'total' => (int)$total,
|
||||
'page' => $page,
|
||||
'limit' => $limit
|
||||
]
|
||||
]));
|
||||
|
||||
} catch (Exception $e) {
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => $e->getCode() ?: 500,
|
||||
'success' => false,
|
||||
'msg' => '获取提现明细失败:' . $e->getMessage(),
|
||||
'data' => null
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改渠道分销员密码
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function changePassword()
|
||||
{
|
||||
try {
|
||||
// 获取参数并去除首尾空格
|
||||
$channelCode = trim($this->request->param('channelCode', ''));
|
||||
$oldPassword = trim($this->request->param('oldPassword', ''));
|
||||
$newPassword = trim($this->request->param('newPassword', ''));
|
||||
|
||||
// 参数验证
|
||||
if (empty($channelCode)) {
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => 400,
|
||||
'success' => false,
|
||||
'msg' => '渠道编码不能为空',
|
||||
'data' => null
|
||||
]));
|
||||
}
|
||||
|
||||
if (empty($oldPassword)) {
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => 400,
|
||||
'success' => false,
|
||||
'msg' => '原密码不能为空',
|
||||
'data' => null
|
||||
]));
|
||||
}
|
||||
|
||||
if (empty($newPassword)) {
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => 400,
|
||||
'success' => false,
|
||||
'msg' => '新密码不能为空',
|
||||
'data' => null
|
||||
]));
|
||||
}
|
||||
|
||||
// 验证新密码长度(至少6位)
|
||||
if (mb_strlen($newPassword) < 6) {
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => 400,
|
||||
'success' => false,
|
||||
'msg' => '新密码长度至少为6位',
|
||||
'data' => null
|
||||
]));
|
||||
}
|
||||
|
||||
// 查询渠道信息
|
||||
$channel = Db::name('distribution_channel')
|
||||
->where([
|
||||
['code', '=', $channelCode],
|
||||
['status', '=', DistributionChannel::STATUS_ENABLED],
|
||||
['deleteTime', '=', 0]
|
||||
])
|
||||
->find();
|
||||
|
||||
if (!$channel) {
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => 404,
|
||||
'success' => false,
|
||||
'msg' => '渠道不存在或已被禁用',
|
||||
'data' => null
|
||||
]));
|
||||
}
|
||||
|
||||
// 验证原密码(MD5加密)
|
||||
$oldPasswordMd5 = md5($oldPassword);
|
||||
if ($channel['password'] !== $oldPasswordMd5) {
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => 401,
|
||||
'success' => false,
|
||||
'msg' => '原密码错误',
|
||||
'data' => null
|
||||
]));
|
||||
}
|
||||
|
||||
// 检查新密码是否与原密码相同
|
||||
$newPasswordMd5 = md5($newPassword);
|
||||
if ($channel['password'] === $newPasswordMd5) {
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => 400,
|
||||
'success' => false,
|
||||
'msg' => '新密码不能与原密码相同',
|
||||
'data' => null
|
||||
]));
|
||||
}
|
||||
|
||||
// 更新密码
|
||||
$updateResult = Db::name('distribution_channel')
|
||||
->where('id', $channel['id'])
|
||||
->update([
|
||||
'password' => $newPasswordMd5,
|
||||
'updateTime' => time()
|
||||
]);
|
||||
|
||||
if ($updateResult === false) {
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => 500,
|
||||
'success' => false,
|
||||
'msg' => '密码修改失败,请稍后重试',
|
||||
'data' => null
|
||||
]));
|
||||
}
|
||||
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => 200,
|
||||
'success' => true,
|
||||
'msg' => '密码修改成功',
|
||||
'data' => null
|
||||
]));
|
||||
|
||||
} catch (Exception $e) {
|
||||
return $this->setCorsHeaders(json([
|
||||
'code' => $e->getCode() ?: 500,
|
||||
'success' => false,
|
||||
'msg' => '密码修改失败:' . $e->getMessage(),
|
||||
'data' => null
|
||||
]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,692 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\distribution;
|
||||
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use app\cunkebao\model\DistributionWithdrawal;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
use think\Exception;
|
||||
|
||||
/**
|
||||
* 分销渠道提现申请控制器
|
||||
*/
|
||||
class WithdrawalController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取提现申请列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
// 获取参数
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 20);
|
||||
$status = $this->request->param('status', 'all');
|
||||
$date = $this->request->param('date', '');
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
// 参数验证
|
||||
$page = max(1, intval($page));
|
||||
$limit = max(1, min(100, intval($limit))); // 限制最大100
|
||||
|
||||
// 验证状态参数
|
||||
$validStatuses = ['all', DistributionWithdrawal::STATUS_PENDING, DistributionWithdrawal::STATUS_APPROVED, DistributionWithdrawal::STATUS_REJECTED, DistributionWithdrawal::STATUS_PAID];
|
||||
if (!in_array($status, $validStatuses)) {
|
||||
$status = 'all';
|
||||
}
|
||||
|
||||
// 构建查询条件
|
||||
$where = [];
|
||||
$where[] = ['w.companyId', '=', $companyId];
|
||||
|
||||
// 如果不是管理员,只能查看自己创建的提现申请
|
||||
if (!$this->getUserInfo('isAdmin')) {
|
||||
$where[] = ['w.userId', '=', $this->getUserInfo('id')];
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if ($status !== 'all') {
|
||||
$where[] = ['w.status', '=', $status];
|
||||
}
|
||||
|
||||
// 日期筛选(格式:YYYY/MM/DD)
|
||||
if (!empty($date)) {
|
||||
// 转换日期格式 YYYY/MM/DD 为时间戳范围
|
||||
$dateParts = explode('/', $date);
|
||||
if (count($dateParts) === 3) {
|
||||
$dateStr = $dateParts[0] . '-' . $dateParts[1] . '-' . $dateParts[2];
|
||||
$dateStart = strtotime($dateStr . ' 00:00:00');
|
||||
$dateEnd = strtotime($dateStr . ' 23:59:59');
|
||||
if ($dateStart && $dateEnd) {
|
||||
$where[] = ['w.applyTime', 'between', [$dateStart, $dateEnd]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 关键词搜索(模糊匹配渠道名称、渠道编码)
|
||||
if (!empty($keyword)) {
|
||||
$keyword = trim($keyword);
|
||||
// 需要关联渠道表进行搜索
|
||||
}
|
||||
|
||||
// 构建查询(关联渠道表获取渠道名称和编码,只关联未删除的渠道)
|
||||
$query = Db::name('distribution_withdrawal')
|
||||
->alias('w')
|
||||
->join('distribution_channel c', 'w.channelId = c.id AND c.deleteTime = 0', 'left')
|
||||
->where($where);
|
||||
|
||||
// 关键词搜索(如果有关键词,添加渠道表关联条件)
|
||||
if (!empty($keyword)) {
|
||||
$query->where(function ($query) use ($keyword) {
|
||||
$query->where('c.name', 'like', '%' . $keyword . '%')
|
||||
->whereOr('c.code', 'like', '%' . $keyword . '%');
|
||||
});
|
||||
}
|
||||
|
||||
// 查询总数
|
||||
$total = $query->count();
|
||||
|
||||
// 查询列表(按申请时间倒序)
|
||||
$list = $query->field([
|
||||
'w.id',
|
||||
'w.channelId',
|
||||
'w.userId',
|
||||
'w.amount',
|
||||
'w.status',
|
||||
'w.payType',
|
||||
'w.applyTime',
|
||||
'w.reviewTime',
|
||||
'w.reviewer',
|
||||
'w.remark',
|
||||
'c.name as channelName',
|
||||
'c.code as channelCode'
|
||||
])
|
||||
->order('w.applyTime DESC')
|
||||
->page($page, $limit)
|
||||
->select();
|
||||
|
||||
// 格式化数据
|
||||
$formattedList = [];
|
||||
foreach ($list as $item) {
|
||||
// 格式化申请日期为 YYYY/MM/DD
|
||||
$applyDate = '';
|
||||
if (!empty($item['applyTime'])) {
|
||||
$applyDate = date('Y/m/d', $item['applyTime']);
|
||||
}
|
||||
|
||||
// 格式化审核日期
|
||||
$reviewDate = null;
|
||||
if (!empty($item['reviewTime'])) {
|
||||
$reviewDate = date('Y-m-d H:i:s', $item['reviewTime']);
|
||||
}
|
||||
|
||||
$formattedItem = [
|
||||
'id' => (string)$item['id'],
|
||||
'channelId' => (string)$item['channelId'],
|
||||
'channelName' => $item['channelName'] ?? '',
|
||||
'channelCode' => $item['channelCode'] ?? '',
|
||||
'userId' => (int)($item['userId'] ?? 0),
|
||||
'amount' => round($item['amount'] / 100, 2), // 分转元,保留2位小数
|
||||
'status' => $item['status'] ?? DistributionWithdrawal::STATUS_PENDING,
|
||||
'payType' => !empty($item['payType']) ? $item['payType'] : null, // 支付类型
|
||||
'applyDate' => $applyDate,
|
||||
'reviewDate' => $reviewDate,
|
||||
'reviewer' => !empty($item['reviewer']) ? $item['reviewer'] : null,
|
||||
'remark' => !empty($item['remark']) ? $item['remark'] : null,
|
||||
];
|
||||
$formattedList[] = $formattedItem;
|
||||
}
|
||||
|
||||
// 返回结果
|
||||
return json([
|
||||
'code' => 200,
|
||||
'success' => true,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'list' => $formattedList,
|
||||
'total' => (int)$total
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
return json([
|
||||
'code' => $e->getCode() ?: 500,
|
||||
'success' => false,
|
||||
'msg' => '获取提现申请列表失败:' . $e->getMessage(),
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建提现申请
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
try {
|
||||
// 获取参数(接口接收的金额单位为元)
|
||||
// 原先使用 channelId,现在改为使用渠道编码 channelCode
|
||||
$channelCode = $this->request->param('channelCode', '');
|
||||
$amount = $this->request->param('amount', 0); // 金额单位:元
|
||||
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
// 参数验证
|
||||
if (empty($channelCode)) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'success' => false,
|
||||
'msg' => '渠道编码不能为空',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
// 验证金额(转换为浮点数进行验证)
|
||||
$amount = floatval($amount);
|
||||
if (empty($amount) || $amount <= 0) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'success' => false,
|
||||
'msg' => '提现金额必须大于0',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
// 验证金额格式(最多2位小数)
|
||||
if (!preg_match('/^\d+(\.\d{1,2})?$/', (string)$amount)) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'success' => false,
|
||||
'msg' => '提现金额格式不正确,最多保留2位小数',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
// 检查渠道是否存在且属于当前公司(通过渠道编码查询)
|
||||
$channel = Db::name('distribution_channel')
|
||||
->where([
|
||||
['code', '=', $channelCode],
|
||||
['companyId', '=', $companyId],
|
||||
['deleteTime', '=', 0]
|
||||
])
|
||||
->find();
|
||||
|
||||
if (!$channel) {
|
||||
return json([
|
||||
'code' => 404,
|
||||
'success' => false,
|
||||
'msg' => '渠道不存在或没有权限',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
// 统一使用渠道ID变量,后续逻辑仍然基于 channelId
|
||||
$channelId = $channel['id'];
|
||||
// 从渠道获取创建者的userId,而不是当前登录用户的userId
|
||||
$userId = intval($channel['userId'] ?? 0);
|
||||
|
||||
// 检查渠道状态
|
||||
if ($channel['status'] !== 'enabled') {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'success' => false,
|
||||
'msg' => '渠道已禁用,无法申请提现',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
// 检查可提现金额
|
||||
// 数据库存储的是分,接口接收的是元,需要统一单位进行比较
|
||||
$withdrawableAmountInFen = intval($channel['withdrawableAmount'] ?? 0); // 数据库中的分
|
||||
$withdrawableAmountInYuan = round($withdrawableAmountInFen / 100, 2); // 转换为元用于提示
|
||||
$amountInFen = intval(round($amount * 100)); // 将接口接收的元转换为分
|
||||
|
||||
if ($amountInFen > $withdrawableAmountInFen) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'success' => false,
|
||||
'msg' => '提现金额不能超过可提现金额(' . number_format($withdrawableAmountInYuan, 2) . '元)',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
// 检查是否有待审核的申请
|
||||
$pendingWithdrawal = Db::name('distribution_withdrawal')
|
||||
->where([
|
||||
['channelId', '=', $channelId],
|
||||
['companyId', '=', $companyId],
|
||||
['status', '=', DistributionWithdrawal::STATUS_PENDING]
|
||||
])
|
||||
->find();
|
||||
|
||||
if ($pendingWithdrawal) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'success' => false,
|
||||
'msg' => '该渠道已有待审核的提现申请,请等待审核完成后再申请',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
// 开始事务
|
||||
Db::startTrans();
|
||||
try {
|
||||
// 创建提现申请(金额以分存储)
|
||||
$withdrawalData = [
|
||||
'companyId' => $companyId,
|
||||
'channelId' => $channelId,
|
||||
'userId' => $userId,
|
||||
'amount' => $amountInFen, // 存储为分
|
||||
'status' => DistributionWithdrawal::STATUS_PENDING,
|
||||
'applyTime' => time(),
|
||||
'createTime' => time(),
|
||||
'updateTime' => time(),
|
||||
];
|
||||
|
||||
$withdrawalId = Db::name('distribution_withdrawal')->insertGetId($withdrawalData);
|
||||
|
||||
if (!$withdrawalId) {
|
||||
throw new Exception('创建提现申请失败');
|
||||
}
|
||||
|
||||
// 扣除渠道可提现金额(以分为单位)
|
||||
Db::name('distribution_channel')
|
||||
->where('id', $channelId)
|
||||
->setDec('withdrawableAmount', $amountInFen);
|
||||
|
||||
// 提交事务
|
||||
Db::commit();
|
||||
|
||||
// 获取创建的申请数据
|
||||
$withdrawal = Db::name('distribution_withdrawal')
|
||||
->alias('w')
|
||||
->join('distribution_channel c', 'w.channelId = c.id', 'left')
|
||||
->where('w.id', $withdrawalId)
|
||||
->field([
|
||||
'w.id',
|
||||
'w.channelId',
|
||||
'w.userId',
|
||||
'w.amount',
|
||||
'w.status',
|
||||
'w.payType',
|
||||
'w.applyTime',
|
||||
'c.name as channelName',
|
||||
'c.code as channelCode'
|
||||
])
|
||||
->find();
|
||||
|
||||
// 格式化返回数据(分转元)
|
||||
$result = [
|
||||
'id' => (string)$withdrawal['id'],
|
||||
'channelId' => (string)$withdrawal['channelId'],
|
||||
'channelName' => $withdrawal['channelName'] ?? '',
|
||||
'channelCode' => $withdrawal['channelCode'] ?? '',
|
||||
'userId' => (int)($withdrawal['userId'] ?? 0),
|
||||
'amount' => round($withdrawal['amount'] / 100, 2), // 分转元,保留2位小数
|
||||
'status' => $withdrawal['status'],
|
||||
'payType' => !empty($withdrawal['payType']) ? $withdrawal['payType'] : null, // 支付类型:wechat、alipay、bankcard(创建时为null)
|
||||
'applyDate' => !empty($withdrawal['applyTime']) ? date('Y/m/d', $withdrawal['applyTime']) : '',
|
||||
'reviewDate' => null,
|
||||
'reviewer' => null,
|
||||
'remark' => null,
|
||||
];
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'success' => true,
|
||||
'msg' => '提现申请提交成功',
|
||||
'data' => $result
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
Db::rollback();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
return json([
|
||||
'code' => $e->getCode() ?: 500,
|
||||
'success' => false,
|
||||
'msg' => '提交提现申请失败:' . $e->getMessage(),
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 审核提现申请(通过/拒绝)
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function review()
|
||||
{
|
||||
try {
|
||||
// 获取参数
|
||||
$id = $this->request->param('id', 0);
|
||||
$action = $this->request->param('action', ''); // approve 或 reject
|
||||
$remark = $this->request->param('remark', '');
|
||||
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$reviewer = $this->getUserInfo('username') ?: $this->getUserInfo('account') ?: '系统管理员';
|
||||
|
||||
// 参数验证
|
||||
if (empty($id)) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'success' => false,
|
||||
'msg' => '申请ID不能为空',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
if (!in_array($action, ['approve', 'reject'])) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'success' => false,
|
||||
'msg' => '审核操作参数错误,必须为 approve 或 reject',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
// 如果是拒绝,备注必填
|
||||
if ($action === 'reject' && empty($remark)) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'success' => false,
|
||||
'msg' => '拒绝申请时,拒绝理由不能为空',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
// 检查申请是否存在且属于当前公司
|
||||
$withdrawal = Db::name('distribution_withdrawal')
|
||||
->where([
|
||||
['id', '=', $id],
|
||||
['companyId', '=', $companyId]
|
||||
])
|
||||
->find();
|
||||
|
||||
if (!$withdrawal) {
|
||||
return json([
|
||||
'code' => 404,
|
||||
'success' => false,
|
||||
'msg' => '提现申请不存在或没有权限',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
// 检查申请状态
|
||||
if ($withdrawal['status'] !== DistributionWithdrawal::STATUS_PENDING) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'success' => false,
|
||||
'msg' => '该申请已审核,无法重复审核',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
// 开始事务
|
||||
Db::startTrans();
|
||||
try {
|
||||
$updateData = [
|
||||
'reviewTime' => time(),
|
||||
'reviewer' => $reviewer,
|
||||
'remark' => $remark ?: '',
|
||||
'updateTime' => time(),
|
||||
];
|
||||
|
||||
if ($action === 'approve') {
|
||||
// 审核通过
|
||||
$updateData['status'] = DistributionWithdrawal::STATUS_APPROVED;
|
||||
} else {
|
||||
// 审核拒绝,退回可提现金额(金额以分存储)
|
||||
$updateData['status'] = DistributionWithdrawal::STATUS_REJECTED;
|
||||
|
||||
// 退回渠道可提现金额(以分为单位)
|
||||
Db::name('distribution_channel')
|
||||
->where('id', $withdrawal['channelId'])
|
||||
->setInc('withdrawableAmount', intval($withdrawal['amount']));
|
||||
}
|
||||
|
||||
// 更新申请状态
|
||||
Db::name('distribution_withdrawal')
|
||||
->where('id', $id)
|
||||
->update($updateData);
|
||||
|
||||
// 提交事务
|
||||
Db::commit();
|
||||
|
||||
$msg = $action === 'approve' ? '审核通过成功' : '审核拒绝成功';
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'success' => true,
|
||||
'msg' => $msg,
|
||||
'data' => [
|
||||
'id' => (string)$id,
|
||||
'status' => $updateData['status']
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
Db::rollback();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
return json([
|
||||
'code' => $e->getCode() ?: 500,
|
||||
'success' => false,
|
||||
'msg' => '审核失败:' . $e->getMessage(),
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打款(标记为已打款)
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function markPaid()
|
||||
{
|
||||
try {
|
||||
// 获取参数
|
||||
$id = $this->request->param('id', 0);
|
||||
$payType = $this->request->param('payType', ''); // 支付类型:wechat、alipay、bankcard
|
||||
$remark = $this->request->param('remark', '');
|
||||
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
// 参数验证
|
||||
if (empty($id)) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'success' => false,
|
||||
'msg' => '申请ID不能为空',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
// 验证支付类型
|
||||
$validPayTypes = [
|
||||
DistributionWithdrawal::PAY_TYPE_WECHAT,
|
||||
DistributionWithdrawal::PAY_TYPE_ALIPAY,
|
||||
DistributionWithdrawal::PAY_TYPE_BANKCARD
|
||||
];
|
||||
if (empty($payType) || !in_array($payType, $validPayTypes)) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'success' => false,
|
||||
'msg' => '支付类型不能为空,必须为:wechat(微信)、alipay(支付宝)、bankcard(银行卡)',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
// 检查申请是否存在且属于当前公司
|
||||
$withdrawal = Db::name('distribution_withdrawal')
|
||||
->where([
|
||||
['id', '=', $id],
|
||||
['companyId', '=', $companyId]
|
||||
])
|
||||
->find();
|
||||
|
||||
if (!$withdrawal) {
|
||||
return json([
|
||||
'code' => 404,
|
||||
'success' => false,
|
||||
'msg' => '提现申请不存在或没有权限',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
// 检查申请状态(只有已通过的申请才能打款)
|
||||
if ($withdrawal['status'] !== DistributionWithdrawal::STATUS_APPROVED) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'success' => false,
|
||||
'msg' => '只有已通过的申请才能标记为已打款',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
// 更新状态为已打款
|
||||
$result = Db::name('distribution_withdrawal')
|
||||
->where('id', $id)
|
||||
->update([
|
||||
'status' => DistributionWithdrawal::STATUS_PAID,
|
||||
'payType' => $payType,
|
||||
'remark' => !empty($remark) ? $remark : $withdrawal['remark'],
|
||||
'updateTime' => time()
|
||||
]);
|
||||
|
||||
if ($result === false) {
|
||||
return json([
|
||||
'code' => 500,
|
||||
'success' => false,
|
||||
'msg' => '标记打款失败',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'success' => true,
|
||||
'msg' => '标记打款成功',
|
||||
'data' => [
|
||||
'id' => (string)$id,
|
||||
'status' => DistributionWithdrawal::STATUS_PAID,
|
||||
'payType' => $payType
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
return json([
|
||||
'code' => $e->getCode() ?: 500,
|
||||
'success' => false,
|
||||
'msg' => '标记打款失败:' . $e->getMessage(),
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取提现申请详情
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function detail()
|
||||
{
|
||||
try {
|
||||
// 获取参数
|
||||
$id = $this->request->param('id', 0);
|
||||
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
// 参数验证
|
||||
if (empty($id)) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'success' => false,
|
||||
'msg' => '申请ID不能为空',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
// 构建查询条件
|
||||
$where = [
|
||||
['w.id', '=', $id],
|
||||
['w.companyId', '=', $companyId]
|
||||
];
|
||||
|
||||
// 如果不是管理员,只能查看自己创建的提现申请
|
||||
if (!$this->getUserInfo('isAdmin')) {
|
||||
$where[] = ['w.userId', '=', $this->getUserInfo('id')];
|
||||
}
|
||||
|
||||
// 查询申请详情(关联渠道表)
|
||||
$withdrawal = Db::name('distribution_withdrawal')
|
||||
->alias('w')
|
||||
->join('distribution_channel c', 'w.channelId = c.id AND c.deleteTime = 0', 'left')
|
||||
->where($where)
|
||||
->field([
|
||||
'w.id',
|
||||
'w.channelId',
|
||||
'w.userId',
|
||||
'w.amount',
|
||||
'w.status',
|
||||
'w.payType',
|
||||
'w.applyTime',
|
||||
'w.reviewTime',
|
||||
'w.reviewer',
|
||||
'w.remark',
|
||||
'c.name as channelName',
|
||||
'c.code as channelCode'
|
||||
])
|
||||
->find();
|
||||
|
||||
if (!$withdrawal) {
|
||||
return json([
|
||||
'code' => 404,
|
||||
'success' => false,
|
||||
'msg' => '提现申请不存在或没有权限',
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
|
||||
// 格式化返回数据(分转元)
|
||||
$result = [
|
||||
'id' => (string)$withdrawal['id'],
|
||||
'channelId' => (string)$withdrawal['channelId'],
|
||||
'channelName' => $withdrawal['channelName'] ?? '',
|
||||
'channelCode' => $withdrawal['channelCode'] ?? '',
|
||||
'userId' => (int)($withdrawal['userId'] ?? 0),
|
||||
'amount' => round($withdrawal['amount'] / 100, 2), // 分转元,保留2位小数
|
||||
'status' => $withdrawal['status'],
|
||||
'payType' => !empty($withdrawal['payType']) ? $withdrawal['payType'] : null, // 支付类型:wechat、alipay、bankcard
|
||||
'applyDate' => !empty($withdrawal['applyTime']) ? date('Y/m/d', $withdrawal['applyTime']) : '',
|
||||
'reviewDate' => !empty($withdrawal['reviewTime']) ? date('Y-m-d H:i:s', $withdrawal['reviewTime']) : null,
|
||||
'reviewer' => !empty($withdrawal['reviewer']) ? $withdrawal['reviewer'] : null,
|
||||
'remark' => !empty($withdrawal['remark']) ? $withdrawal['remark'] : null,
|
||||
];
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'success' => true,
|
||||
'msg' => '获取成功',
|
||||
'data' => $result
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
return json([
|
||||
'code' => $e->getCode() ?: 500,
|
||||
'success' => false,
|
||||
'msg' => '获取详情失败:' . $e->getMessage(),
|
||||
'data' => null
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
namespace app\cunkebao\controller\friend;
|
||||
|
||||
use app\common\model\Device as DeviceModel;
|
||||
use app\common\model\DeviceUser as DeviceUserModel;
|
||||
use app\common\model\WechatFriendShip as WechatFriendShipModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use app\api\controller\AutomaticAssign;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 设备管理控制器
|
||||
*/
|
||||
class GetFriendListV1Controller extends BaseController
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* 获取好友列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$page = $this->request->param('page',1);
|
||||
$limit = $this->request->param('limit',20);
|
||||
$keyword = $this->request->param('keyword','');
|
||||
$deviceIds = $this->request->param('deviceIds','');
|
||||
|
||||
if(!empty($deviceIds)){
|
||||
$deviceIds = explode(',',$deviceIds);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
$where = [];
|
||||
if ($this->getUserInfo('isAdmin') == 1) {
|
||||
$where[] = ['isDeleted','=',0];
|
||||
} else {
|
||||
$where[] = ['isDeleted','=',0];
|
||||
}
|
||||
|
||||
if(!empty($keyword)){
|
||||
$where[] = ['nickname|alias|wechatId','like','%'.$keyword.'%'];
|
||||
}
|
||||
|
||||
/* $wechatIds = Db::name('device')->alias('d')
|
||||
->join('device_wechat_login dwl','dwl.deviceId=d.id AND dwl.companyId='.$this->getUserInfo('companyId'))
|
||||
->where(['d.companyId' => $this->getUserInfo('companyId'),'d.deleteTime' => 0])
|
||||
->group('dwl.deviceId')
|
||||
->order('dwl.id desc');*/
|
||||
|
||||
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
$wechatIds = Db::name('device')->alias('d')
|
||||
// 仅关联每个设备在 device_wechat_login 中的最新一条记录
|
||||
->join('(SELECT MAX(id) AS id, deviceId FROM ck_device_wechat_login WHERE companyId='.$companyId.' GROUP BY deviceId) dwl_max','dwl_max.deviceId = d.id')
|
||||
->join('device_wechat_login dwl','dwl.id = dwl_max.id')
|
||||
->where(['d.companyId' => $companyId,'d.deleteTime' => 0]);
|
||||
|
||||
|
||||
if (!empty($deviceIds)){
|
||||
$wechatIds = $wechatIds->where('d.id','in',$deviceIds);
|
||||
}
|
||||
$wechatIds = $wechatIds->column('dwl.wechatId');
|
||||
|
||||
$where[] = ['ownerWechatId','in',$wechatIds];
|
||||
|
||||
$data = Db::table('s2_wechat_friend')
|
||||
->field([
|
||||
'id', 'nickname', 'avatar', 'alias', 'wechatId',
|
||||
'gender', 'phone', 'createTime', 'updateTime', 'deleteTime',
|
||||
'ownerNickname', 'ownerAlias', 'ownerWechatId',
|
||||
'accountUserName', 'accountNickname', 'accountRealName'
|
||||
])
|
||||
->where($where);
|
||||
$total = $data->count();
|
||||
$list = $data->page($page, $limit)->order('id DESC')->select();
|
||||
|
||||
// 格式化时间字段和处理数据
|
||||
$formattedList = [];
|
||||
foreach ($list as $item) {
|
||||
$formattedItem = [
|
||||
'id' => $item['id'],
|
||||
'nickname' => $item['nickname'] ?? '',
|
||||
'avatar' => $item['avatar'] ?? '',
|
||||
'alias' => $item['alias'] ?? '',
|
||||
'wechatId' => $item['wechatId'] ?? '',
|
||||
'gender' => $item['gender'] ?? 0,
|
||||
'phone' => $item['phone'] ?? '',
|
||||
'account' => $item['accountUserName'] ?? '',
|
||||
'username' => $item['accountRealName'] ?? '',
|
||||
'createTime' => !empty($item['createTime']) ? date('Y-m-d H:i:s', $item['createTime']) : '1970-01-01 08:00:00',
|
||||
'updateTime' => !empty($item['updateTime']) ? date('Y-m-d H:i:s', $item['updateTime']) : '1970-01-01 08:00:00',
|
||||
'deleteTime' => !empty($item['deleteTime']) ? date('Y-m-d H:i:s', $item['deleteTime']) : '1970-01-01 08:00:00',
|
||||
'ownerNickname' => $item['ownerNickname'] ?? '',
|
||||
'ownerAlias' => $item['ownerAlias'] ?? '',
|
||||
'ownerWechatId' => $item['ownerWechatId'] ?? '',
|
||||
'accountNickname' => $item['accountNickname'] ?? ''
|
||||
];
|
||||
$formattedList[] = $formattedItem;
|
||||
}
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'list' => $formattedList,
|
||||
'total' => $total,
|
||||
'companyId' => $this->getUserInfo('companyId')
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return json([
|
||||
'code' => $e->getCode(),
|
||||
'msg' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 好友转移
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function transfer()
|
||||
{
|
||||
$friendId = $this->request->param('friendId', 0);
|
||||
$toAccountId = $this->request->param('toAccountId', '');
|
||||
$comment = $this->request->param('comment', '');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
// 参数验证
|
||||
if (empty($friendId)) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'msg' => '好友ID不能为空'
|
||||
]);
|
||||
}
|
||||
|
||||
if (empty($toAccountId)) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'msg' => '目标账号ID不能为空'
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
// 验证目标账号是否存在且属于当前公司
|
||||
$accountInfo = Db::table('s2_company_account')
|
||||
->where('id', $toAccountId)
|
||||
->where('departmentId', $companyId)
|
||||
->field('id as accountId, userName as accountUserName, realName as accountRealName, nickname as accountNickname, tenantId')
|
||||
->find();
|
||||
|
||||
if (empty($accountInfo)) {
|
||||
return json([
|
||||
'code' => 404,
|
||||
'msg' => '目标账号不存在'
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
// 调用 AutomaticAssign 进行好友转移
|
||||
$automaticAssign = new AutomaticAssign();
|
||||
$result = $automaticAssign->allotWechatFriend([
|
||||
'wechatFriendId' => $friendId,
|
||||
'toAccountId' => $toAccountId,
|
||||
'comment' => $comment,
|
||||
'notifyReceiver' => false,
|
||||
'optFrom' => 4
|
||||
], true);
|
||||
|
||||
$resultData = json_decode($result, true);
|
||||
|
||||
if (!empty($resultData) && $resultData['code'] == 200) {
|
||||
// 转移成功后更新数据库
|
||||
$updateData = [
|
||||
'accountId' => $accountInfo['accountId'],
|
||||
'accountUserName' => $accountInfo['accountUserName'],
|
||||
'accountRealName' => $accountInfo['accountRealName'],
|
||||
'accountNickname' => $accountInfo['accountNickname'],
|
||||
'updateTime' => time()
|
||||
];
|
||||
|
||||
Db::table('s2_wechat_friend')
|
||||
->where('id', $friendId)
|
||||
->update($updateData);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '好友转移成功',
|
||||
'data' => [
|
||||
'friendId' => $friendId,
|
||||
'toAccountId' => $toAccountId
|
||||
]
|
||||
]);
|
||||
} else {
|
||||
return json([
|
||||
'code' => 500,
|
||||
'msg' => '好友转移失败:' . ($resultData['msg'] ?? '未知错误')
|
||||
]);
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return json([
|
||||
'code' => 500,
|
||||
'msg' => '好友转移失败:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\plan;
|
||||
|
||||
use app\common\model\Device as DeviceModel;
|
||||
use app\common\model\DeviceWechatLogin as DeviceWechatLoginModel;
|
||||
use app\common\model\WechatCustomer as WechatCustomerModel;
|
||||
use library\ResponseHelper;
|
||||
use think\Controller;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 获取获客计划详情控制器
|
||||
*/
|
||||
class GetAddFriendPlanDetailV1Controller extends Controller
|
||||
{
|
||||
/**
|
||||
* 生成签名
|
||||
*
|
||||
* @param array $params 参数数组
|
||||
* @param string $apiKey API密钥
|
||||
* @return string
|
||||
*/
|
||||
private function generateSignature($params, $apiKey)
|
||||
{
|
||||
// 1. 移除sign和apiKey
|
||||
unset($params['sign'], $params['apiKey']);
|
||||
|
||||
// 2. 移除空值
|
||||
$params = array_filter($params, function($value) {
|
||||
return !is_null($value) && $value !== '';
|
||||
});
|
||||
|
||||
// 3. 参数按键名升序排序
|
||||
ksort($params);
|
||||
|
||||
// 4. 直接拼接参数值
|
||||
$stringToSign = implode('', array_values($params));
|
||||
|
||||
// 5. 第一次MD5加密
|
||||
$firstMd5 = md5($stringToSign);
|
||||
|
||||
// 6. 拼接apiKey并第二次MD5加密
|
||||
return md5($firstMd5 . $apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成测试URL
|
||||
*
|
||||
* @param string $apiKey API密钥
|
||||
* @return array
|
||||
*/
|
||||
public function testUrl($apiKey)
|
||||
{
|
||||
try {
|
||||
if (empty($apiKey)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 构建测试参数
|
||||
$testParams = [
|
||||
'name' => '测试客户',
|
||||
'phone' => '18888888888',
|
||||
'apiKey' => $apiKey,
|
||||
'timestamp' => time()
|
||||
];
|
||||
|
||||
// 生成签名
|
||||
$sign = $this->generateSignature($testParams, $apiKey);
|
||||
$testParams['sign'] = $sign;
|
||||
|
||||
// 构建签名过程说明
|
||||
$signParams = $testParams;
|
||||
unset($signParams['sign'], $signParams['apiKey']);
|
||||
ksort($signParams);
|
||||
$signStr = implode('', array_values($signParams));
|
||||
|
||||
// 构建完整URL参数,不对中文进行编码
|
||||
$urlParams = [];
|
||||
foreach ($testParams as $key => $value) {
|
||||
$urlParams[] = $key . '=' . $value;
|
||||
}
|
||||
$fullUrl = implode('&', $urlParams);
|
||||
|
||||
return [
|
||||
'apiKey' => $apiKey,
|
||||
'originalString' => $signStr,
|
||||
'sign' => $sign,
|
||||
'fullUrl' => $fullUrl
|
||||
];
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取计划详情
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$planId = $this->request->param('planId');
|
||||
|
||||
if (empty($planId)) {
|
||||
return ResponseHelper::error('计划ID不能为空', 400);
|
||||
}
|
||||
|
||||
// 查询计划详情
|
||||
$plan = Db::name('customer_acquisition_task')
|
||||
->where('id', $planId)
|
||||
->find();
|
||||
|
||||
if (!$plan) {
|
||||
return ResponseHelper::error('计划不存在', 404);
|
||||
}
|
||||
|
||||
// 解析JSON字段
|
||||
$sceneConf = json_decode($plan['sceneConf'], true) ?: [];
|
||||
$reqConf = json_decode($plan['reqConf'], true) ?: [];
|
||||
$reqConf['deviceGroups'] = $reqConf['device'];
|
||||
$msgConf = json_decode($plan['msgConf'], true) ?: [];
|
||||
$tagConf = json_decode($plan['tagConf'], true) ?: [];
|
||||
|
||||
// 处理分销配置
|
||||
$distributionConfig = $sceneConf['distribution'] ?? [
|
||||
'enabled' => false,
|
||||
'channels' => [],
|
||||
'customerRewardAmount' => 0,
|
||||
'addFriendRewardAmount' => 0,
|
||||
];
|
||||
|
||||
// 格式化分销配置(分转元,并获取渠道详情)
|
||||
$distributionEnabled = !empty($distributionConfig['enabled']);
|
||||
$distributionChannels = [];
|
||||
if ($distributionEnabled && !empty($distributionConfig['channels'])) {
|
||||
$channels = Db::name('distribution_channel')
|
||||
->where([
|
||||
['id', 'in', $distributionConfig['channels']],
|
||||
['deleteTime', '=', 0]
|
||||
])
|
||||
->field('id,code,name')
|
||||
->select();
|
||||
$distributionChannels = array_map(function($channel) {
|
||||
return [
|
||||
'id' => (int)$channel['id'],
|
||||
'code' => $channel['code'],
|
||||
'name' => $channel['name']
|
||||
];
|
||||
}, $channels);
|
||||
}
|
||||
|
||||
// 将分销配置添加到返回数据中
|
||||
$sceneConf['distributionEnabled'] = $distributionEnabled;
|
||||
$sceneConf['distributionChannels'] = $distributionChannels;
|
||||
$sceneConf['customerRewardAmount'] = round(($distributionConfig['customerRewardAmount'] ?? 0) / 100, 2); // 分转元
|
||||
$sceneConf['addFriendRewardAmount'] = round(($distributionConfig['addFriendRewardAmount'] ?? 0) / 100, 2); // 分转元
|
||||
|
||||
|
||||
|
||||
if(!empty($sceneConf['wechatGroups'])){
|
||||
$groupList = Db::name('wechat_group')->alias('wg')
|
||||
->join('wechat_account wa', 'wa.wechatId = wg.ownerWechatId')
|
||||
->where('wg.id', 'in', $sceneConf['wechatGroups'])
|
||||
->order('wg.id', 'desc')
|
||||
->field('wg.id,wg.name,wg.chatroomId,wg.ownerWechatId,wa.nickName as ownerNickName,wa.avatar as ownerAvatar,wa.alias as ownerAlias,wg.avatar')
|
||||
->select();
|
||||
$sceneConf['wechatGroupsOptions'] = $groupList;
|
||||
}else{
|
||||
$sceneConf['wechatGroupsOptions'] = [];
|
||||
}
|
||||
|
||||
|
||||
if (!empty($reqConf['deviceGroups'])){
|
||||
$deviceGroupsOptions = DeviceModel::alias('d')
|
||||
->field([
|
||||
'd.id', 'd.imei', 'd.memo', 'd.alive',
|
||||
'l.wechatId',
|
||||
'a.nickname', 'a.alias', '0 totalFriend', '0 totalFriend'
|
||||
])
|
||||
->leftJoin('device_wechat_login l', 'd.id = l.deviceId and l.alive =' . DeviceWechatLoginModel::ALIVE_WECHAT_ACTIVE . ' and l.companyId = d.companyId')
|
||||
->leftJoin('wechat_account a', 'l.wechatId = a.wechatId')
|
||||
->order('d.id desc')
|
||||
->whereIn('d.id',$reqConf['deviceGroups'])
|
||||
->select();
|
||||
foreach ($deviceGroupsOptions as &$device) {
|
||||
$curstomer = WechatCustomerModel::field('friendShip')->where(['wechatId' => $device['wechatId']])->find();
|
||||
$device['totalFriend'] = $curstomer->friendShip->totalFriend ?? 0;
|
||||
}
|
||||
unset($device);
|
||||
$reqConf['deviceGroupsOptions'] = $deviceGroupsOptions;
|
||||
}else{
|
||||
$reqConf['deviceGroupsOptions'] = [];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
unset(
|
||||
$reqConf['device'],
|
||||
$sceneConf['groupSelected'],
|
||||
);
|
||||
|
||||
// 合并数据
|
||||
$newData['messagePlans'] = $msgConf;
|
||||
$newData = array_merge($newData, $sceneConf, $reqConf, $tagConf, $plan);
|
||||
|
||||
// 移除不需要的字段
|
||||
unset(
|
||||
$newData['sceneConf'],
|
||||
$newData['reqConf'],
|
||||
$newData['msgConf'],
|
||||
$newData['tagConf'],
|
||||
$newData['userInfo'],
|
||||
$newData['createTime'],
|
||||
$newData['updateTime'],
|
||||
$newData['deleteTime']
|
||||
);
|
||||
|
||||
// 生成测试URL
|
||||
$newData['textUrl'] = $this->testUrl($newData['apiKey']);
|
||||
|
||||
return ResponseHelper::success($newData, '获取计划详情成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\plan;
|
||||
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
|
||||
/**
|
||||
* 获取计划任务列表控制器
|
||||
*/
|
||||
class GetCreateAddFriendPlanV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 生成唯一API密钥
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function generateApiKey()
|
||||
{
|
||||
// 生成6组随机字符串,每组5个字符
|
||||
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
$apiKey = '';
|
||||
|
||||
for ($i = 0; $i < 6; $i++) {
|
||||
$segment = '';
|
||||
for ($j = 0; $j < 5; $j++) {
|
||||
$segment .= $chars[mt_rand(0, strlen($chars) - 1)];
|
||||
}
|
||||
$apiKey .= ($i > 0 ? '-' : '') . $segment;
|
||||
}
|
||||
|
||||
// 检查是否已存在
|
||||
$exists = Db::name('customer_acquisition_task')
|
||||
->where('apiKey', $apiKey)
|
||||
->find();
|
||||
|
||||
if ($exists) {
|
||||
// 如果已存在,递归重新生成
|
||||
return $this->generateApiKey();
|
||||
}
|
||||
|
||||
return $apiKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝计划任务
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function copy()
|
||||
{
|
||||
try {
|
||||
$params = $this->request->param();
|
||||
$planId = isset($params['planId']) ? intval($params['planId']) : 0;
|
||||
|
||||
if ($planId <= 0) {
|
||||
return ResponseHelper::error('计划ID不能为空', 400);
|
||||
}
|
||||
|
||||
$plan = Db::name('customer_acquisition_task')->where('id', $planId)->find();
|
||||
if (!$plan) {
|
||||
return ResponseHelper::error('计划不存在', 404);
|
||||
}
|
||||
|
||||
unset($plan['id']);
|
||||
$plan['name'] = $plan['name'] . ' (拷贝)';
|
||||
$plan['createTime'] = time();
|
||||
$plan['updateTime'] = time();
|
||||
$plan['apiKey'] = $this->generateApiKey(); // 生成新的API密钥
|
||||
|
||||
$newPlanId = Db::name('customer_acquisition_task')->insertGetId($plan);
|
||||
if (!$newPlanId) {
|
||||
return ResponseHelper::error('拷贝计划失败', 500);
|
||||
}
|
||||
|
||||
return ResponseHelper::success(['planId' => $newPlanId], '拷贝计划任务成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除计划任务
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
try {
|
||||
$params = $this->request->param();
|
||||
$planId = isset($params['planId']) ? intval($params['planId']) : 0;
|
||||
|
||||
if ($planId <= 0) {
|
||||
return ResponseHelper::error('计划ID不能为空', 400);
|
||||
}
|
||||
|
||||
$result = Db::name('customer_acquisition_task')->where('id', $planId)->update(['deleteTime' => time()]);
|
||||
if (!$result) {
|
||||
return ResponseHelper::error('删除计划失败', 500);
|
||||
}
|
||||
|
||||
return ResponseHelper::success([], '删除计划任务成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改计划任务状态
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function updateStatus()
|
||||
{
|
||||
try {
|
||||
$params = $this->request->param();
|
||||
$planId = isset($params['planId']) ? intval($params['planId']) : 0;
|
||||
$status = isset($params['status']) ? intval($params['status']) : 0;
|
||||
|
||||
if ($planId <= 0) {
|
||||
return ResponseHelper::error('计划ID不能为空', 400);
|
||||
}
|
||||
|
||||
$result = Db::name('customer_acquisition_task')->where('id', $planId)->update(['status' => $status, 'updateTime' => time()]);
|
||||
if (!$result) {
|
||||
return ResponseHelper::error('修改计划状态失败', 500);
|
||||
}
|
||||
|
||||
return ResponseHelper::success([], '修改计划任务状态成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\plan;
|
||||
|
||||
use app\common\model\PlanScene as PlansSceneModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 获客场景控制器
|
||||
*/
|
||||
class GetPlanSceneListV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取开启的场景列表
|
||||
*
|
||||
* @param array $params 查询参数
|
||||
* @return array
|
||||
*/
|
||||
protected function getSceneList(array $params = []): array
|
||||
{
|
||||
try {
|
||||
// 构建查询条件
|
||||
$where = ['status' => PlansSceneModel::STATUS_ACTIVE];
|
||||
|
||||
// 搜索条件
|
||||
if (!empty($params['keyword'])) {
|
||||
$where[] = ['name', 'like', '%' . $params['keyword'] . '%'];
|
||||
}
|
||||
|
||||
// 标签筛选
|
||||
if (!empty($params['tag'])) {
|
||||
$where[] = ['scenarioTags', 'like', '%' . $params['tag'] . '%'];
|
||||
}
|
||||
|
||||
// 查询数据
|
||||
$query = PlansSceneModel::where($where);
|
||||
|
||||
// 获取分页数据
|
||||
$list = $query->order('sort DESC')->select()->toArray();
|
||||
|
||||
if (empty($list)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$sceneIds = array_column($list, 'id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$statsMap = $this->buildSceneStats($sceneIds, (int)$companyId);
|
||||
|
||||
// 处理数据
|
||||
foreach($list as &$val) {
|
||||
$val['scenarioTags'] = json_decode($val['scenarioTags'], true) ?: [];
|
||||
$sceneStats = $statsMap[$val['id']] ?? ['count' => 0, 'growth' => '0%'];
|
||||
$val['count'] = $sceneStats['count'];
|
||||
$val['growth'] = $sceneStats['growth'];
|
||||
}
|
||||
unset($val);
|
||||
|
||||
return $list;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
throw new \Exception('获取场景列表失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取场景列表
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$params = $this->request->param();
|
||||
$result = $this->getSceneList($params);
|
||||
return ResponseHelper::success($result);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取场景详情
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function detail()
|
||||
{
|
||||
try {
|
||||
$id = $this->request->param('id', '');
|
||||
if(empty($id)) {
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
$data = PlansSceneModel::where([
|
||||
'status' => PlansSceneModel::STATUS_ACTIVE,
|
||||
'id' => $id
|
||||
])->find();
|
||||
|
||||
if(empty($data)) {
|
||||
return ResponseHelper::error('场景不存在');
|
||||
}
|
||||
|
||||
$data['scenarioTags'] = json_decode($data['scenarioTags'], true) ?: [];
|
||||
$data['count'] = $this->getPlanCount($id);
|
||||
$data['growth'] = $this->calculateGrowth($id);
|
||||
|
||||
return ResponseHelper::success($data);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取计划数量
|
||||
*
|
||||
* @param int $sceneId 场景ID
|
||||
* @return int
|
||||
*/
|
||||
private function getPlanCount(int $sceneId): int
|
||||
{
|
||||
return Db::name('customer_acquisition_task')
|
||||
->where('sceneId', $sceneId)
|
||||
->where('companyId',$this->getUserInfo('companyId'))
|
||||
->where('deleteTime', 0)
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算增长率
|
||||
*
|
||||
* @param int $sceneId 场景ID
|
||||
* @return string
|
||||
*/
|
||||
private function calculateGrowth(int $sceneId): string
|
||||
{
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$currentStart = strtotime(date('Y-m-01 00:00:00'));
|
||||
$nextMonthStart = strtotime(date('Y-m-01 00:00:00', strtotime('+1 month')));
|
||||
$lastMonthStart = strtotime(date('Y-m-01 00:00:00', strtotime('-1 month')));
|
||||
|
||||
$currentMonth = $this->getSceneMonthlyCount($sceneId, $companyId, $currentStart, $nextMonthStart - 1);
|
||||
$lastMonth = $this->getSceneMonthlyCount($sceneId, $companyId, $lastMonthStart, $currentStart - 1);
|
||||
|
||||
return $this->formatGrowthPercentage($currentMonth, $lastMonth);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量构建场景统计数据
|
||||
* @param array $sceneIds
|
||||
* @param int $companyId
|
||||
* @return array
|
||||
*/
|
||||
private function buildSceneStats(array $sceneIds, int $companyId): array
|
||||
{
|
||||
if (empty($sceneIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$totalCounts = $this->getSceneTaskCounts($sceneIds, $companyId);
|
||||
|
||||
$currentStart = strtotime(date('Y-m-01 00:00:00'));
|
||||
$nextMonthStart = strtotime(date('Y-m-01 00:00:00', strtotime('+1 month')));
|
||||
$lastMonthStart = strtotime(date('Y-m-01 00:00:00', strtotime('-1 month')));
|
||||
|
||||
$currentMonthCounts = $this->getSceneMonthlyCounts($sceneIds, $companyId, $currentStart, $nextMonthStart - 1);
|
||||
$lastMonthCounts = $this->getSceneMonthlyCounts($sceneIds, $companyId, $lastMonthStart, $currentStart - 1);
|
||||
|
||||
$stats = [];
|
||||
foreach ($sceneIds as $sceneId) {
|
||||
$current = $currentMonthCounts[$sceneId] ?? 0;
|
||||
$last = $lastMonthCounts[$sceneId] ?? 0;
|
||||
$stats[$sceneId] = [
|
||||
'count' => $totalCounts[$sceneId] ?? 0,
|
||||
'growth' => $this->formatGrowthPercentage($current, $last),
|
||||
];
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取场景计划总数
|
||||
* @param array $sceneIds
|
||||
* @param int $companyId
|
||||
* @return array
|
||||
*/
|
||||
private function getSceneTaskCounts(array $sceneIds, int $companyId): array
|
||||
{
|
||||
if (empty($sceneIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
$where = [
|
||||
['companyId', '=', $companyId],
|
||||
['deleteTime', '=', 0],
|
||||
['sceneId', 'in', $sceneIds],
|
||||
];
|
||||
if(!$this->getUserInfo('isAdmin')){
|
||||
$where[] = ['userId', '=', $this->getUserInfo('id')];
|
||||
}
|
||||
|
||||
|
||||
$rows = Db::name('customer_acquisition_task')
|
||||
->where($where)
|
||||
->field('sceneId, COUNT(*) as total')
|
||||
->group('sceneId')
|
||||
->select();
|
||||
|
||||
$result = [];
|
||||
foreach ($rows as $row) {
|
||||
$sceneId = is_array($row) ? ($row['sceneId'] ?? 0) : ($row->sceneId ?? 0);
|
||||
if (!$sceneId) {
|
||||
continue;
|
||||
}
|
||||
$result[$sceneId] = (int)(is_array($row) ? ($row['total'] ?? 0) : ($row->total ?? 0));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取场景月度数据
|
||||
* @param array $sceneIds
|
||||
* @param int $companyId
|
||||
* @param int $startTime
|
||||
* @param int $endTime
|
||||
* @return array
|
||||
*/
|
||||
private function getSceneMonthlyCounts(array $sceneIds, int $companyId, int $startTime, int $endTime): array
|
||||
{
|
||||
if (empty($sceneIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$rows = Db::name('customer_acquisition_task')
|
||||
->whereIn('sceneId', $sceneIds)
|
||||
->where('companyId', $companyId)
|
||||
->where('status', 1)
|
||||
->where('deleteTime', 0)
|
||||
->whereBetween('createTime', [$startTime, $endTime])
|
||||
->field('sceneId, COUNT(*) as total')
|
||||
->group('sceneId')
|
||||
->select();
|
||||
|
||||
$result = [];
|
||||
foreach ($rows as $row) {
|
||||
$sceneId = is_array($row) ? ($row['sceneId'] ?? 0) : ($row->sceneId ?? 0);
|
||||
if (!$sceneId) {
|
||||
continue;
|
||||
}
|
||||
$result[$sceneId] = (int)(is_array($row) ? ($row['total'] ?? 0) : ($row->total ?? 0));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个场景的月度数据
|
||||
* @param int $sceneId
|
||||
* @param int $companyId
|
||||
* @param int $startTime
|
||||
* @param int $endTime
|
||||
* @return int
|
||||
*/
|
||||
private function getSceneMonthlyCount(int $sceneId, int $companyId, int $startTime, int $endTime): int
|
||||
{
|
||||
return Db::name('customer_acquisition_task')
|
||||
->where('sceneId', $sceneId)
|
||||
->where('companyId', $companyId)
|
||||
->where('status', 1)
|
||||
->where('deleteTime', 0)
|
||||
->whereBetween('createTime', [$startTime, $endTime])
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算增长百分比
|
||||
* @param int $current
|
||||
* @param int $last
|
||||
* @return string
|
||||
*/
|
||||
private function formatGrowthPercentage(int $current, int $last): string
|
||||
{
|
||||
if ($last == 0) {
|
||||
return $current > 0 ? '100%' : '0%';
|
||||
}
|
||||
|
||||
$growth = round(($current - $last) / $last * 100, 2);
|
||||
return $growth . '%';
|
||||
}
|
||||
}
|
||||
554
application/cunkebao/controller/plan/PlanSceneV1Controller.php
Normal file
554
application/cunkebao/controller/plan/PlanSceneV1Controller.php
Normal file
@@ -0,0 +1,554 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\plan;
|
||||
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use app\cunkebao\controller\plan\PosterWeChatMiniProgram;
|
||||
|
||||
/**
|
||||
* 获取计划任务列表控制器
|
||||
*/
|
||||
class PlanSceneV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取计划任务列表
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$params = $this->request->param();
|
||||
$page = isset($params['page']) ? intval($params['page']) : 1;
|
||||
$limit = isset($params['limit']) ? intval($params['limit']) : 10;
|
||||
$keyword = isset($params['keyword']) ? trim($params['keyword']) : '';
|
||||
$sceneId = $this->request->param('sceneId','');
|
||||
$where = [
|
||||
'deleteTime' => 0,
|
||||
'companyId' => $this->getUserInfo('companyId'),
|
||||
];
|
||||
|
||||
if(!$this->getUserInfo('isAdmin')){
|
||||
$where['userId'] = $this->getUserInfo('id');
|
||||
}
|
||||
|
||||
if(!empty($sceneId)){
|
||||
$where['sceneId'] = $sceneId;
|
||||
}
|
||||
|
||||
if(!empty($keyword)){
|
||||
$where[] = ['name', 'like', '%' . $keyword . '%'];
|
||||
}
|
||||
|
||||
|
||||
$total = Db::name('customer_acquisition_task')->where($where)->count();
|
||||
$list = Db::name('customer_acquisition_task')
|
||||
->where($where)
|
||||
->order('createTime', 'desc')
|
||||
->page($page, $limit)
|
||||
->select();
|
||||
|
||||
if (!empty($list)) {
|
||||
$taskIds = array_column($list, 'id');
|
||||
$statsMap = $this->buildTaskStats($taskIds);
|
||||
|
||||
foreach($list as &$val){
|
||||
$val['createTime'] = !empty($val['createTime']) ? date('Y-m-d H:i:s', $val['createTime']) : '';
|
||||
$val['updateTime'] = !empty($val['updateTime']) ? date('Y-m-d H:i:s', $val['updateTime']) : '';
|
||||
$val['sceneConf'] = json_decode($val['sceneConf'],true) ?: [];
|
||||
$val['reqConf'] = json_decode($val['reqConf'],true) ?: [];
|
||||
$val['msgConf'] = json_decode($val['msgConf'],true) ?: [];
|
||||
$val['tagConf'] = json_decode($val['tagConf'],true) ?: [];
|
||||
|
||||
$stats = $statsMap[$val['id']] ?? [
|
||||
'acquiredCount' => 0,
|
||||
'addedCount' => 0,
|
||||
'passCount' => 0,
|
||||
'lastUpdated' => 0
|
||||
];
|
||||
|
||||
$val['acquiredCount'] = $stats['acquiredCount'];
|
||||
$val['addedCount'] = $stats['addedCount'];
|
||||
$val['passCount'] = $stats['passCount'];
|
||||
$val['passRate'] = ($stats['addedCount'] > 0 && $stats['passCount'] > 0)
|
||||
? number_format(($stats['passCount'] / $stats['addedCount']) * 100, 2)
|
||||
: 0;
|
||||
$val['lastUpdated'] = !empty($stats['lastUpdated']) ? date('Y-m-d H:i', $stats['lastUpdated']) : '--';
|
||||
}
|
||||
unset($val);
|
||||
}
|
||||
return ResponseHelper::success([
|
||||
'total' => $total,
|
||||
'list' => $list
|
||||
], '获取计划任务列表成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除计划任务
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
try {
|
||||
$params = $this->request->param();
|
||||
$planId = isset($params['planId']) ? intval($params['planId']) : 0;
|
||||
|
||||
if ($planId <= 0) {
|
||||
return ResponseHelper::error('计划ID不能为空', 400);
|
||||
}
|
||||
|
||||
$result = Db::name('customer_acquisition_task')->where('id', $planId)->update(['deleteTime' => time()]);
|
||||
if (!$result) {
|
||||
return ResponseHelper::error('删除计划失败', 500);
|
||||
}
|
||||
|
||||
return ResponseHelper::success([], '删除计划任务成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改计划任务状态
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function updateStatus()
|
||||
{
|
||||
try {
|
||||
$params = $this->request->param();
|
||||
$planId = isset($params['planId']) ? intval($params['planId']) : 0;
|
||||
$status = isset($params['status']) ? intval($params['status']) : 0;
|
||||
|
||||
if ($planId <= 0) {
|
||||
return ResponseHelper::error('计划ID不能为空', 400);
|
||||
}
|
||||
|
||||
$result = Db::name('customer_acquisition_task')->where('id', $planId)->update(['status' => $status, 'updateTime' => time()]);
|
||||
if (!$result) {
|
||||
return ResponseHelper::error('修改计划状态失败', 500);
|
||||
}
|
||||
|
||||
return ResponseHelper::success([], '修改计划任务状态成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取获客计划设备列表
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getPlanDevices()
|
||||
{
|
||||
try {
|
||||
$params = $this->request->param();
|
||||
$planId = isset($params['planId']) ? intval($params['planId']) : 0;
|
||||
$page = isset($params['page']) ? intval($params['page']) : 1;
|
||||
$limit = isset($params['limit']) ? intval($params['limit']) : 10;
|
||||
$deviceStatus = isset($params['deviceStatus']) ? $params['deviceStatus'] : '';
|
||||
$searchKeyword = isset($params['searchKeyword']) ? trim($params['searchKeyword']) : '';
|
||||
|
||||
// 验证计划ID
|
||||
if ($planId <= 0) {
|
||||
return ResponseHelper::error('计划ID不能为空', 400);
|
||||
}
|
||||
|
||||
// 验证计划是否存在且用户有权限
|
||||
$plan = Db::name('customer_acquisition_task')
|
||||
->where([
|
||||
'id' => $planId,
|
||||
'deleteTime' => 0,
|
||||
'companyId' => $this->getUserInfo('companyId')
|
||||
])
|
||||
->find();
|
||||
|
||||
if (!$plan) {
|
||||
return ResponseHelper::error('计划不存在或无权限访问', 404);
|
||||
}
|
||||
|
||||
// 如果是管理员,需要验证用户权限
|
||||
if (!$this->getUserInfo('isAdmin')) {
|
||||
$userPlan = Db::name('customer_acquisition_task')
|
||||
->where([
|
||||
'id' => $planId,
|
||||
'userId' => $this->getUserInfo('id')
|
||||
])
|
||||
->find();
|
||||
|
||||
if (!$userPlan) {
|
||||
return ResponseHelper::error('您没有权限访问该计划', 403);
|
||||
}
|
||||
}
|
||||
|
||||
// 构建查询条件
|
||||
$where = [
|
||||
'pt.plan_id' => $planId,
|
||||
'd.deleteTime' => 0,
|
||||
'd.companyId' => $this->getUserInfo('companyId')
|
||||
];
|
||||
|
||||
// 设备状态筛选
|
||||
if (!empty($deviceStatus)) {
|
||||
$where['d.alive'] = $deviceStatus;
|
||||
}
|
||||
|
||||
// 搜索关键词
|
||||
$searchWhere = [];
|
||||
if (!empty($searchKeyword)) {
|
||||
$searchWhere[] = ['d.imei', 'like', "%{$searchKeyword}%"];
|
||||
$searchWhere[] = ['d.memo', 'like', "%{$searchKeyword}%"];
|
||||
}
|
||||
|
||||
// 查询设备总数
|
||||
$totalQuery = Db::name('plan_task_device')->alias('pt')
|
||||
->join('device d', 'pt.device_id = d.id')
|
||||
->where($where);
|
||||
|
||||
if (!empty($searchWhere)) {
|
||||
$totalQuery->where(function ($query) use ($searchWhere) {
|
||||
foreach ($searchWhere as $condition) {
|
||||
$query->whereOr($condition[0], $condition[1], $condition[2]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$total = $totalQuery->count();
|
||||
|
||||
// 查询设备列表
|
||||
$listQuery = Db::name('plan_task_device')->alias('pt')
|
||||
->join('device d', 'pt.device_id = d.id')
|
||||
->field([
|
||||
'd.id',
|
||||
'd.imei',
|
||||
'd.memo',
|
||||
'd.alive',
|
||||
'd.extra',
|
||||
'd.createTime',
|
||||
'd.updateTime',
|
||||
'pt.status as plan_device_status',
|
||||
'pt.createTime as assign_time'
|
||||
])
|
||||
->where($where)
|
||||
->order('pt.createTime', 'desc');
|
||||
|
||||
if (!empty($searchWhere)) {
|
||||
$listQuery->where(function ($query) use ($searchWhere) {
|
||||
foreach ($searchWhere as $condition) {
|
||||
$query->whereOr($condition[0], $condition[1], $condition[2]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$list = $listQuery->page($page, $limit)->select();
|
||||
|
||||
// 处理设备数据
|
||||
foreach ($list as &$device) {
|
||||
// 格式化时间
|
||||
$device['createTime'] = date('Y-m-d H:i:s', $device['createTime']);
|
||||
$device['updateTime'] = date('Y-m-d H:i:s', $device['updateTime']);
|
||||
$device['assign_time'] = date('Y-m-d H:i:s', $device['assign_time']);
|
||||
|
||||
// 解析设备额外信息
|
||||
if (!empty($device['extra'])) {
|
||||
$extra = json_decode($device['extra'], true);
|
||||
$device['battery'] = isset($extra['battery']) ? intval($extra['battery']) : 0;
|
||||
$device['device_info'] = $extra;
|
||||
} else {
|
||||
$device['battery'] = 0;
|
||||
$device['device_info'] = [];
|
||||
}
|
||||
|
||||
// 设备状态文本
|
||||
$device['alive_text'] = $this->getDeviceStatusText($device['alive']);
|
||||
$device['plan_device_status_text'] = $this->getPlanDeviceStatusText($device['plan_device_status']);
|
||||
|
||||
// 获取设备当前微信登录信息
|
||||
$wechatLogin = Db::name('device_wechat_login')
|
||||
->where([
|
||||
'deviceId' => $device['id'],
|
||||
'companyId' => $this->getUserInfo('companyId'),
|
||||
'alive' => 1
|
||||
])
|
||||
->order('createTime', 'desc')
|
||||
->find();
|
||||
|
||||
$device['current_wechat'] = $wechatLogin ? [
|
||||
'wechatId' => $wechatLogin['wechatId'],
|
||||
'nickname' => $wechatLogin['nickname'] ?? '',
|
||||
'loginTime' => date('Y-m-d H:i:s', $wechatLogin['createTime'])
|
||||
] : null;
|
||||
|
||||
// 获取设备在该计划中的任务统计
|
||||
$device['task_stats'] = $this->getDeviceTaskStats($device['id'], $planId);
|
||||
|
||||
// 移除原始extra字段
|
||||
unset($device['extra']);
|
||||
}
|
||||
unset($device);
|
||||
|
||||
return ResponseHelper::success([
|
||||
'total' => $total,
|
||||
'list' => $list,
|
||||
'plan_info' => [
|
||||
'id' => $plan['id'],
|
||||
'name' => $plan['name'],
|
||||
'status' => $plan['status']
|
||||
]
|
||||
], '获取计划设备列表成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备状态文本
|
||||
*
|
||||
* @param int $status
|
||||
* @return string
|
||||
*/
|
||||
private function getDeviceStatusText($status)
|
||||
{
|
||||
$statusMap = [
|
||||
0 => '离线',
|
||||
1 => '在线',
|
||||
2 => '忙碌',
|
||||
3 => '故障'
|
||||
];
|
||||
return isset($statusMap[$status]) ? $statusMap[$status] : '未知';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取计划设备状态文本
|
||||
*
|
||||
* @param int $status
|
||||
* @return string
|
||||
*/
|
||||
private function getPlanDeviceStatusText($status)
|
||||
{
|
||||
$statusMap = [
|
||||
0 => '待分配',
|
||||
1 => '已分配',
|
||||
2 => '执行中',
|
||||
3 => '已完成',
|
||||
4 => '已暂停',
|
||||
5 => '已取消'
|
||||
];
|
||||
return isset($statusMap[$status]) ? $statusMap[$status] : '未知';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备在指定计划中的任务统计
|
||||
*
|
||||
* @param int $deviceId
|
||||
* @param int $planId
|
||||
* @return array
|
||||
*/
|
||||
private function getDeviceTaskStats($deviceId, $planId)
|
||||
{
|
||||
// 获取该设备在计划中的任务总数
|
||||
$totalTasks = Db::name('task_customer')
|
||||
->where([
|
||||
'task_id' => $planId,
|
||||
'device_id' => $deviceId
|
||||
])
|
||||
->count();
|
||||
|
||||
// 获取已完成的任务数
|
||||
$completedTasks = Db::name('task_customer')
|
||||
->where([
|
||||
'task_id' => $planId,
|
||||
'device_id' => $deviceId,
|
||||
'status' => 4
|
||||
])
|
||||
->count();
|
||||
|
||||
// 获取进行中的任务数
|
||||
$processingTasks = Db::name('task_customer')
|
||||
->where([
|
||||
'task_id' => $planId,
|
||||
'device_id' => $deviceId,
|
||||
'status' => ['in', [1, 2, 3]]
|
||||
])
|
||||
->count();
|
||||
|
||||
return [
|
||||
'total_tasks' => $totalTasks,
|
||||
'completed_tasks' => $completedTasks,
|
||||
'processing_tasks' => $processingTasks,
|
||||
'completion_rate' => $totalTasks > 0 ? round(($completedTasks / $totalTasks) * 100, 2) : 0
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建任务统计
|
||||
* @param array $taskIds
|
||||
* @return array
|
||||
*/
|
||||
private function buildTaskStats(array $taskIds): array
|
||||
{
|
||||
if (empty($taskIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$rows = Db::name('task_customer')
|
||||
->whereIn('task_id', $taskIds)
|
||||
->field([
|
||||
'task_id as taskId',
|
||||
'COUNT(1) as acquiredCount',
|
||||
"SUM(CASE WHEN status IN (1,2,3,4,5) THEN 1 ELSE 0 END) as addedCount",
|
||||
"SUM(CASE WHEN status IN (4,5) THEN 1 ELSE 0 END) as passCount",
|
||||
'MAX(updateTime) as lastUpdated'
|
||||
])
|
||||
->group('task_id')
|
||||
->select();
|
||||
|
||||
$stats = [];
|
||||
foreach ($rows as $row) {
|
||||
$taskId = is_array($row) ? ($row['taskId'] ?? 0) : ($row->taskId ?? 0);
|
||||
if (!$taskId) {
|
||||
continue;
|
||||
}
|
||||
$stats[$taskId] = [
|
||||
'acquiredCount' => (int)(is_array($row) ? ($row['acquiredCount'] ?? 0) : ($row->acquiredCount ?? 0)),
|
||||
'addedCount' => (int)(is_array($row) ? ($row['addedCount'] ?? 0) : ($row->addedCount ?? 0)),
|
||||
'passCount' => (int)(is_array($row) ? ($row['passCount'] ?? 0) : ($row->passCount ?? 0)),
|
||||
'lastUpdated' => (int)(is_array($row) ? ($row['lastUpdated'] ?? 0) : ($row->lastUpdated ?? 0)),
|
||||
];
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取微信小程序码
|
||||
* @return \think\response\Json
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @throws \think\exception\DbException
|
||||
*/
|
||||
public function getWxMinAppCode()
|
||||
{
|
||||
$params = $this->request->param();
|
||||
$taskId = isset($params['taskId']) ? intval($params['taskId']) : 0;
|
||||
$channelId = isset($params['channelId']) ? intval($params['channelId']) : 0;
|
||||
|
||||
if($taskId <= 0) {
|
||||
return ResponseHelper::error('任务ID或场景ID不能为空', 400);
|
||||
}
|
||||
|
||||
$task = Db::name('customer_acquisition_task')->where(['id' => $taskId, 'deleteTime' => 0])->find();
|
||||
if(!$task) {
|
||||
return ResponseHelper::error('任务不存在', 400);
|
||||
}
|
||||
|
||||
// 如果提供了channelId,验证渠道是否存在且有效
|
||||
if ($channelId > 0) {
|
||||
$channel = Db::name('distribution_channel')
|
||||
->where([
|
||||
['id', '=', $channelId],
|
||||
['companyId', '=', $task['companyId']],
|
||||
['status', '=', 'enabled'],
|
||||
['deleteTime', '=', 0]
|
||||
])
|
||||
->find();
|
||||
|
||||
if (!$channel) {
|
||||
return ResponseHelper::error('分销渠道不存在或已被禁用', 400);
|
||||
}
|
||||
}
|
||||
|
||||
$posterWeChatMiniProgram = new PosterWeChatMiniProgram();
|
||||
$result = $posterWeChatMiniProgram->generateMiniProgramCodeWithScene($taskId, $channelId);
|
||||
$result = json_decode($result, true);
|
||||
if ($result['code'] == 200){
|
||||
return ResponseHelper::success($result['data'], '获取小程序码成功');
|
||||
}else{
|
||||
return ResponseHelper::error('获取小程序失败:' . $result['msg']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取已获客/已添加用户
|
||||
* @return \think\response\Json
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @throws \think\exception\DbException
|
||||
*/
|
||||
public function getUserList(){
|
||||
$type = $this->request->param('type',1);
|
||||
$planId = $this->request->param('planId','');
|
||||
$page = $this->request->param('page',1);
|
||||
$pageSize = $this->request->param('pageSize',10);
|
||||
$keyword = $this->request->param('keyword','');
|
||||
|
||||
if (!in_array($type, [1, 2])) {
|
||||
return ResponseHelper::error('类型错误');
|
||||
}
|
||||
|
||||
if (empty($planId)){
|
||||
return ResponseHelper::error('获客场景id不能为空');
|
||||
}
|
||||
|
||||
$task = Db::name('customer_acquisition_task')
|
||||
->where(['id' => $planId, 'deleteTime' => 0,'companyId' => $this->getUserInfo('companyId')])
|
||||
->find();
|
||||
if(empty($task)) {
|
||||
return ResponseHelper::error('活动不存在');
|
||||
}
|
||||
$query = Db::name('task_customer')->where(['task_id' => $task['id']]);
|
||||
|
||||
if ($type == 2){
|
||||
$query = $query->whereIn('status',[4,5]);
|
||||
}
|
||||
|
||||
if (!empty($keyword)) {
|
||||
$query = $query->where('name|phone|tags|siteTags', 'like', '%' . $keyword . '%');
|
||||
}
|
||||
|
||||
$total = $query->count();
|
||||
$list = $query->page($page, $pageSize)->order('id', 'desc')->select();
|
||||
foreach ($list as &$item) {
|
||||
unset($item['processed_wechat_ids'],$item['task_id']);
|
||||
$userinfo = Db::table('s2_wechat_friend')
|
||||
->field('alias,wechatId,nickname,avatar')
|
||||
->where('alias|wechatId|phone|conRemark','like','%'.$item['phone'].'%')
|
||||
->order('id DESC')
|
||||
->find();
|
||||
|
||||
if (!empty($userinfo)) {
|
||||
$item['userinfo'] = $userinfo;
|
||||
}else{
|
||||
$item['userinfo'] = [];
|
||||
}
|
||||
|
||||
$item['tags'] = json_decode($item['tags'], true);
|
||||
$item['siteTags'] = json_decode($item['siteTags'], true);
|
||||
$item['createTime'] = !empty($item['createTime']) ? date('Y-m-d H:i:s', $item['createTime']) : '';
|
||||
$item['updateTime'] = !empty($item['updateTime']) ? date('Y-m-d H:i:s', $item['updateTime']) : '';
|
||||
}
|
||||
|
||||
|
||||
|
||||
$data = [
|
||||
'total' => $total,
|
||||
'list' => $list,
|
||||
];
|
||||
return ResponseHelper::success($data,'获取成功');
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\plan;
|
||||
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
use think\facade\Request;
|
||||
|
||||
/**
|
||||
* 获客场景控制器
|
||||
*/
|
||||
class PostCreateAddFriendPlanV1Controller extends BaseController
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* 生成唯一API密钥
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateApiKey()
|
||||
{
|
||||
// 生成5组随机字符串,每组5个字符
|
||||
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||
$apiKey = '';
|
||||
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$segment = '';
|
||||
for ($j = 0; $j < 5; $j++) {
|
||||
$segment .= $chars[mt_rand(0, strlen($chars) - 1)];
|
||||
}
|
||||
$apiKey .= ($i > 0 ? '-' : '') . $segment;
|
||||
}
|
||||
|
||||
// 检查是否已存在
|
||||
$exists = Db::name('customer_acquisition_task')
|
||||
->where('apiKey', $apiKey)
|
||||
->find();
|
||||
|
||||
if ($exists) {
|
||||
// 如果已存在,递归重新生成
|
||||
return $this->generateApiKey();
|
||||
}
|
||||
|
||||
return $apiKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加计划任务
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$params = $this->request->param();
|
||||
|
||||
// 验证必填字段
|
||||
if (empty($params['name'])) {
|
||||
return ResponseHelper::error('计划名称不能为空', 400);
|
||||
}
|
||||
|
||||
if (empty($params['sceneId'])) {
|
||||
return ResponseHelper::error('场景ID不能为空', 400);
|
||||
}
|
||||
|
||||
if (empty($params['deviceGroups'])) {
|
||||
return ResponseHelper::error('请选择设备', 400);
|
||||
}
|
||||
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
// 处理分销配置
|
||||
$distributionConfig = $this->processDistributionConfig($params, $companyId);
|
||||
|
||||
// 归类参数
|
||||
$msgConf = isset($params['messagePlans']) ? $params['messagePlans'] : [];
|
||||
$tagConf = [
|
||||
'scenarioTags' => $params['scenarioTags'] ?? [],
|
||||
'customTags' => $params['customTags'] ?? [],
|
||||
];
|
||||
$reqConf = [
|
||||
'device' => $params['deviceGroups'] ?? [],
|
||||
'remarkType' => $params['remarkType'] ?? '',
|
||||
'greeting' => $params['greeting'] ?? '',
|
||||
'addFriendInterval' => $params['addFriendInterval'] ?? '',
|
||||
'startTime' => $params['startTime'] ?? '',
|
||||
'endTime' => $params['endTime'] ?? '',
|
||||
];
|
||||
// 其余参数归为sceneConf
|
||||
$sceneConf = $params;
|
||||
unset(
|
||||
$sceneConf['id'],
|
||||
$sceneConf['apiKey'],
|
||||
$sceneConf['userId'],
|
||||
$sceneConf['status'],
|
||||
$sceneConf['planId'],
|
||||
$sceneConf['name'],
|
||||
$sceneConf['sceneId'],
|
||||
$sceneConf['messagePlans'],
|
||||
$sceneConf['scenarioTags'],
|
||||
$sceneConf['customTags'],
|
||||
$sceneConf['device'],
|
||||
$sceneConf['orderTableFileName'],
|
||||
$sceneConf['userInfo'],
|
||||
$sceneConf['textUrl'],
|
||||
$sceneConf['remarkType'],
|
||||
$sceneConf['greeting'],
|
||||
$sceneConf['addFriendInterval'],
|
||||
$sceneConf['startTime'],
|
||||
$sceneConf['orderTableFile'],
|
||||
$sceneConf['endTime'],
|
||||
$sceneConf['distributionEnabled'],
|
||||
$sceneConf['distributionChannels'],
|
||||
$sceneConf['customerRewardAmount'],
|
||||
$sceneConf['addFriendRewardAmount']
|
||||
);
|
||||
|
||||
// 将分销配置添加到sceneConf中
|
||||
$sceneConf['distribution'] = $distributionConfig;
|
||||
|
||||
// 构建数据
|
||||
$data = [
|
||||
'name' => $params['name'],
|
||||
'sceneId' => $params['sceneId'],
|
||||
'sceneConf' => json_encode($sceneConf, JSON_UNESCAPED_UNICODE),
|
||||
'reqConf' => json_encode($reqConf, JSON_UNESCAPED_UNICODE),
|
||||
'msgConf' => json_encode($msgConf, JSON_UNESCAPED_UNICODE),
|
||||
'tagConf' => json_encode($tagConf, JSON_UNESCAPED_UNICODE),
|
||||
'userId' => $this->getUserInfo('id'),
|
||||
'companyId' => $this->getUserInfo('companyId'),
|
||||
'status' => !empty($params['status']) ? 1 : 0,
|
||||
'apiKey' => $this->generateApiKey(), // 生成API密钥
|
||||
'createTime' => time(),
|
||||
'updateTime' => time(),
|
||||
];
|
||||
|
||||
|
||||
try {
|
||||
Db::startTrans();
|
||||
// 插入数据
|
||||
$planId = Db::name('customer_acquisition_task')->insertGetId($data);
|
||||
|
||||
if (!$planId) {
|
||||
throw new \Exception('添加计划失败');
|
||||
}
|
||||
|
||||
|
||||
//订单
|
||||
if ($params['sceneId'] == 2) {
|
||||
if (!empty($params['orderFileUrl'])) {
|
||||
// 先下载到本地临时文件,再分析,最后删除
|
||||
$originPath = $params['orderFileUrl'];
|
||||
$tmpFile = tempnam(sys_get_temp_dir(), 'order_');
|
||||
// 判断是否为远程文件
|
||||
if (preg_match('/^https?:\/\//i', $originPath)) {
|
||||
// 远程URL,下载到本地
|
||||
$fileContent = file_get_contents($originPath);
|
||||
if ($fileContent === false) {
|
||||
exit('远程文件下载失败: ' . $originPath);
|
||||
}
|
||||
file_put_contents($tmpFile, $fileContent);
|
||||
} else {
|
||||
// 本地文件,直接copy
|
||||
if (!file_exists($originPath)) {
|
||||
exit('文件不存在: ' . $originPath);
|
||||
}
|
||||
copy($originPath, $tmpFile);
|
||||
}
|
||||
// 解析临时文件
|
||||
$ext = strtolower(pathinfo($originPath, PATHINFO_EXTENSION));
|
||||
$rows = [];
|
||||
if (in_array($ext, ['xls', 'xlsx'])) {
|
||||
// 直接用composer自动加载的PHPExcel
|
||||
$excel = \PHPExcel_IOFactory::load($tmpFile);
|
||||
$sheet = $excel->getActiveSheet();
|
||||
$data = $sheet->toArray();
|
||||
if (count($data) > 1) {
|
||||
array_shift($data); // 去掉表头
|
||||
}
|
||||
|
||||
foreach ($data as $cols) {
|
||||
$rows[] = [
|
||||
'name' => isset($cols[0]) ? trim($cols[0]) : '',
|
||||
'phone' => isset($cols[1]) ? trim($cols[1]) : '',
|
||||
'wechatId' => isset($cols[2]) ? trim($cols[2]) : '',
|
||||
'source' => isset($cols[3]) ? trim($cols[3]) : '',
|
||||
'orderAmount' => isset($cols[4]) ? trim($cols[4]) : '',
|
||||
'orderDate' => isset($cols[5]) ? trim($cols[5]) : '',
|
||||
];
|
||||
}
|
||||
} elseif ($ext === 'csv') {
|
||||
$content = file_get_contents($tmpFile);
|
||||
$lines = preg_split('/\r\n|\r|\n/', $content);
|
||||
if (count($lines) > 1) {
|
||||
array_shift($lines); // 去掉表头
|
||||
foreach ($lines as $line) {
|
||||
if (trim($line) === '') continue;
|
||||
$cols = str_getcsv($line);
|
||||
if (count($cols) >= 6) {
|
||||
$rows[] = [
|
||||
'name' => isset($cols[0]) ? trim($cols[0]) : '',
|
||||
'phone' => isset($cols[1]) ? trim($cols[1]) : '',
|
||||
'wechatId' => isset($cols[2]) ? trim($cols[2]) : '',
|
||||
'source' => isset($cols[3]) ? trim($cols[3]) : '',
|
||||
'orderAmount' => isset($cols[4]) ? trim($cols[4]) : '',
|
||||
'orderDate' => isset($cols[5]) ? trim($cols[5]) : '',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unlink($tmpFile);
|
||||
exit('暂不支持的文件类型: ' . $ext);
|
||||
}
|
||||
// 删除临时文件
|
||||
unlink($tmpFile);
|
||||
}
|
||||
}
|
||||
|
||||
//电话获客
|
||||
if ($params['sceneId'] == 5) {
|
||||
$rows = Db::name('call_recording')
|
||||
->where('companyId', $this->getUserInfo('companyId'))
|
||||
->group('phone')
|
||||
->field('id,phone')
|
||||
->select();
|
||||
}
|
||||
|
||||
|
||||
//群获客
|
||||
if ($params['sceneId'] == 7) {
|
||||
if (!empty($params['wechatGroups']) && is_array($params['wechatGroups'])) {
|
||||
$rows = Db::name('wechat_group_member')->alias('gm')
|
||||
->join('wechat_account wa', 'gm.identifier = wa.wechatId')
|
||||
->whereIn('gm.groupId', $params['wechatGroups'])
|
||||
->group('gm.identifier')
|
||||
->column('wa.id,wa.wechatId,wa.alias,wa.phone');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (in_array($params['sceneId'], [2, 5, 7]) && !empty($rows) && is_array($rows)) {
|
||||
// 1000条为一组进行批量处理
|
||||
$batchSize = 1000;
|
||||
$totalRows = count($rows);
|
||||
|
||||
for ($i = 0; $i < $totalRows; $i += $batchSize) {
|
||||
$batchRows = array_slice($rows, $i, $batchSize);
|
||||
|
||||
if (!empty($batchRows)) {
|
||||
// 1. 提取当前批次的phone
|
||||
$phones = [];
|
||||
foreach ($batchRows as $row) {
|
||||
if (!empty($row['phone'])) {
|
||||
$phone = $row['phone'];
|
||||
} elseif (!empty($row['alias'])) {
|
||||
$phone = $row['alias'];
|
||||
} else {
|
||||
$phone = $row['wechatId'];
|
||||
}
|
||||
if (!empty($phone)) {
|
||||
$phones[] = $phone;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 批量查询已存在的phone
|
||||
$existingPhones = [];
|
||||
if (!empty($phones)) {
|
||||
$existing = Db::name('task_customer')
|
||||
->where('task_id', $planId)
|
||||
->where('phone', 'in', $phones)
|
||||
->field('phone')
|
||||
->select();
|
||||
$existingPhones = array_column($existing, 'phone');
|
||||
}
|
||||
|
||||
// 3. 过滤出新数据,批量插入
|
||||
$newData = [];
|
||||
foreach ($batchRows as $row) {
|
||||
if (!empty($row['phone'])) {
|
||||
$phone = $row['phone'];
|
||||
} elseif (!empty($row['alias'])) {
|
||||
$phone = $row['alias'];
|
||||
} else {
|
||||
$phone = $row['wechatId'];
|
||||
}
|
||||
if (!empty($phone) && !in_array($phone, $existingPhones)) {
|
||||
$newData[] = [
|
||||
'task_id' => $planId,
|
||||
'name' => '',
|
||||
'source' => '场景获客_' . $params['name'] ?? '',
|
||||
'phone' => $phone,
|
||||
'tags' => json_encode([], JSON_UNESCAPED_UNICODE),
|
||||
'siteTags' => json_encode([], JSON_UNESCAPED_UNICODE),
|
||||
'createTime' => time(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 批量插入新数据
|
||||
if (!empty($newData)) {
|
||||
Db::name('task_customer')->insertAll($newData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Db::commit();
|
||||
|
||||
return ResponseHelper::success(['planId' => $planId], '添加计划任务成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// 回滚事务
|
||||
Db::rollback();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证JSON格式是否正确
|
||||
*
|
||||
* @param string $string
|
||||
* @return bool
|
||||
*/
|
||||
private function validateJson($string)
|
||||
{
|
||||
if (empty($string)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
json_decode($string);
|
||||
return (json_last_error() == JSON_ERROR_NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理分销配置
|
||||
*
|
||||
* @param array $params 请求参数
|
||||
* @param int $companyId 公司ID
|
||||
* @return array 分销配置
|
||||
*/
|
||||
private function processDistributionConfig($params, $companyId)
|
||||
{
|
||||
$distributionEnabled = !empty($params['distributionEnabled']) ? true : false;
|
||||
|
||||
$config = [
|
||||
'enabled' => $distributionEnabled,
|
||||
'channels' => [],
|
||||
'customerRewardAmount' => 0, // 获客奖励金额(分)
|
||||
'addFriendRewardAmount' => 0, // 添加奖励金额(分)
|
||||
];
|
||||
|
||||
// 如果未开启分销,直接返回默认配置
|
||||
if (!$distributionEnabled) {
|
||||
return $config;
|
||||
}
|
||||
|
||||
// 验证渠道ID
|
||||
$channelIds = $params['distributionChannels'] ?? [];
|
||||
if (empty($channelIds) || !is_array($channelIds)) {
|
||||
throw new \Exception('请选择至少一个分销渠道');
|
||||
}
|
||||
|
||||
// 查询有效的渠道(只保留存在且已启用的渠道)
|
||||
$channels = Db::name('distribution_channel')
|
||||
->where([
|
||||
['id', 'in', $channelIds],
|
||||
['companyId', '=', $companyId],
|
||||
['status', '=', 'enabled'],
|
||||
['deleteTime', '=', 0]
|
||||
])
|
||||
->field('id,code,name')
|
||||
->select();
|
||||
|
||||
// 如果没有有效渠道,才报错
|
||||
if (empty($channels)) {
|
||||
throw new \Exception('所选的分销渠道均不存在或已被禁用,请重新选择');
|
||||
}
|
||||
|
||||
// 只保留有效的渠道ID
|
||||
$config['channels'] = array_column($channels, 'id');
|
||||
|
||||
// 验证获客奖励金额(元转分)
|
||||
$customerRewardAmount = isset($params['customerRewardAmount']) ? floatval($params['customerRewardAmount']) : 0;
|
||||
if ($customerRewardAmount < 0) {
|
||||
throw new \Exception('获客奖励金额不能为负数');
|
||||
}
|
||||
if ($customerRewardAmount > 0 && !preg_match('/^\d+(\.\d{1,2})?$/', (string)$customerRewardAmount)) {
|
||||
throw new \Exception('获客奖励金额格式不正确,最多保留2位小数');
|
||||
}
|
||||
$config['customerRewardAmount'] = intval(round($customerRewardAmount * 100)); // 元转分
|
||||
|
||||
// 验证添加奖励金额(元转分)
|
||||
$addFriendRewardAmount = isset($params['addFriendRewardAmount']) ? floatval($params['addFriendRewardAmount']) : 0;
|
||||
if ($addFriendRewardAmount < 0) {
|
||||
throw new \Exception('添加奖励金额不能为负数');
|
||||
}
|
||||
if ($addFriendRewardAmount > 0 && !preg_match('/^\d+(\.\d{1,2})?$/', (string)$addFriendRewardAmount)) {
|
||||
throw new \Exception('添加奖励金额格式不正确,最多保留2位小数');
|
||||
}
|
||||
$config['addFriendRewardAmount'] = intval(round($addFriendRewardAmount * 100)); // 元转分
|
||||
|
||||
return $config;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\plan;
|
||||
|
||||
use library\ResponseHelper;
|
||||
use think\Controller;
|
||||
use think\Db;
|
||||
use app\cunkebao\service\DistributionRewardService;
|
||||
|
||||
/**
|
||||
* 对外API接口控制器
|
||||
*/
|
||||
class PostExternalApiV1Controller extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* 验证签名
|
||||
*
|
||||
* @param array $params 请求参数
|
||||
* @param string $apiKey API密钥
|
||||
* @param string $sign 签名
|
||||
* @return bool
|
||||
*/
|
||||
private function validateSign($params, $apiKey, $sign)
|
||||
{
|
||||
// 1. 从参数中移除sign和apiKey
|
||||
unset($params['sign'], $params['apiKey'],$params['portrait']);
|
||||
|
||||
// 2. 移除空值
|
||||
$params = array_filter($params, function($value) {
|
||||
return !is_null($value) && $value !== '';
|
||||
});
|
||||
|
||||
// 3. 参数按键名升序排序
|
||||
ksort($params);
|
||||
|
||||
// 4. 直接拼接参数值
|
||||
$stringToSign = implode('', array_values($params));
|
||||
|
||||
// 5. 第一次MD5加密
|
||||
$firstMd5 = md5($stringToSign);
|
||||
|
||||
// 6. 拼接apiKey并第二次MD5加密
|
||||
$expectedSign = md5($firstMd5 . $apiKey);
|
||||
|
||||
// 7. 比对签名
|
||||
return $expectedSign === $sign;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对外API接口入口
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$params = $this->request->param();
|
||||
|
||||
// 验证必填参数
|
||||
if (empty($params['apiKey'])) {
|
||||
return ResponseHelper::error('apiKey不能为空', 400);
|
||||
}
|
||||
|
||||
if (empty($params['sign'])) {
|
||||
return ResponseHelper::error('sign不能为空', 400);
|
||||
}
|
||||
|
||||
if (empty($params['timestamp'])) {
|
||||
return ResponseHelper::error('timestamp不能为空', 400);
|
||||
}
|
||||
|
||||
// 验证时间戳(允许5分钟误差)
|
||||
if (abs(time() - intval($params['timestamp'])) > 300) {
|
||||
return ResponseHelper::error('请求已过期', 400);
|
||||
}
|
||||
|
||||
// 查询API密钥是否存在
|
||||
$plan = Db::name('customer_acquisition_task')
|
||||
->where('apiKey', $params['apiKey'])
|
||||
->where('status', 1)
|
||||
->find();
|
||||
|
||||
if (!$plan) {
|
||||
return ResponseHelper::error('无效的apiKey', 401);
|
||||
}
|
||||
|
||||
// 验证签名
|
||||
if (!$this->validateSign($params,$params['apiKey'], $params['sign'])) {
|
||||
return ResponseHelper::error('签名验证失败', 401);
|
||||
}
|
||||
|
||||
$identifier = !empty($params['wechatId']) ? $params['wechatId'] : $params['phone'];
|
||||
|
||||
// 渠道ID(cid),对应 distribution_channel.id
|
||||
$channelId = !empty($params['cid']) ? intval($params['cid']) : 0;
|
||||
|
||||
|
||||
$trafficPool = Db::name('traffic_pool')->where('identifier', $identifier)->find();
|
||||
if (!$trafficPool) {
|
||||
$trafficPoolId =Db::name('traffic_pool')->insertGetId([
|
||||
'identifier' => $identifier,
|
||||
'mobile' => !empty($params['phone']) ? $params['phone'] : '',
|
||||
'createTime' => time()
|
||||
]);
|
||||
}else{
|
||||
$trafficPoolId = $trafficPool['id'];
|
||||
}
|
||||
|
||||
$taskCustomer = Db::name('task_customer')
|
||||
->where('task_id', $plan['id'])
|
||||
->where('phone', $identifier)
|
||||
->find();
|
||||
|
||||
// 处理用户画像
|
||||
if(!empty($params['portrait']) && is_array($params['portrait'])){
|
||||
$this->updatePortrait($params['portrait'],$trafficPoolId,$plan['companyId']);
|
||||
}
|
||||
if (!$taskCustomer) {
|
||||
$tags = !empty($params['tags']) ? explode(',', $params['tags']) : [];
|
||||
$siteTags = !empty($params['siteTags']) ? explode(',', $params['siteTags']) : [];
|
||||
|
||||
// 处理渠道ID:只有在分销配置中允许、且渠道本身正常时,才记录到task_customer
|
||||
$finalChannelId = 0;
|
||||
if ($channelId > 0) {
|
||||
$sceneConf = json_decode($plan['sceneConf'], true) ?: [];
|
||||
$distributionConfig = $sceneConf['distribution'] ?? null;
|
||||
$allowedChannelIds = $distributionConfig['channels'] ?? [];
|
||||
if (!empty($distributionConfig) && !empty($distributionConfig['enabled']) && in_array($channelId, $allowedChannelIds)) {
|
||||
// 验证渠道是否存在且正常
|
||||
$channel = Db::name('distribution_channel')
|
||||
->where([
|
||||
['id', '=', $channelId],
|
||||
['companyId', '=', $plan['companyId']],
|
||||
['status', '=', 'enabled'],
|
||||
['deleteTime', '=', 0]
|
||||
])
|
||||
->find();
|
||||
if ($channel) {
|
||||
$finalChannelId = intval($channelId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$customerId = Db::name('task_customer')->insertGetId([
|
||||
'task_id' => $plan['id'],
|
||||
'channelId' => $finalChannelId,
|
||||
'phone' => $identifier,
|
||||
'name' => !empty($params['name']) ? $params['name'] : '',
|
||||
'source' => !empty($params['source']) ? $params['source'] : '',
|
||||
'remark' => !empty($params['remark']) ? $params['remark'] : '',
|
||||
'tags' => json_encode($tags,256),
|
||||
'siteTags' => json_encode($siteTags,256),
|
||||
'createTime' => time(),
|
||||
]);
|
||||
|
||||
// 记录获客奖励(异步处理,不影响主流程)
|
||||
if ($customerId) {
|
||||
try {
|
||||
// 只有在存在有效渠道ID时才触发分佣
|
||||
if ($finalChannelId > 0) {
|
||||
DistributionRewardService::recordCustomerReward($plan['id'], $customerId, $identifier, $finalChannelId);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// 记录错误但不影响主流程
|
||||
\think\facade\Log::error('记录获客奖励失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'message' => '新增成功',
|
||||
'data' => $identifier
|
||||
]);
|
||||
}else{
|
||||
$siteTags = !empty($params['siteTags']) ? explode(',',$params['siteTags']) : [];
|
||||
|
||||
// 更新新老标签数据,实现去重
|
||||
$this->updateSiteTags($taskCustomer['id'], $siteTags);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'message' => '已存在',
|
||||
'data' => $identifier
|
||||
]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户画像
|
||||
* @param array $data 用户画像数据
|
||||
* @param int $trafficPoolId 流量池id
|
||||
*/
|
||||
public function updatePortrait($data,$trafficPoolId,$companyId)
|
||||
{
|
||||
if(empty($data) || empty($trafficPoolId) || !is_array($data)){
|
||||
return;
|
||||
}
|
||||
|
||||
$type = !empty($data['type']) ? $data['type'] : 0;
|
||||
$source = !empty($data['source']) ? $data['source'] : 0;
|
||||
$sourceData = !empty($data['sourceData']) ? $data['sourceData'] : [];
|
||||
$remark = !empty($data['remark']) ? $data['remark'] : '';
|
||||
$uniqueId = !empty($data['uniqueId']) ? $data['uniqueId'] : 0;
|
||||
ksort($sourceData);
|
||||
$sourceData = json_encode($sourceData,256);
|
||||
|
||||
|
||||
$data = [
|
||||
'companyId' => $companyId,
|
||||
'trafficPoolId' => $trafficPoolId,
|
||||
'type' => $type,
|
||||
'source' => $source,
|
||||
'sourceData' => $sourceData,
|
||||
'remark' => $remark,
|
||||
'uniqueId' => $uniqueId,
|
||||
'count' => 1,
|
||||
'createTime' => time(),
|
||||
'updateTime' => time(),
|
||||
];
|
||||
|
||||
$res= Db::name('user_portrait')
|
||||
->where(['trafficPoolId'=>$trafficPoolId,'type'=>$type,'source'=>$source,'uniqueId'=>$uniqueId])
|
||||
->where('createTime','>',time()-1800)
|
||||
->find();
|
||||
if($res){
|
||||
$count = $res['count'] + 1;
|
||||
Db::name('user_portrait')->where(['id'=>$res['id']])->update(['count'=>$count,'updateTime'=>time()]);
|
||||
}else{
|
||||
Db::name('user_portrait')->insert($data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新站点标签数据,实现去重
|
||||
* @param int $taskCustomerId 任务客户ID
|
||||
* @param array $newSiteTags 新的站点标签数组
|
||||
*/
|
||||
private function updateSiteTags($taskCustomerId, $newSiteTags)
|
||||
{
|
||||
|
||||
if (empty($taskCustomerId) || empty($newSiteTags) || !is_array($newSiteTags)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取当前任务客户的站点标签
|
||||
$taskCustomer = Db::name('task_customer')->where('id', $taskCustomerId)->find();
|
||||
|
||||
if (!$taskCustomer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 解析现有的站点标签
|
||||
$existingSiteTags = [];
|
||||
if (!empty($taskCustomer['siteTags'])) {
|
||||
$existingSiteTags = json_decode($taskCustomer['siteTags'], true);
|
||||
if (!is_array($existingSiteTags)) {
|
||||
$existingSiteTags = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 合并新老标签并去重
|
||||
$mergedSiteTags = array_merge($existingSiteTags, $newSiteTags);
|
||||
$uniqueSiteTags = array_unique($mergedSiteTags);
|
||||
|
||||
// 过滤空值并重新索引数组
|
||||
$uniqueSiteTags = array_values(array_filter($uniqueSiteTags, function($tag) {
|
||||
return !empty(trim($tag));
|
||||
}));
|
||||
|
||||
|
||||
// 更新数据库中的站点标签
|
||||
Db::name('task_customer')->where('id', $taskCustomerId)->update([
|
||||
'siteTags' => json_encode($uniqueSiteTags, JSON_UNESCAPED_UNICODE),
|
||||
'updateTime' => time()
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// 记录错误日志,但不影响主流程
|
||||
\think\facade\Log::error('更新站点标签失败: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\plan;
|
||||
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
use think\Controller;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 更新获客计划控制器
|
||||
*/
|
||||
class PostUpdateAddFriendPlanV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 更新计划任务
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$params = $this->request->param();
|
||||
|
||||
// 验证必填字段
|
||||
if (empty($params['planId'])) {
|
||||
return ResponseHelper::error('计划ID不能为空', 400);
|
||||
}
|
||||
|
||||
if (empty($params['name'])) {
|
||||
return ResponseHelper::error('计划名称不能为空', 400);
|
||||
}
|
||||
|
||||
if (empty($params['sceneId'])) {
|
||||
return ResponseHelper::error('场景ID不能为空', 400);
|
||||
}
|
||||
|
||||
if (empty($params['deviceGroups'])) {
|
||||
return ResponseHelper::error('请选择设备', 400);
|
||||
}
|
||||
|
||||
// 检查计划是否存在
|
||||
$plan = Db::name('customer_acquisition_task')
|
||||
->where('id', $params['planId'])
|
||||
->find();
|
||||
|
||||
if (!$plan) {
|
||||
return ResponseHelper::error('计划不存在', 404);
|
||||
}
|
||||
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
// 处理分销配置
|
||||
$distributionConfig = $this->processDistributionConfig($params, $companyId);
|
||||
|
||||
// 归类参数
|
||||
$msgConf = isset($params['messagePlans']) ? $params['messagePlans'] : [];
|
||||
$tagConf = [
|
||||
'scenarioTags' => $params['scenarioTags'] ?? [],
|
||||
'customTags' => $params['customTags'] ?? [],
|
||||
];
|
||||
$reqConf = [
|
||||
'device' => $params['deviceGroups'] ?? [],
|
||||
'remarkType' => $params['remarkType'] ?? '',
|
||||
'greeting' => $params['greeting'] ?? '',
|
||||
'addFriendInterval' => $params['addFriendInterval'] ?? '',
|
||||
'startTime' => $params['startTime'] ?? '',
|
||||
'endTime' => $params['endTime'] ?? '',
|
||||
];
|
||||
|
||||
// 其余参数归为sceneConf
|
||||
$sceneConf = $params;
|
||||
unset(
|
||||
$sceneConf['id'],
|
||||
$sceneConf['apiKey'],
|
||||
$sceneConf['userId'],
|
||||
$sceneConf['status'],
|
||||
$sceneConf['planId'],
|
||||
$sceneConf['name'],
|
||||
$sceneConf['sceneId'],
|
||||
$sceneConf['messagePlans'],
|
||||
$sceneConf['scenarioTags'],
|
||||
$sceneConf['customTags'],
|
||||
$sceneConf['deviceGroups'],
|
||||
$sceneConf['orderTableFileName'],
|
||||
$sceneConf['userInfo'],
|
||||
$sceneConf['textUrl'],
|
||||
$sceneConf['remarkType'],
|
||||
$sceneConf['greeting'],
|
||||
$sceneConf['addFriendInterval'],
|
||||
$sceneConf['startTime'],
|
||||
$sceneConf['orderTableFile'],
|
||||
$sceneConf['endTime'],
|
||||
$sceneConf['distributionEnabled'],
|
||||
$sceneConf['distributionChannels'],
|
||||
$sceneConf['customerRewardAmount'],
|
||||
$sceneConf['addFriendRewardAmount']
|
||||
);
|
||||
|
||||
// 将分销配置添加到sceneConf中
|
||||
$sceneConf['distribution'] = $distributionConfig;
|
||||
|
||||
// 构建更新数据
|
||||
$data = [
|
||||
'name' => $params['name'],
|
||||
'sceneId' => $params['sceneId'],
|
||||
'sceneConf' => json_encode($sceneConf, JSON_UNESCAPED_UNICODE),
|
||||
'reqConf' => json_encode($reqConf, JSON_UNESCAPED_UNICODE),
|
||||
'msgConf' => json_encode($msgConf, JSON_UNESCAPED_UNICODE),
|
||||
'tagConf' => json_encode($tagConf, JSON_UNESCAPED_UNICODE),
|
||||
'status' => !empty($params['status']) ? 1 : 0,
|
||||
'updateTime' => time(),
|
||||
];
|
||||
|
||||
|
||||
try {
|
||||
// 更新数据
|
||||
$result = Db::name('customer_acquisition_task')
|
||||
->where('id', $params['planId'])
|
||||
->update($data);
|
||||
|
||||
if ($result === false) {
|
||||
throw new \Exception('更新计划失败');
|
||||
}
|
||||
|
||||
//订单
|
||||
if ($params['sceneId'] == 2) {
|
||||
if (!empty($params['orderFileUrl'])) {
|
||||
// 先下载到本地临时文件,再分析,最后删除
|
||||
$originPath = $params['orderFileUrl'];
|
||||
$tmpFile = tempnam(sys_get_temp_dir(), 'order_');
|
||||
// 判断是否为远程文件
|
||||
if (preg_match('/^https?:\/\//i', $originPath)) {
|
||||
// 远程URL,下载到本地
|
||||
$fileContent = file_get_contents($originPath);
|
||||
if ($fileContent === false) {
|
||||
exit('远程文件下载失败: ' . $originPath);
|
||||
}
|
||||
file_put_contents($tmpFile, $fileContent);
|
||||
} else {
|
||||
// 本地文件,直接copy
|
||||
if (!file_exists($originPath)) {
|
||||
exit('文件不存在: ' . $originPath);
|
||||
}
|
||||
copy($originPath, $tmpFile);
|
||||
}
|
||||
// 解析临时文件
|
||||
$ext = strtolower(pathinfo($originPath, PATHINFO_EXTENSION));
|
||||
$rows = [];
|
||||
if (in_array($ext, ['xls', 'xlsx'])) {
|
||||
// 直接用composer自动加载的PHPExcel
|
||||
$excel = \PHPExcel_IOFactory::load($tmpFile);
|
||||
$sheet = $excel->getActiveSheet();
|
||||
$data = $sheet->toArray();
|
||||
if (count($data) > 1) {
|
||||
array_shift($data); // 去掉表头
|
||||
}
|
||||
|
||||
foreach ($data as $cols) {
|
||||
$rows[] = [
|
||||
'name' => isset($cols[0]) ? trim($cols[0]) : '',
|
||||
'phone' => isset($cols[1]) ? trim($cols[1]) : '',
|
||||
'wechatId' => isset($cols[2]) ? trim($cols[2]) : '',
|
||||
'source' => isset($cols[3]) ? trim($cols[3]) : '',
|
||||
'orderAmount' => isset($cols[4]) ? trim($cols[4]) : '',
|
||||
'orderDate' => isset($cols[5]) ? trim($cols[5]) : '',
|
||||
];
|
||||
}
|
||||
} elseif ($ext === 'csv') {
|
||||
$content = file_get_contents($tmpFile);
|
||||
$lines = preg_split('/\r\n|\r|\n/', $content);
|
||||
if (count($lines) > 1) {
|
||||
array_shift($lines); // 去掉表头
|
||||
foreach ($lines as $line) {
|
||||
if (trim($line) === '') continue;
|
||||
$cols = str_getcsv($line);
|
||||
if (count($cols) >= 6) {
|
||||
$rows[] = [
|
||||
'name' => isset($cols[0]) ? trim($cols[0]) : '',
|
||||
'phone' => isset($cols[1]) ? trim($cols[1]) : '',
|
||||
'wechatId' => isset($cols[2]) ? trim($cols[2]) : '',
|
||||
'source' => isset($cols[3]) ? trim($cols[3]) : '',
|
||||
'orderAmount' => isset($cols[4]) ? trim($cols[4]) : '',
|
||||
'orderDate' => isset($cols[5]) ? trim($cols[5]) : '',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unlink($tmpFile);
|
||||
exit('暂不支持的文件类型: ' . $ext);
|
||||
}
|
||||
// 删除临时文件
|
||||
unlink($tmpFile);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//电话获客
|
||||
if ($params['sceneId'] == 5) {
|
||||
$rows = Db::name('call_recording')
|
||||
->where('companyId', $this->getUserInfo('companyId'))
|
||||
->group('phone')
|
||||
->field('id,phone')
|
||||
->select();
|
||||
}
|
||||
|
||||
//群获客
|
||||
if ($params['sceneId'] == 7) {
|
||||
if (!empty($params['wechatGroups']) && is_array($params['wechatGroups'])) {
|
||||
$rows = Db::name('wechat_group_member')->alias('gm')
|
||||
->join('wechat_account wa', 'gm.identifier = wa.wechatId')
|
||||
->whereIn('gm.groupId', $params['wechatGroups'])
|
||||
->group('gm.identifier')
|
||||
->column('wa.id,wa.wechatId,wa.alias,wa.phone');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (in_array($params['sceneId'], [2, 5, 7]) && !empty($rows) && is_array($rows)) {
|
||||
// 1000条为一组进行批量处理
|
||||
$batchSize = 1000;
|
||||
$totalRows = count($rows);
|
||||
|
||||
for ($i = 0; $i < $totalRows; $i += $batchSize) {
|
||||
$batchRows = array_slice($rows, $i, $batchSize);
|
||||
if (!empty($batchRows)) {
|
||||
// 1. 提取当前批次的phone
|
||||
// 1. 提取当前批次的phone
|
||||
$phones = [];
|
||||
foreach ($batchRows as $row) {
|
||||
if (!empty($row['phone'])) {
|
||||
$phone = $row['phone'];
|
||||
} elseif (!empty($row['alias'])) {
|
||||
$phone = $row['alias'];
|
||||
} else {
|
||||
$phone = $row['wechatId'];
|
||||
}
|
||||
if (!empty($phone)) {
|
||||
$phones[] = $phone;
|
||||
}
|
||||
}
|
||||
// 2. 批量查询已存在的phone
|
||||
$existingPhones = [];
|
||||
if (!empty($phones)) {
|
||||
$existing = Db::name('task_customer')
|
||||
->where('task_id', $params['planId'])
|
||||
->where('phone', 'in', $phones)
|
||||
->field('phone')
|
||||
->select();
|
||||
$existingPhones = array_column($existing, 'phone');
|
||||
}
|
||||
|
||||
// 3. 过滤出新数据,批量插入
|
||||
$newData = [];
|
||||
foreach ($batchRows as $row) {
|
||||
if (!empty($row['phone'])) {
|
||||
$phone = $row['phone'];
|
||||
} elseif (!empty($row['alias'])) {
|
||||
$phone = $row['alias'];
|
||||
} else {
|
||||
$phone = $row['wechatId'];
|
||||
}
|
||||
if (!empty($phone) && !in_array($phone, $existingPhones)) {
|
||||
$newData[] = [
|
||||
'task_id' => $params['planId'],
|
||||
'name' => !empty($row['name']) ? $row['name'] : '',
|
||||
'source' => '场景获客_' . $params['name'] ?? '',
|
||||
'phone' => $phone,
|
||||
'tags' => json_encode([], JSON_UNESCAPED_UNICODE),
|
||||
'siteTags' => json_encode([], JSON_UNESCAPED_UNICODE),
|
||||
'createTime' => time(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 批量插入新数据
|
||||
if (!empty($newData)) {
|
||||
Db::name('task_customer')->insertAll($newData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return ResponseHelper::success(['planId' => $params['planId']], '更新计划任务成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// 回滚事务
|
||||
Db::rollback();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理分销配置
|
||||
*
|
||||
* @param array $params 请求参数
|
||||
* @param int $companyId 公司ID
|
||||
* @return array 分销配置
|
||||
*/
|
||||
private function processDistributionConfig($params, $companyId)
|
||||
{
|
||||
$distributionEnabled = !empty($params['distributionEnabled']) ? true : false;
|
||||
|
||||
$config = [
|
||||
'enabled' => $distributionEnabled,
|
||||
'channels' => [],
|
||||
'customerRewardAmount' => 0, // 获客奖励金额(分)
|
||||
'addFriendRewardAmount' => 0, // 添加奖励金额(分)
|
||||
];
|
||||
|
||||
// 如果未开启分销,直接返回默认配置
|
||||
if (!$distributionEnabled) {
|
||||
return $config;
|
||||
}
|
||||
|
||||
// 验证渠道ID
|
||||
$channelIds = $params['distributionChannels'] ?? [];
|
||||
if (empty($channelIds) || !is_array($channelIds)) {
|
||||
throw new \Exception('请选择至少一个分销渠道');
|
||||
}
|
||||
|
||||
// 查询有效的渠道(只保留存在且已启用的渠道)
|
||||
$channels = Db::name('distribution_channel')
|
||||
->where([
|
||||
['id', 'in', $channelIds],
|
||||
['companyId', '=', $companyId],
|
||||
['status', '=', 'enabled'],
|
||||
['deleteTime', '=', 0]
|
||||
])
|
||||
->field('id,code,name')
|
||||
->select();
|
||||
|
||||
// 如果没有有效渠道,才报错
|
||||
if (empty($channels)) {
|
||||
throw new \Exception('所选的分销渠道均不存在或已被禁用,请重新选择');
|
||||
}
|
||||
|
||||
// 只保留有效的渠道ID
|
||||
$config['channels'] = array_column($channels, 'id');
|
||||
|
||||
// 验证获客奖励金额(元转分)
|
||||
$customerRewardAmount = isset($params['customerRewardAmount']) ? floatval($params['customerRewardAmount']) : 0;
|
||||
if ($customerRewardAmount < 0) {
|
||||
throw new \Exception('获客奖励金额不能为负数');
|
||||
}
|
||||
if ($customerRewardAmount > 0 && !preg_match('/^\d+(\.\d{1,2})?$/', (string)$customerRewardAmount)) {
|
||||
throw new \Exception('获客奖励金额格式不正确,最多保留2位小数');
|
||||
}
|
||||
$config['customerRewardAmount'] = intval(round($customerRewardAmount * 100)); // 元转分
|
||||
|
||||
// 验证添加奖励金额(元转分)
|
||||
$addFriendRewardAmount = isset($params['addFriendRewardAmount']) ? floatval($params['addFriendRewardAmount']) : 0;
|
||||
if ($addFriendRewardAmount < 0) {
|
||||
throw new \Exception('添加奖励金额不能为负数');
|
||||
}
|
||||
if ($addFriendRewardAmount > 0 && !preg_match('/^\d+(\.\d{1,2})?$/', (string)$addFriendRewardAmount)) {
|
||||
throw new \Exception('添加奖励金额格式不正确,最多保留2位小数');
|
||||
}
|
||||
$config['addFriendRewardAmount'] = intval(round($addFriendRewardAmount * 100)); // 元转分
|
||||
|
||||
return $config;
|
||||
}
|
||||
}
|
||||
403
application/cunkebao/controller/plan/PosterWeChatMiniProgram.php
Normal file
403
application/cunkebao/controller/plan/PosterWeChatMiniProgram.php
Normal file
@@ -0,0 +1,403 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\plan;
|
||||
|
||||
use think\Controller;
|
||||
use think\Request;
|
||||
use EasyWeChat\Factory;
|
||||
use think\facade\Env;
|
||||
|
||||
// use EasyWeChat\Kernel\Exceptions\DecryptException;
|
||||
use EasyWeChat\Kernel\Http\StreamResponse;
|
||||
use think\Db;
|
||||
|
||||
class PosterWeChatMiniProgram extends Controller
|
||||
{
|
||||
|
||||
protected $config;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
// 从环境变量获取配置
|
||||
$this->config = [
|
||||
'app_id' => Env::get('weChat.appidMiniApp','wx789850448e26c91d'),
|
||||
'secret' => Env::get('weChat.secretMiniApp','d18f75b3a3623cb40da05648b08365a1'),
|
||||
'response_type' => 'array'
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public function index()
|
||||
{
|
||||
return 'Hello, World!';
|
||||
}
|
||||
|
||||
|
||||
// 生成小程序码,存客宝-操盘手调用
|
||||
public function generateMiniProgramCodeWithScene($taskId = '', $channelId = 0)
|
||||
{
|
||||
|
||||
if (empty($taskId)) {
|
||||
return json_encode(['code' => 500, 'data' => '', 'msg' => '任务id不能为空']);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
$app = Factory::miniProgram($this->config);
|
||||
// scene参数长度限制为32位
|
||||
// 如果提供了channelId,格式为:taskId,channelId
|
||||
// 如果没有channelId,格式为:taskId
|
||||
if (!empty($channelId) && $channelId > 0) {
|
||||
$scene = sprintf("%s,%s", $taskId, $channelId);
|
||||
} else {
|
||||
$scene = sprintf("%s", $taskId);
|
||||
}
|
||||
|
||||
// 确保scene长度不超过32位
|
||||
if (strlen($scene) > 32) {
|
||||
$scene = substr($scene, 0, 32);
|
||||
}
|
||||
|
||||
// 调用接口生成小程序码
|
||||
$response = $app->app_code->getUnlimit($scene, [
|
||||
'page' => 'pages/poster/index2', // 必须是已经发布的小程序页面
|
||||
'width' => 430, // 二维码的宽度,默认430
|
||||
// 'auto_color' => false, // 自动配置线条颜色
|
||||
// 'line_color' => ['r' => 0, 'g' => 0, 'b' => 0], // 颜色设置
|
||||
// 'is_hyaline' => false, // 是否需要透明底色
|
||||
]);
|
||||
// 保存小程序码到文件
|
||||
if ($response instanceof StreamResponse) {
|
||||
// $filename = 'minicode_' . $taskId . '.png';
|
||||
// $response->saveAs('path/to/codes', $filename);
|
||||
// return 'path/to/codes/' . $filename;
|
||||
|
||||
$img = $response->getBody()->getContents();//获取图片二进制流
|
||||
$img_base64 = 'data:image/png;base64,' . base64_encode($img);//转化base64
|
||||
return json_encode(['code' => 200, 'data' => $img_base64]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return json_encode(['code' => 500, 'data' => '', 'msg' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
// getPhoneNumber
|
||||
public function getPhoneNumber()
|
||||
{
|
||||
|
||||
$taskId = request()->param('id');
|
||||
$code = request()->param('code');
|
||||
// code 不能为空
|
||||
if (!$code) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'message' => 'code不能为空'
|
||||
]);
|
||||
}
|
||||
|
||||
$task = Db::name('customer_acquisition_task')->where('id', $taskId)->find();
|
||||
if (!$task) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'message' => '任务不存在'
|
||||
]);
|
||||
}
|
||||
|
||||
$app = Factory::miniProgram($this->config);
|
||||
|
||||
$result = $app->phone_number->getUserPhoneNumber($code);
|
||||
|
||||
if ($result['errcode'] == 0 && isset($result['phone_info']['phoneNumber'])) {
|
||||
|
||||
// TODO 拿到手机号之后的后续操作:
|
||||
// 1. 先写入 ck_traffic_pool 表 identifier mobile 都是 用 phone字段的值
|
||||
$trafficPool = Db::name('traffic_pool')->where('identifier', $result['phone_info']['phoneNumber'])->find();
|
||||
if (!$trafficPool) {
|
||||
Db::name('traffic_pool')->insert([
|
||||
'identifier' => $result['phone_info']['phoneNumber'],
|
||||
'mobile' => $result['phone_info']['phoneNumber'],
|
||||
'createTime' => time()
|
||||
]);
|
||||
}
|
||||
// 2. 写入 ck_task_customer: 以 task_id ~~identifier~~ phone 为条件,如果存在则忽略,使用类似laravel的firstOrcreate(但我不知道thinkphp5.1里的写法)
|
||||
// $taskCustomer = Db::name('task_customer')->where('task_id', $taskId)->where('identifier', $result['phone_info']['phoneNumber'])->find();
|
||||
$taskCustomer = Db::name('task_customer')
|
||||
->where('task_id', $taskId)
|
||||
->where('phone', $result['phone_info']['phoneNumber'])
|
||||
->find();
|
||||
if (!$taskCustomer) {
|
||||
// 渠道ID(cid),对应 distribution_channel.id
|
||||
$channelId = intval($this->request->param('cid', 0));
|
||||
|
||||
$finalChannelId = 0;
|
||||
if ($channelId > 0) {
|
||||
// 获取任务信息,解析分销配置
|
||||
$sceneConf = json_decode($task['sceneConf'] ?? '[]', true) ?: [];
|
||||
$distributionConfig = $sceneConf['distribution'] ?? null;
|
||||
$allowedChannelIds = $distributionConfig['channels'] ?? [];
|
||||
if (!empty($distributionConfig) && !empty($distributionConfig['enabled']) && in_array($channelId, $allowedChannelIds)) {
|
||||
// 验证渠道是否存在且正常
|
||||
$channel = Db::name('distribution_channel')
|
||||
->where([
|
||||
['id', '=', $channelId],
|
||||
['companyId', '=', $task['companyId']],
|
||||
['status', '=', 'enabled'],
|
||||
['deleteTime', '=', 0]
|
||||
])
|
||||
->find();
|
||||
if ($channel) {
|
||||
$finalChannelId = $channelId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$customerId = Db::name('task_customer')->insertGetId([
|
||||
'task_id' => $taskId,
|
||||
'channelId' => $finalChannelId,
|
||||
// 'identifier' => $result['phone_info']['phoneNumber'],
|
||||
'phone' => $result['phone_info']['phoneNumber'],
|
||||
'source' => $task['name'],
|
||||
'createTime' => time(),
|
||||
'tags' => json_encode([]),
|
||||
'siteTags' => json_encode([]),
|
||||
]);
|
||||
|
||||
// 记录获客奖励(异步处理,不影响主流程)
|
||||
if ($customerId) {
|
||||
try {
|
||||
if ($finalChannelId > 0) {
|
||||
\app\cunkebao\service\DistributionRewardService::recordCustomerReward(
|
||||
$taskId,
|
||||
$customerId,
|
||||
$result['phone_info']['phoneNumber'],
|
||||
$finalChannelId
|
||||
);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// 记录错误但不影响主流程
|
||||
\think\facade\Log::error('记录获客奖励失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
// return $result['phone_info']['phoneNumber'];
|
||||
return json([
|
||||
'code' => 200,
|
||||
'message' => '获取手机号成功',
|
||||
'data' => $result['phone_info']['phoneNumber']
|
||||
]);
|
||||
} else {
|
||||
// return null;
|
||||
return json([
|
||||
'code' => 400,
|
||||
'message' => '获取手机号失败: ' . $result['errmsg'] ?? '未知错误'
|
||||
]);
|
||||
}
|
||||
|
||||
// return $result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function decryptphones()
|
||||
{
|
||||
|
||||
$taskId = request()->param('id');
|
||||
$rawInput = trim((string)request()->param('phone', ''));
|
||||
// 渠道ID(cid),对应 distribution_channel.id
|
||||
$channelId = intval(request()->param('cid', 0));
|
||||
if ($rawInput === '') {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'message' => '手机号或微信号不能为空'
|
||||
]);
|
||||
}
|
||||
$task = Db::name('customer_acquisition_task')->where('id', $taskId)->find();
|
||||
|
||||
if (!$task) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'message' => '任务不存在'
|
||||
]);
|
||||
}
|
||||
|
||||
// 预先根据任务的分销配置校验渠道是否有效(仅当传入了cid时)
|
||||
$finalChannelId = 0;
|
||||
if ($channelId > 0) {
|
||||
$sceneConf = json_decode($task['sceneConf'] ?? '[]', true) ?: [];
|
||||
$distributionConfig = $sceneConf['distribution'] ?? null;
|
||||
$allowedChannelIds = $distributionConfig['channels'] ?? [];
|
||||
if (!empty($distributionConfig) && !empty($distributionConfig['enabled']) && in_array($channelId, $allowedChannelIds)) {
|
||||
// 验证渠道是否存在且正常
|
||||
$channel = Db::name('distribution_channel')
|
||||
->where([
|
||||
['id', '=', $channelId],
|
||||
['companyId', '=', $task['companyId']],
|
||||
['status', '=', 'enabled'],
|
||||
['deleteTime', '=', 0]
|
||||
])
|
||||
->find();
|
||||
if ($channel) {
|
||||
$finalChannelId = $channelId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$lines = preg_split('/\r\n|\r|\n/', $rawInput);
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '') {
|
||||
continue;
|
||||
}
|
||||
$parts = array_map('trim', explode(',', $line, 2));
|
||||
$identifier = $parts[0] ?? '';
|
||||
$remark = $parts[1] ?? '';
|
||||
if ($identifier === '') {
|
||||
continue;
|
||||
}
|
||||
$isPhone = preg_match('/^\+?\d{6,}$/', $identifier);
|
||||
$trafficPool = Db::name('traffic_pool')->where('identifier', $identifier)->find();
|
||||
if (!$trafficPool) {
|
||||
$insertData = [
|
||||
'identifier' => $identifier,
|
||||
'createTime' => time()
|
||||
];
|
||||
if ($isPhone) {
|
||||
$insertData['mobile'] = $identifier;
|
||||
} else {
|
||||
$insertData['wechatId'] = $identifier;
|
||||
}
|
||||
Db::name('traffic_pool')->insert($insertData);
|
||||
} else {
|
||||
$updates = [];
|
||||
if ($isPhone && empty($trafficPool['mobile'])) {
|
||||
$updates['mobile'] = $identifier;
|
||||
}
|
||||
if (!$isPhone && empty($trafficPool['wechatId'])) {
|
||||
$updates['wechatId'] = $identifier;
|
||||
}
|
||||
if (!empty($updates)) {
|
||||
$updates['updateTime'] = time();
|
||||
Db::name('traffic_pool')->where('id', $trafficPool['id'])->update($updates);
|
||||
}
|
||||
}
|
||||
|
||||
$taskCustomer = Db::name('task_customer')
|
||||
->where('task_id', $taskId)
|
||||
->where('phone', $identifier)
|
||||
->find();
|
||||
if (empty($taskCustomer)) {
|
||||
$insertCustomer = [
|
||||
'task_id' => $taskId,
|
||||
'channelId' => $finalChannelId, // 记录本次导入归属的分销渠道(如有)
|
||||
'phone' => $identifier,
|
||||
'source' => $task['name'],
|
||||
'createTime'=> time(),
|
||||
'tags' => json_encode([]),
|
||||
'siteTags' => json_encode([]),
|
||||
];
|
||||
if ($remark !== '') {
|
||||
$insertCustomer['remark'] = $remark;
|
||||
}
|
||||
// 使用 insertGetId 以便在需要时记录获客奖励
|
||||
$customerId = Db::name('task_customer')->insertGetId($insertCustomer);
|
||||
|
||||
// 表单录入成功即视为一次获客:
|
||||
// 仅在存在有效渠道ID时,记录获客奖励(谁的cid谁获客)
|
||||
if (!empty($customerId) && $finalChannelId > 0) {
|
||||
try {
|
||||
\app\cunkebao\service\DistributionRewardService::recordCustomerReward(
|
||||
$taskId,
|
||||
$customerId,
|
||||
$identifier,
|
||||
$finalChannelId
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
// 记录错误但不影响主流程
|
||||
\think\facade\Log::error('记录获客奖励失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
} elseif ($remark !== '' && $taskCustomer['remark'] !== $remark) {
|
||||
Db::name('task_customer')
|
||||
->where('id', $taskCustomer['id'])
|
||||
->update([
|
||||
'remark' => $remark,
|
||||
'updateTime' => time()
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// return $phone;
|
||||
return json([
|
||||
'code' => 200,
|
||||
'message' => '操作成功',
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
// return $result;
|
||||
|
||||
|
||||
// todo 获取海报获客任务的任务/海报数据 -- 表还没设计好,不急 ck_customer_acquisition_task
|
||||
public
|
||||
function getPosterTaskData()
|
||||
{
|
||||
$id = request()->param('id');
|
||||
$task = Db::name('customer_acquisition_task')
|
||||
->where(['id' => $id, 'deleteTime' => 0])
|
||||
->field('id,name,sceneConf,status')
|
||||
->find();
|
||||
if (!$task) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'message' => '任务不存在'
|
||||
]);
|
||||
}
|
||||
|
||||
if ($task['status'] == 0) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'message' => '任务已结束'
|
||||
]);
|
||||
}
|
||||
|
||||
$sceneConf = json_decode($task['sceneConf'], true);
|
||||
|
||||
if (isset($sceneConf['posters']['url'])) {
|
||||
$posterUrl = !empty($sceneConf['posters']['url']);
|
||||
} else {
|
||||
$posterUrl = 'https://hebbkx1anhila5yf.public.blob.vercel-storage.com/%E7%82%B9%E5%87%BB%E5%92%A8%E8%AF%A2-FTiyAMAPop2g9LvjLOLDz0VwPg3KVu.gif';
|
||||
}
|
||||
|
||||
|
||||
if (isset($sceneConf['tips'])) {
|
||||
$sTip = $sceneConf['tips'];
|
||||
} else {
|
||||
$sTip = '';
|
||||
}
|
||||
|
||||
unset($task['sceneConf']);
|
||||
$task['sTip'] = $sTip;
|
||||
|
||||
$data = [
|
||||
'id' => $task['id'],
|
||||
'name' => $task['name'],
|
||||
'poster' => ['sUrl' => $posterUrl],
|
||||
'task' => $task,
|
||||
];
|
||||
|
||||
|
||||
// todo 只需 返回 poster_url success_tip
|
||||
return json([
|
||||
'code' => 200,
|
||||
'message' => '获取海报获客任务数据成功',
|
||||
'data' => $data
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\traffic;
|
||||
|
||||
use app\common\model\TrafficPool as TrafficPoolModel;
|
||||
use app\common\model\TrafficSource as TrafficSourceModel;
|
||||
use app\common\model\WechatFriendShip as WechatFriendShipModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
|
||||
/**
|
||||
* 流量池控制器
|
||||
*/
|
||||
class GetConvertedListWithInCompanyV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 构建返回数据
|
||||
*
|
||||
* @param \think\Paginator $result
|
||||
* @return array
|
||||
*/
|
||||
protected function makeResultedSet(\think\Paginator $result): array
|
||||
{
|
||||
$resultSets = [];
|
||||
|
||||
foreach ($result->items() as $item) {
|
||||
$item->tags = json_decode($item->tags);
|
||||
|
||||
array_push($resultSets, $item->toArray());
|
||||
}
|
||||
|
||||
return $resultSets;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询条件
|
||||
*
|
||||
* @param array $params
|
||||
* @return array
|
||||
*/
|
||||
protected function makeWhere(array $params = []): array
|
||||
{
|
||||
if (!empty($keyword = $this->request->param('keyword'))) {
|
||||
$where[] = ['exp', "w.alias LIKE '%{$keyword}%' OR w.nickname LIKE '%{$keyword}%'"];
|
||||
}
|
||||
|
||||
// 来源的筛选
|
||||
if ($fromd = $this->request->param('fromd')) {
|
||||
$where['s.fromd'] = $fromd;
|
||||
}
|
||||
|
||||
$where['s.companyId'] = $this->getUserInfo('companyId');
|
||||
$where['s.status'] = TrafficSourceModel::STATUS_PASSED;
|
||||
|
||||
return array_merge($where, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取流量池列表
|
||||
*
|
||||
* @param array $where
|
||||
* @return \think\Paginator
|
||||
*/
|
||||
protected function getPoolListByCompanyId(array $where): \think\Paginator
|
||||
{
|
||||
$query = TrafficSourceModel::alias('s')
|
||||
->field(
|
||||
[
|
||||
'w.id', 'w.nickname', 'w.avatar',
|
||||
'CASE WHEN w.alias IS NULL OR w.alias = "" THEN w.wechatId ELSE w.alias END AS wechatId',
|
||||
's.fromd',
|
||||
'f.tags', 'f.createTime', TrafficSourceModel::STATUS_PASSED . ' status'
|
||||
]
|
||||
)
|
||||
->join('traffic_pool p', 'p.identifier=s.identifier')
|
||||
->join('wechat_account w', 'p.wechatId=w.wechatId')
|
||||
->join('wechat_friendship f', 'w.wechatId=f.wechatId and f.deleteTime=0')
|
||||
->order('s.id desc');
|
||||
|
||||
foreach ($where as $key => $value) {
|
||||
if (is_numeric($key) && is_array($value) && isset($value[0]) && $value[0] === 'exp') {
|
||||
$query->whereExp('', $value[1]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$query->where($key, $value);
|
||||
}
|
||||
|
||||
return $query->paginate($this->request->param('limit/d', 10), false, ['page' => $this->request->param('page/d', 1)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取流量池列表
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$result = $this->getPoolListByCompanyId( $this->makeWhere() );
|
||||
|
||||
return ResponseHelper::success(
|
||||
[
|
||||
'list' => $this->makeResultedSet($result),
|
||||
'total' => $result->total(),
|
||||
]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\traffic;
|
||||
|
||||
use app\common\model\TrafficSource as TrafficSourceModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
|
||||
/**
|
||||
* 流量池控制器
|
||||
*/
|
||||
class GetPoolStatisticsV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取今日转化数量
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function getTodayAddedCount(): int
|
||||
{
|
||||
return TrafficSourceModel::where(
|
||||
[
|
||||
'companyId' => $this->getUserInfo('companyId'),
|
||||
'status' => TrafficSourceModel::STATUS_PASSED,
|
||||
]
|
||||
)
|
||||
->whereBetween('updateTime',
|
||||
[
|
||||
strtotime(date('Y-m-d 00:00:00')),
|
||||
strtotime(date('Y-m-d 23:59:59'))
|
||||
]
|
||||
)
|
||||
->count('*');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取流量池总数
|
||||
*
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getTotalCount(): int
|
||||
{
|
||||
return TrafficSourceModel::where(
|
||||
[
|
||||
'companyId' => $this->getUserInfo('companyId')
|
||||
]
|
||||
)
|
||||
->count('*');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取流量池数据统计
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
return ResponseHelper::success(
|
||||
[
|
||||
'totalCount' => $this->getTotalCount(),
|
||||
'todayAddCount' => $this->getTodayAddedCount(),
|
||||
]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,655 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\traffic;
|
||||
|
||||
use app\common\model\TrafficPool as TrafficPoolModel;
|
||||
use app\common\model\TrafficSource as TrafficSourceModel;
|
||||
use app\common\model\WechatFriendShip as WechatFriendShipModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 流量池控制器
|
||||
*/
|
||||
class GetPotentialListWithInCompanyV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 构建查询条件
|
||||
*
|
||||
* @param array $params
|
||||
* @return array
|
||||
*/
|
||||
protected function makeWhere(array $params = []): array
|
||||
{
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
$device = $this->request->param('deviceId');
|
||||
$status = $this->request->param('addStatus', '');
|
||||
$taskId = $this->request->param('taskId', '');
|
||||
$packageId = $this->request->param('packageId', '');
|
||||
$where = [];
|
||||
if (!empty($keyword)) {
|
||||
$where[] = ['p.identifier|wa.nickname|wa.phone|wa.wechatId|wa.alias', 'like', '%' . $keyword . '%'];
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if (!empty($status)) {
|
||||
if ($status == 1) {
|
||||
$where[] = ['s.status', '=', 4];
|
||||
} elseif ($status == 2) {
|
||||
$where[] = ['s.status', '=', 0];
|
||||
} elseif ($status == -1) {
|
||||
$where[] = ['s.status', '=', 2];
|
||||
} elseif ($status == 3) {
|
||||
$where[] = ['s.status', '=', 2];
|
||||
}
|
||||
}
|
||||
|
||||
// 来源的筛选
|
||||
if ($packageId) {
|
||||
if ($packageId != -1) {
|
||||
$where[] = ['tsp.id', '=', $packageId];
|
||||
} else {
|
||||
$where[] = ['tsp.id', '=', null];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!empty($device)) {
|
||||
// $where[] = ['d.deviceId', '=', $device];
|
||||
}
|
||||
|
||||
if (!empty($taskId)) {
|
||||
//$where[] = ['t.sceneId', '=', $taskId];
|
||||
}
|
||||
$where[] = ['s.companyId', '=', $this->getUserInfo('companyId')];
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取流量池列表
|
||||
*
|
||||
* @param array $where
|
||||
* @return \think\Paginator
|
||||
*/
|
||||
protected function getPoolListByCompanyId(array $where, $isPage = true)
|
||||
{
|
||||
$query = TrafficPoolModel::alias('p')
|
||||
->field(
|
||||
[
|
||||
'p.id', 'p.identifier', 'p.mobile', 'p.wechatId', 'p.identifier',
|
||||
's.fromd', 's.status', 's.createTime', 's.companyId', 's.sourceId', 's.type',
|
||||
'wa.nickname', 'wa.avatar', 'wa.gender', 'wa.phone', 'wa.alias'
|
||||
]
|
||||
)
|
||||
->join('traffic_source s', 'p.identifier=s.identifier')
|
||||
->join('wechat_account wa', 'p.identifier=wa.wechatId', 'left')
|
||||
->join('traffic_source_package_item tspi', 'p.identifier = tspi.identifier AND s.companyId = tspi.companyId', 'left')
|
||||
->join('traffic_source_package tsp', 'tspi.packageId=tsp.id', 'left')
|
||||
->join('device_wechat_login d', 's.sourceId=d.wechatId', 'left')
|
||||
->where($where);
|
||||
|
||||
|
||||
$result = $query->order('p.id DESC,s.id DESC')->group('p.identifier');
|
||||
|
||||
if ($isPage) {
|
||||
$result = $query->paginate($this->request->param('limit/d', 10), false, ['page' => $this->request->param('page/d', 1)]);
|
||||
$list = $result->items();
|
||||
$total = $result->total();
|
||||
} else {
|
||||
$list = $result->select();
|
||||
$total = '';
|
||||
}
|
||||
|
||||
|
||||
if ($isPage) {
|
||||
foreach ($list as &$item) {
|
||||
//流量池筛选
|
||||
$package = Db::name('traffic_source_package_item')->alias('tspi')
|
||||
->join('traffic_source_package p', 'tspi.packageId=p.id AND tspi.companyId=p.companyId')
|
||||
->where(['tspi.identifier' => $item->identifier])
|
||||
->whereIn('tspi.companyId', [0, $item->companyId])
|
||||
->column('p.name');
|
||||
$item['packages'] = $package;
|
||||
if ($item->type == 1) {
|
||||
$tag = Db::name('wechat_friendship')->where(['wechatId' => $item->wechatId])->column('tags');
|
||||
$tags = [];
|
||||
foreach ($tag as $k => $v) {
|
||||
$v = json_decode($v, true);
|
||||
if (!empty($v)) {
|
||||
$tags = array_merge($tags, $v);
|
||||
}
|
||||
}
|
||||
$item['tags'] = $tags;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
unset($item);
|
||||
$data = ['list' => $list, 'total' => $total];
|
||||
return json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取流量池列表
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$result = $this->getPoolListByCompanyId($this->makeWhere());
|
||||
$result = json_decode($result, true);
|
||||
return ResponseHelper::success(
|
||||
[
|
||||
'list' => $result['list'],
|
||||
'total' => $result['total'],
|
||||
]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
|
||||
public function getUser()
|
||||
{
|
||||
|
||||
$wechatId = $this->request->param('wechatId', '');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($wechatId)) {
|
||||
return json_encode(['code' => 500, 'msg' => '微信id不能为空']);
|
||||
}
|
||||
|
||||
$total = [
|
||||
'msg' => 0,
|
||||
'money' => 0,
|
||||
'isFriend' => false,
|
||||
'percentage' => '0.00%',
|
||||
];
|
||||
|
||||
|
||||
$data = TrafficPoolModel::alias('p')
|
||||
->field(['p.id', 'p.identifier', 'p.wechatId',
|
||||
'wa.nickname', 'wa.avatar', 'wa.gender', 'wa.phone', 'wa.alias'])
|
||||
->join('wechat_account wa', 'p.identifier=wa.wechatId', 'left')
|
||||
->order('p.id DESC')
|
||||
->where(['p.identifier' => $wechatId])
|
||||
->group('p.identifier')
|
||||
->find();
|
||||
$data['lastMsgTime'] = '';
|
||||
|
||||
//来源
|
||||
$source = Db::name('traffic_source')->alias('ts')
|
||||
->field(['wa.nickname', 'wa.avatar', 'wa.gender', 'wa.phone', 'wa.wechatId', 'wa.alias',
|
||||
'ts.createTime',
|
||||
'wf.id as friendId', 'wf.wechatAccountId'])
|
||||
->join('wechat_account wa', 'ts.sourceId=wa.wechatId', 'left')
|
||||
->join(['s2_wechat_friend' => 'wf'], 'wa.wechatId=wf.ownerWechatId', 'left')
|
||||
->where(['ts.companyId' => $companyId, 'ts.identifier' => $data['identifier'], 'wf.wechatId' => $data['wechatId']])
|
||||
->order('ts.createTime DESC')
|
||||
->select();
|
||||
|
||||
$wechatFriendId = [];
|
||||
if (!empty($source)) {
|
||||
$total['isFriend'] = true;
|
||||
foreach ($source as &$v) {
|
||||
$wechatFriendId[] = $v['friendId'];
|
||||
//最后消息
|
||||
$v['createTime'] = date('Y-m-d H:i:s', $v['createTime']);
|
||||
$lastMsgTime = Db::table('s2_wechat_message')
|
||||
->where(['wechatFriendId' => $v['friendId'], 'wechatAccountId' => $v['wechatAccountId']])
|
||||
->value('wechatTime');
|
||||
$v['lastMsgTime'] = !empty($lastMsgTime) ? date('Y-m-d H:i:s', $lastMsgTime) : '';
|
||||
|
||||
//设备信息
|
||||
$device = Db::name('device_wechat_login')->alias('dwl')
|
||||
->join('device d', 'd.id=dwl.deviceId')
|
||||
->where(['dwl.wechatId' => $v['wechatId']])
|
||||
->field('d.id,d.memo,d.imei,d.brand,d.extra,d.alive')
|
||||
->order('dwl.id DESC')
|
||||
->find();
|
||||
$extra = json_decode($device['extra'], true);
|
||||
unset($device['extra']);
|
||||
$device['address'] = !empty($extra['address']) ? $extra['address'] : '';
|
||||
$v['device'] = $device;
|
||||
}
|
||||
unset($v);
|
||||
}
|
||||
$data['source'] = $source;
|
||||
|
||||
|
||||
//流量池
|
||||
$package = Db::name('traffic_source_package_item')->alias('tspi')
|
||||
->join('traffic_source_package p', 'tspi.packageId=p.id AND tspi.companyId=p.companyId')
|
||||
->where(['tspi.companyId' => $companyId, 'tspi.identifier' => $data['identifier']])
|
||||
->column('p.name');
|
||||
$package2 = Db::name('traffic_source_package_item')->alias('tspi')
|
||||
->join('traffic_source_package p', 'tspi.packageId=p.id')
|
||||
->where(['tspi.companyId' => $companyId, 'tspi.identifier' => $data['identifier']])
|
||||
->column('p.name');
|
||||
$packages = array_merge($package, $package2);
|
||||
$data['packages'] = $packages;
|
||||
|
||||
|
||||
if (!empty($wechatFriendId)) {
|
||||
//消息统计
|
||||
$msgTotal = Db::table('s2_wechat_message')
|
||||
->whereIn('wechatFriendId', $wechatFriendId)
|
||||
->count();
|
||||
$total['msg'] = $msgTotal;
|
||||
|
||||
//金额计算
|
||||
$money = Db::table('s2_wechat_message')
|
||||
->whereIn('wechatFriendId', $wechatFriendId)
|
||||
->where(['isSend' => 1, 'msgType' => 419430449])
|
||||
->select();
|
||||
if (!empty($money)) {
|
||||
foreach ($money as $v) {
|
||||
$content = json_decode($v['content'], true);
|
||||
if ($content['paysubtype'] == 1) {
|
||||
$number = number_format(str_replace("¥", "", $content['feedesc']), 2);
|
||||
$floatValue = floatval($number);
|
||||
$total['money'] += $floatValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$taskNum = Db::name('task_customer')->alias('tc')
|
||||
->join('customer_acquisition_task t', 'tc.task_id=t.id')
|
||||
->where(['t.companyId' => $companyId, 't.deleteTime' => 0])
|
||||
->whereIn('tc.phone', [$data['phone'], $data['wechatId'], $data['alias']])
|
||||
->count();
|
||||
|
||||
$passNum = Db::name('task_customer')->alias('tc')
|
||||
->join('customer_acquisition_task t', 'tc.task_id=t.id')
|
||||
->where(['t.companyId' => $companyId, 't.deleteTime' => 0, 'tc.status' => 4])
|
||||
->whereIn('tc.phone', [$data['phone'], $data['wechatId'], $data['alias']])
|
||||
->count();
|
||||
|
||||
if (!empty($taskNum) && !empty($passNum)) {
|
||||
$percentage = number_format(($taskNum / $passNum) * 100, 2);
|
||||
$total['percentage'] = $percentage;
|
||||
}
|
||||
|
||||
|
||||
$data['total'] = $total;
|
||||
$data['rmm'] = [
|
||||
'r' => 0,
|
||||
'f' => 0,
|
||||
'm' => 0,
|
||||
];
|
||||
return ResponseHelper::success($data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 用户旅程
|
||||
* @return false|string
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @throws \think\exception\DbException
|
||||
*/
|
||||
public function getUserJourney()
|
||||
{
|
||||
$page = $this->request->param('page', 1);
|
||||
$pageSize = $this->request->param('pageSize', 10);
|
||||
$userId = $this->request->param('userId', '');
|
||||
if (empty($userId)) {
|
||||
return json_encode(['code' => 500, 'msg' => '用户id不能为空']);
|
||||
}
|
||||
|
||||
$query = Db::name('user_portrait')
|
||||
->field('id,type,trafficPoolId,remark,count,createTime,updateTime')
|
||||
->where(['trafficPoolId' => $userId]);
|
||||
|
||||
$total = $query->count();
|
||||
|
||||
$list = $query->order('createTime desc')
|
||||
->page($page, $pageSize)
|
||||
->select();
|
||||
|
||||
|
||||
foreach ($list as $k => $v) {
|
||||
$list[$k]['createTime'] = date('Y-m-d H:i:s', $v['createTime']);
|
||||
$list[$k]['updateTime'] = date('Y-m-d H:i:s', $v['updateTime']);
|
||||
}
|
||||
return ResponseHelper::success(['list' => $list, 'total' => $total]);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function getUserTags()
|
||||
{
|
||||
$userId = $this->request->param('userId', '');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($userId)) {
|
||||
return json_encode(['code' => 500, 'msg' => '用户id不能为空']);
|
||||
}
|
||||
$data = Db::name('traffic_pool')->alias('tp')
|
||||
->join('wechat_friendship f', 'tp.wechatId=f.wechatId AND f.companyId=' . $companyId, 'left')
|
||||
->join(['s2_wechat_friend' => 'wf'], 'f.wechatId=wf.wechatId', 'left')
|
||||
->where(['tp.id' => $userId])
|
||||
->order('tp.createTime desc')
|
||||
->column('wf.id,wf.labels,wf.siteLabels');
|
||||
if (empty($data)) {
|
||||
return ResponseHelper::success(['wechat' => [], 'siteLabels' => []]);
|
||||
}
|
||||
|
||||
|
||||
$tags = [];
|
||||
$siteLabels = [];
|
||||
foreach ($data as $k => $v) {
|
||||
$tag = json_decode($v['labels'], true);
|
||||
$tag2 = json_decode($v['siteLabels'], true);
|
||||
if (!empty($tag)) {
|
||||
$tags = array_merge($tags, $tag);
|
||||
}
|
||||
if (!empty($tag2)) {
|
||||
$siteLabels = array_merge($siteLabels, $tag2);
|
||||
}
|
||||
}
|
||||
$tags = array_unique($tags);
|
||||
$tags = array_values($tags);
|
||||
$siteLabels = array_unique($siteLabels);
|
||||
$siteLabels = array_values($siteLabels);
|
||||
return ResponseHelper::success(['wechat' => $tags, 'siteLabels' => $siteLabels]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public function addPackage()
|
||||
{
|
||||
try {
|
||||
$type = $this->request->param('type', '');
|
||||
$addPackageId = $this->request->param('addPackageId', '');
|
||||
$packageName = $this->request->param('packageName', '');
|
||||
$userIds = $this->request->param('userIds', []);
|
||||
$tableFile = $this->request->param('tableFile', '');
|
||||
|
||||
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$userId = $this->getUserInfo('id');
|
||||
if (empty($addPackageId) && empty($packageName)) {
|
||||
return ResponseHelper::error('存储的流量池不能为空');
|
||||
}
|
||||
|
||||
if (empty($type)) {
|
||||
return ResponseHelper::error('请选择类型');
|
||||
}
|
||||
|
||||
if (!empty($addPackageId)) {
|
||||
$package = Db::name('traffic_source_package')
|
||||
->where(['id' => $addPackageId, 'isDel' => 0])
|
||||
->whereIn('companyId', [$companyId, 0])
|
||||
->field('id,name')
|
||||
->find();
|
||||
if (empty($package)) {
|
||||
return ResponseHelper::error('该流量池不存在');
|
||||
}
|
||||
$packageId = $package['id'];
|
||||
} else {
|
||||
$package = Db::name('traffic_source_package')
|
||||
->where(['isDel' => 0, 'name' => $packageName])
|
||||
->whereIn('companyId', [$companyId, 0])
|
||||
->field('id,name')
|
||||
->find();
|
||||
if (!empty($package)) {
|
||||
return ResponseHelper::error('该流量池名称已存在');
|
||||
}
|
||||
$packageId = Db::name('traffic_source_package')->insertGetId([
|
||||
'userId' => $userId,
|
||||
'companyId' => $companyId,
|
||||
'name' => $packageName,
|
||||
'matchingRules' => json_encode($this->makeWhere()),
|
||||
'createTime' => time(),
|
||||
'isDel' => 0,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
if ($type == 1) {
|
||||
$result = $this->getPoolListByCompanyId($this->makeWhere(), false);
|
||||
$result = json_decode($result, true);
|
||||
$result = array_column($result['list'], 'identifier');
|
||||
} elseif ($type == 2) {
|
||||
if (empty($packageId)) {
|
||||
return ResponseHelper::error('选择的用户');
|
||||
}
|
||||
//================== 表格数据处理 ==================
|
||||
if (!is_array($userIds)) {
|
||||
return ResponseHelper::error('选择的用户类型错误');
|
||||
}
|
||||
$result = Db::name('traffic_pool')->alias('tp')
|
||||
->join('traffic_source tc', 'tp.identifier=tc.identifier')
|
||||
->whereIn('tp.id', $userIds)
|
||||
->where(['companyId' => $companyId])
|
||||
->group('tp.identifier')
|
||||
->column('tc.identifier');
|
||||
} else {
|
||||
/*if (empty($tableFile)){
|
||||
return ResponseHelper::error('请上传用户文件');
|
||||
}
|
||||
|
||||
// 先下载到本地临时文件,再分析,最后删除
|
||||
$originPath = $tableFile;
|
||||
$tmpFile = tempnam(sys_get_temp_dir(), 'user_');
|
||||
// 判断是否为远程文件
|
||||
if (preg_match('/^https?:\/\//i', $originPath)) {
|
||||
// 远程URL,下载到本地
|
||||
$fileContent = file_get_contents($originPath);
|
||||
if ($fileContent === false) {
|
||||
exit('远程文件下载失败: ' . $originPath);
|
||||
}
|
||||
file_put_contents($tmpFile, $fileContent);
|
||||
} else {
|
||||
// 本地文件,直接copy
|
||||
if (!file_exists($originPath)) {
|
||||
exit('文件不存在: ' . $originPath);
|
||||
}
|
||||
copy($originPath, $tmpFile);
|
||||
}
|
||||
// 解析临时文件
|
||||
$ext = strtolower(pathinfo($originPath, PATHINFO_EXTENSION));
|
||||
$rows = [];
|
||||
if (in_array($ext, ['xls', 'xlsx'])) {
|
||||
// 直接用composer自动加载的PHPExcel
|
||||
$excel = \PHPExcel_IOFactory::load($tmpFile);
|
||||
$sheet = $excel->getActiveSheet();
|
||||
$data = $sheet->toArray();
|
||||
if (count($data) > 1) {
|
||||
array_shift($data); // 去掉表头
|
||||
}
|
||||
|
||||
foreach ($data as $cols) {
|
||||
$rows[] = [
|
||||
'name' => isset($cols[0]) ? trim($cols[0]) : '',
|
||||
'phone' => isset($cols[1]) ? trim($cols[1]) : '',
|
||||
'source' => isset($cols[2]) ? trim($cols[2]) : '',
|
||||
];
|
||||
}
|
||||
} elseif ($ext === 'csv') {
|
||||
$content = file_get_contents($tmpFile);
|
||||
$lines = preg_split('/\r\n|\r|\n/', $content);
|
||||
if (count($lines) > 1) {
|
||||
array_shift($lines); // 去掉表头
|
||||
foreach ($lines as $line) {
|
||||
if (trim($line) === '') continue;
|
||||
$cols = str_getcsv($line);
|
||||
if (count($cols) >= 6) {
|
||||
$rows[] = [
|
||||
'name' => isset($cols[0]) ? trim($cols[0]) : '',
|
||||
'phone' => isset($cols[1]) ? trim($cols[1]) : '',
|
||||
'source' => isset($cols[2]) ? trim($cols[2]) : '',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unlink($tmpFile);
|
||||
exit('暂不支持的文件类型: ' . $ext);
|
||||
}
|
||||
// 删除临时文件
|
||||
unlink($tmpFile);*/
|
||||
//================== 表格数据处理 ==================
|
||||
}
|
||||
|
||||
$rows = [
|
||||
['name' => '张三', 'phone' => '18883458888', 'source' => '234'],
|
||||
['name' => '李四', 'phone' => '18878988889', 'source' => '456'],
|
||||
];
|
||||
|
||||
|
||||
if (in_array($type, [1, 2])) {
|
||||
// 1000条为一组进行批量处理
|
||||
$batchSize = 1000;
|
||||
$totalRows = count($result);
|
||||
|
||||
for ($i = 0; $i < $totalRows; $i += $batchSize) {
|
||||
$batchRows = array_slice($result, $i, $batchSize);
|
||||
if (!empty($batchRows)) {
|
||||
// 2. 批量查询已存在的手机
|
||||
$existing = Db::name('traffic_source_package_item')
|
||||
->where(['companyId' => $companyId, 'packageId' => $packageId])
|
||||
->whereIn('identifier', $batchRows)
|
||||
->field('identifier')
|
||||
->select();
|
||||
$existingPhones = array_column($existing, 'identifier');
|
||||
// 3. 过滤出新数据,批量插入
|
||||
$newData = [];
|
||||
foreach ($batchRows as $row) {
|
||||
if (!in_array($row, $existingPhones)) {
|
||||
$newData[] = [
|
||||
'packageId' => $packageId,
|
||||
'companyId' => $companyId,
|
||||
'identifier' => $row,
|
||||
'createTime' => time(),
|
||||
];
|
||||
}
|
||||
}
|
||||
// 4. 批量插入新数据
|
||||
if (!empty($newData)) {
|
||||
Db::name('traffic_source_package_item')->insertAll($newData);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 1000条为一组进行批量处理
|
||||
$batchSize = 1000;
|
||||
$totalRows = count($rows);
|
||||
|
||||
try {
|
||||
for ($i = 0; $i < $totalRows; $i += $batchSize) {
|
||||
Db::startTrans();
|
||||
$batchRows = array_slice($rows, $i, $batchSize);
|
||||
if (!empty($batchRows)) {
|
||||
$identifiers = array_column($batchRows, 'phone');
|
||||
//流量池处理
|
||||
$existing = Db::name('traffic_pool')
|
||||
->whereIn('identifier', $identifiers)
|
||||
->column('identifier');
|
||||
|
||||
$newData = [];
|
||||
foreach ($batchRows as $row) {
|
||||
if (!in_array($row['phone'], $existing)) {
|
||||
$newData[] = [
|
||||
'identifier' => $row['phone'],
|
||||
'mobile' => $row['phone'],
|
||||
'createTime' => time(),
|
||||
];
|
||||
}
|
||||
}
|
||||
if (!empty($newData)) {
|
||||
Db::name('traffic_pool')->insertAll($newData);
|
||||
}
|
||||
|
||||
//流量池来源处理
|
||||
$newData2 = [];
|
||||
$existing2 = Db::name('traffic_source')
|
||||
->where(['companyId' => $companyId])
|
||||
->whereIn('identifier', $identifiers)
|
||||
->column('identifier');
|
||||
foreach ($batchRows as $row) {
|
||||
if (!in_array($row['phone'], $existing2)) {
|
||||
$newData2[] = [
|
||||
'type' => 0,
|
||||
'name' => $row['name'],
|
||||
'identifier' => $row['phone'],
|
||||
'fromd' => $row['source'],
|
||||
'companyId' => $companyId,
|
||||
'createTime' => time(),
|
||||
'updateTime' => time(),
|
||||
];
|
||||
}
|
||||
}
|
||||
if (!empty($newData2)) {
|
||||
Db::name('traffic_source')->insertAll($newData2);
|
||||
}
|
||||
|
||||
//流量池包数据处理
|
||||
$newData3 = [];
|
||||
$existing3 = Db::name('traffic_source_package_item')
|
||||
->where(['companyId' => $companyId, 'packageId' => $packageId])
|
||||
->whereIn('identifier', $identifiers)
|
||||
->field('identifier')
|
||||
->select();
|
||||
foreach ($batchRows as $row) {
|
||||
if (!in_array($row['phone'], $existing3)) {
|
||||
$newData3[] = [
|
||||
'packageId' => $packageId,
|
||||
'companyId' => $companyId,
|
||||
'identifier' => $row['phone'],
|
||||
'createTime' => time(),
|
||||
];
|
||||
}
|
||||
}
|
||||
if (!empty($newData3)) {
|
||||
Db::name('traffic_source_package_item')->insertAll($newData3);
|
||||
}
|
||||
|
||||
Db::commit();
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
DB::rollback();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return ResponseHelper::success('添加成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* public function editUserTags()
|
||||
{
|
||||
$userId = $this->request->param('userId', '');
|
||||
if (empty($userId)) {
|
||||
return json_encode(['code' => 500, 'msg' => '用户id不能为空']);
|
||||
}
|
||||
$tags = $this->request->param('tags', []);
|
||||
$tags = $this->request->param('tags', []);
|
||||
$isWechat = $this->request->param('isWechat', false);
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
$friend = Db::name('traffic_pool')->alias('tp')
|
||||
->join('wechat_friendship f', 'tp.wechatId=f.wechatId AND f.companyId='.$companyId, 'left')
|
||||
->join(['s2_wechat_friend' => 'wf'], 'f.wechatId=wf.wechatId', 'left')
|
||||
->where(['tp.id' => $userId])
|
||||
->order('tp.createTime desc')
|
||||
->column('wf.id,wf.accountId,wf.labels,wf.siteLabels');
|
||||
if (empty($data)) {
|
||||
return ResponseHelper::error('该用户不存在');
|
||||
}
|
||||
|
||||
|
||||
}*/
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\traffic;
|
||||
|
||||
use app\common\model\TrafficPool as TrafficPoolModel;
|
||||
use app\common\model\TrafficSource as TrafficSourceModel;
|
||||
use app\common\model\WechatFriendShip as WechatFriendShipModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
|
||||
/**
|
||||
* 流量池控制器
|
||||
*/
|
||||
class GetPotentialTypeSectionV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 返回流量处理状态选项
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
protected function getTypeSectionCols(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'id' => TrafficSourceModel::STATUS_PENDING,
|
||||
'name' => '待处理'
|
||||
],
|
||||
[
|
||||
'id' => TrafficSourceModel::STATUS_WORKING,
|
||||
'name' => '处理中'
|
||||
],
|
||||
[
|
||||
'id' => TrafficSourceModel::STATUS_REFUSED,
|
||||
'name' => '已拒绝'
|
||||
],
|
||||
[
|
||||
'id' => TrafficSourceModel::STATUS_EXPIRED,
|
||||
'name' => '已过期'
|
||||
],
|
||||
[
|
||||
'id' => TrafficSourceModel::STATUS_CANCELED,
|
||||
'name' => '已取消'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取流量池状态筛选列表
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
return ResponseHelper::success(
|
||||
$this->getTypeSectionCols()
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\traffic;
|
||||
|
||||
use app\common\model\TrafficSource as TrafficSourceModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
|
||||
/**
|
||||
* 流量池控制器
|
||||
*/
|
||||
class GetTrafficSourceSectionV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 动态获取流量来源的selection 选择器列表数据
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getSourceSectionCols(): array
|
||||
{
|
||||
return (array)TrafficSourceModel::where(
|
||||
[
|
||||
'companyId' => $this->getUserInfo('companyId')
|
||||
]
|
||||
)
|
||||
->field('fromd name,id')->group('fromd')->select()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取流量来源筛选列表
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
return ResponseHelper::success(
|
||||
$this->getSourceSectionCols()
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
189
application/cunkebao/controller/wechat/GetWechatController.php
Normal file
189
application/cunkebao/controller/wechat/GetWechatController.php
Normal file
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\wechat;
|
||||
|
||||
use app\common\model\WechatCustomer as WechatCustomerModel;
|
||||
use app\common\model\WechatFriendShip as WechatFriendShipModel;
|
||||
use app\common\model\WechatRestricts as WechatRestrictsModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use Eison\Utils\Helper\ArrHelper;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
class GetWechatController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取微信客服信息
|
||||
*
|
||||
* @param string $wechatId
|
||||
* @return WechatCustomerModel|null
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function getWechatCustomerModel(string $wechatId): ?WechatCustomerModel
|
||||
{
|
||||
if (!isset($this->WechatCustomerModel)) {
|
||||
$this->WechatCustomerModel = WechatCustomerModel::where(
|
||||
[
|
||||
'wechatId' => $wechatId,
|
||||
'companyId' => $this->getUserInfo('companyId')
|
||||
]
|
||||
)
|
||||
->find();
|
||||
}
|
||||
|
||||
return $this->WechatCustomerModel;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取昨日聊天次数
|
||||
*
|
||||
* @param WechatCustomerModel $customer
|
||||
* @return int
|
||||
*/
|
||||
protected function getChatTimesPerDay(?WechatCustomerModel $customer): int
|
||||
{
|
||||
return $customer->activity->yesterdayMsgCount ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 总聊天数量
|
||||
*
|
||||
* @param WechatCustomerModel $customer
|
||||
* @return int
|
||||
*/
|
||||
protected function getChatTimesTotal(?WechatCustomerModel $customer): int
|
||||
{
|
||||
return $customer->activity->totalMsgCount ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算活跃程度(根据消息数)
|
||||
*
|
||||
* @param string $wechatId
|
||||
* @return string
|
||||
*/
|
||||
protected function getActivityLevel(string $wechatId): array
|
||||
{
|
||||
$customer = $this->getWechatCustomerModel($wechatId);
|
||||
|
||||
return [
|
||||
'allTimes' => $this->getChatTimesTotal($customer),
|
||||
'dayTimes' => $this->getChatTimesPerDay($customer),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取限制记录
|
||||
*
|
||||
* @param string $wechatId
|
||||
* @return array
|
||||
*/
|
||||
protected function getRestrict(string $wechatId): array
|
||||
{
|
||||
return WechatRestrictsModel::alias('r')
|
||||
->field(
|
||||
[
|
||||
'r.id', 'r.restrictTime date', 'r.level', 'r.reason'
|
||||
]
|
||||
)
|
||||
->where('r.wechatId', $wechatId)->select()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取账号权重
|
||||
*
|
||||
* @param string $wechatId
|
||||
* @return array
|
||||
*/
|
||||
protected function getAccountWeight(string $wechatId): array
|
||||
{
|
||||
$customer = $this->getWechatCustomerModel($wechatId);
|
||||
$seeders = $customer ? (array)$customer->weight : array();
|
||||
|
||||
// 严谨返回
|
||||
return ArrHelper::getValue('ageWeight,activityWeigth,restrictWeight,realNameWeight,scope', $seeders, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当日最高添加好友记录
|
||||
*
|
||||
* @param string $wechatId
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getAccountWeightAddLimit(string $wechatId): int
|
||||
{
|
||||
return $this->getWechatCustomerModel($wechatId)->weight->addLimit ?? 20;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算今日新增好友数量
|
||||
*
|
||||
* @param string $ownerWechatId
|
||||
* @return int
|
||||
*/
|
||||
protected function getTodayNewFriendCount(string $ownerWechatId): int
|
||||
{
|
||||
|
||||
return Db::table('s2_friend_task')
|
||||
->where('wechatId',$ownerWechatId)
|
||||
->whereBetween('createTime', [strtotime(date('Y-m-d 00:00:00')), strtotime(date('Y-m-d 23:59:59'))])
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取账号加友统计数据.
|
||||
*
|
||||
* @param string $wechatId
|
||||
* @return array
|
||||
*/
|
||||
protected function getStatistics(string $wechatId): array
|
||||
{
|
||||
return [
|
||||
'todayAdded' => $this->getTodayNewFriendCount($wechatId),
|
||||
'addLimit' => $this->getAccountWeightAddLimit($wechatId)
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public function getWechatInfo()
|
||||
{
|
||||
$wechatId = $this->request->param('wechatId','');
|
||||
if (empty($wechatId)) {
|
||||
return ResponseHelper::error('微信id不能为空');
|
||||
}
|
||||
$userInfo = Db::name('wechat_customer')->alias('wc')
|
||||
->join('wechat_account wa','wc.wechatId = wa.wechatId')
|
||||
->where(['wc.wechatId' => $wechatId])
|
||||
->field('wc.*,wa.nickname,wa.alias,wa.avatar,wa.gender')
|
||||
->find();
|
||||
|
||||
if (empty($userInfo)){
|
||||
return ResponseHelper::error('该微信不存在');
|
||||
}
|
||||
$accountAge= !empty($userInfo['createTime']) ? date('Y-m-d H:i:s',$userInfo['createTime']) : date('Y-m-d H:i:s');
|
||||
unset($userInfo['basic'],$userInfo['companyId'],$userInfo['createTime'],$userInfo['updateTime'],$userInfo['id']);
|
||||
|
||||
$userInfo['weight'] = json_decode($userInfo['weight'],true);
|
||||
$userInfo['activity'] = json_decode($userInfo['activity'],true);
|
||||
$userInfo['friendShip'] = json_decode($userInfo['friendShip'],true);
|
||||
|
||||
|
||||
$newData = [
|
||||
'userInfo' => $userInfo,
|
||||
'accountAge' => $accountAge,
|
||||
'activityLevel' => $this->getActivityLevel($wechatId),
|
||||
'accountWeight' => $this->getAccountWeight($wechatId),
|
||||
'statistics' => $this->getStatistics($wechatId),
|
||||
// 'restrictions' => $this->getRestrict($wechatId),
|
||||
];
|
||||
|
||||
|
||||
|
||||
return ResponseHelper::success($newData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\wechat;
|
||||
|
||||
use app\common\controller\ExportController;
|
||||
use app\common\model\Device as DeviceModel;
|
||||
use app\common\model\DeviceUser as DeviceUserModel;
|
||||
use app\common\model\DeviceWechatLogin as DeviceWechatLoginModel;
|
||||
use app\common\model\User as UserModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 查看微信朋友圈列表(仅限当前操盘手可访问的微信)
|
||||
*/
|
||||
class GetWechatMomentsV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 主操盘手获取项目下所有设备ID
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getCompanyDevicesId(): array
|
||||
{
|
||||
return DeviceModel::where('companyId', $this->getUserInfo('companyId'))
|
||||
->column('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 非主操盘手仅可查看分配到的设备
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getUserDevicesId(): array
|
||||
{
|
||||
return DeviceUserModel::where([
|
||||
'userId' => $this->getUserInfo('id'),
|
||||
'companyId' => $this->getUserInfo('companyId'),
|
||||
])->column('deviceId');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户可访问的设备ID
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getDevicesId(): array
|
||||
{
|
||||
return ($this->getUserInfo('isAdmin') == UserModel::ADMIN_STP)
|
||||
? $this->getCompanyDevicesId()
|
||||
: $this->getUserDevicesId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户可访问的微信ID集合
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getAccessibleWechatIds(): array
|
||||
{
|
||||
$deviceIds = $this->getDevicesId();
|
||||
if (empty($deviceIds)) {
|
||||
throw new \Exception('暂无可用设备', 200);
|
||||
}
|
||||
|
||||
return DeviceWechatLoginModel::distinct(true)
|
||||
->where('companyId', $this->getUserInfo('companyId'))
|
||||
->whereIn('deviceId', $deviceIds)
|
||||
->column('wechatId');
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看朋友圈列表
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$wechatId = $this->request->param('wechatId/s', '');
|
||||
if (empty($wechatId)) {
|
||||
return ResponseHelper::error('wechatId不能为空');
|
||||
}
|
||||
|
||||
// 权限校验:只能查看当前账号可访问的微信
|
||||
$accessibleWechatIds = $this->getAccessibleWechatIds();
|
||||
if (!in_array($wechatId, $accessibleWechatIds, true)) {
|
||||
return ResponseHelper::error('无权查看该微信的朋友圈', 403);
|
||||
}
|
||||
|
||||
// 获取对应的微信账号ID
|
||||
$accountId = Db::table('s2_wechat_account')
|
||||
->where('wechatId', $wechatId)
|
||||
->value('id');
|
||||
|
||||
if (empty($accountId)) {
|
||||
return ResponseHelper::error('微信账号不存在或尚未同步', 404);
|
||||
}
|
||||
|
||||
$query = Db::table('s2_wechat_moments')
|
||||
->where('wechatAccountId', $accountId)
|
||||
->where('userName', $wechatId);
|
||||
|
||||
// 关键词搜索
|
||||
if ($keyword = trim((string)$this->request->param('keyword', ''))) {
|
||||
$query->whereLike('content', '%' . $keyword . '%');
|
||||
}
|
||||
|
||||
// 类型筛选
|
||||
$type = $this->request->param('type', '');
|
||||
if ($type !== '' && $type !== null) {
|
||||
$query->where('type', (int)$type);
|
||||
}
|
||||
|
||||
// 时间筛选
|
||||
$startTime = $this->request->param('startTime', '');
|
||||
$endTime = $this->request->param('endTime', '');
|
||||
if ($startTime || $endTime) {
|
||||
$start = $startTime ? strtotime($startTime) : 0;
|
||||
$end = $endTime ? strtotime($endTime) : time();
|
||||
if ($start && $end && $end < $start) {
|
||||
return ResponseHelper::error('结束时间不能早于开始时间');
|
||||
}
|
||||
$query->whereBetween('createTime', [$start ?: 0, $end ?: time()]);
|
||||
}
|
||||
|
||||
$page = (int)$this->request->param('page', 1);
|
||||
$limit = (int)$this->request->param('limit', 10);
|
||||
|
||||
$paginator = $query->order('createTime', 'desc')
|
||||
->paginate($limit, false, ['page' => $page]);
|
||||
|
||||
$list = array_map(function ($item) {
|
||||
return $this->formatMomentRow($item);
|
||||
}, $paginator->items());
|
||||
|
||||
return ResponseHelper::success([
|
||||
'list' => $list,
|
||||
'total' => $paginator->total(),
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode() ?: 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出朋友圈数据到Excel
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
try {
|
||||
$wechatId = $this->request->param('wechatId/s', '');
|
||||
if (empty($wechatId)) {
|
||||
return ResponseHelper::error('wechatId不能为空');
|
||||
}
|
||||
|
||||
// 权限校验:只能查看当前账号可访问的微信
|
||||
$accessibleWechatIds = $this->getAccessibleWechatIds();
|
||||
if (!in_array($wechatId, $accessibleWechatIds, true)) {
|
||||
return ResponseHelper::error('无权查看该微信的朋友圈', 403);
|
||||
}
|
||||
|
||||
// 获取对应的微信账号ID
|
||||
$accountId = Db::table('s2_wechat_account')
|
||||
->where('wechatId', $wechatId)
|
||||
->value('id');
|
||||
|
||||
if (empty($accountId)) {
|
||||
return ResponseHelper::error('微信账号不存在或尚未同步', 404);
|
||||
}
|
||||
|
||||
$query = Db::table('s2_wechat_moments')
|
||||
->where('wechatAccountId', $accountId);
|
||||
|
||||
// 关键词搜索
|
||||
if ($keyword = trim((string)$this->request->param('keyword', ''))) {
|
||||
$query->whereLike('content', '%' . $keyword . '%');
|
||||
}
|
||||
|
||||
// 类型筛选
|
||||
$type = $this->request->param('type', '');
|
||||
if ($type !== '' && $type !== null) {
|
||||
$query->where('type', (int)$type);
|
||||
}
|
||||
|
||||
// 时间筛选
|
||||
$startTime = $this->request->param('startTime', '');
|
||||
$endTime = $this->request->param('endTime', '');
|
||||
if ($startTime || $endTime) {
|
||||
$start = $startTime ? strtotime($startTime) : 0;
|
||||
$end = $endTime ? strtotime($endTime) : time();
|
||||
if ($start && $end && $end < $start) {
|
||||
return ResponseHelper::error('结束时间不能早于开始时间');
|
||||
}
|
||||
$query->whereBetween('createTime', [$start ?: 0, $end ?: time()]);
|
||||
}
|
||||
|
||||
// 获取所有数据(不分页)
|
||||
$moments = $query->order('createTime', 'desc')->select();
|
||||
|
||||
if (empty($moments)) {
|
||||
return ResponseHelper::error('暂无数据可导出');
|
||||
}
|
||||
|
||||
// 定义表头
|
||||
$headers = [
|
||||
'date' => '日期',
|
||||
'postTime' => '投放时间',
|
||||
'functionCategory' => '作用分类',
|
||||
'content' => '朋友圈文案',
|
||||
'selfReply' => '自回评内容',
|
||||
'displayForm' => '朋友圈展示形式',
|
||||
'image1' => '配图1',
|
||||
'image2' => '配图2',
|
||||
'image3' => '配图3',
|
||||
'image4' => '配图4',
|
||||
'image5' => '配图5',
|
||||
'image6' => '配图6',
|
||||
'image7' => '配图7',
|
||||
'image8' => '配图8',
|
||||
'image9' => '配图9',
|
||||
];
|
||||
|
||||
// 格式化数据
|
||||
$rows = [];
|
||||
foreach ($moments as $moment) {
|
||||
$resUrls = $this->decodeJson($moment['resUrls'] ?? null);
|
||||
$imageUrls = is_array($resUrls) ? $resUrls : [];
|
||||
|
||||
// 格式化日期和时间
|
||||
$createTime = !empty($moment['createTime'])
|
||||
? (is_numeric($moment['createTime']) ? $moment['createTime'] : strtotime($moment['createTime']))
|
||||
: 0;
|
||||
$date = $createTime ? date('Y年m月d日', $createTime) : '';
|
||||
$postTime = $createTime ? date('H:i', $createTime) : '';
|
||||
|
||||
// 判断展示形式
|
||||
$displayForm = '';
|
||||
if (!empty($moment['content']) && !empty($imageUrls)) {
|
||||
$displayForm = '文字+图片';
|
||||
} elseif (!empty($moment['content'])) {
|
||||
$displayForm = '文字';
|
||||
} elseif (!empty($imageUrls)) {
|
||||
$displayForm = '图片';
|
||||
}
|
||||
|
||||
$row = [
|
||||
'date' => $date,
|
||||
'postTime' => $postTime,
|
||||
'functionCategory' => '', // 暂时放空
|
||||
'content' => $moment['content'] ?? '',
|
||||
'selfReply' => '', // 暂时放空
|
||||
'displayForm' => $displayForm,
|
||||
];
|
||||
|
||||
// 分配图片到配图1-9列
|
||||
for ($i = 1; $i <= 9; $i++) {
|
||||
$imageKey = 'image' . $i;
|
||||
$row[$imageKey] = isset($imageUrls[$i - 1]) ? $imageUrls[$i - 1] : '';
|
||||
}
|
||||
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
// 定义图片列(配图1-9)
|
||||
$imageColumns = ['image1', 'image2', 'image3', 'image4', 'image5', 'image6', 'image7', 'image8', 'image9'];
|
||||
|
||||
// 生成文件名
|
||||
$fileName = '朋友圈投放_' . date('Ymd_His');
|
||||
|
||||
// 调用导出方法,优化图片显示效果
|
||||
ExportController::exportExcelWithImages(
|
||||
$fileName,
|
||||
$headers,
|
||||
$rows,
|
||||
$imageColumns,
|
||||
'朋友圈投放',
|
||||
[
|
||||
'imageWidth' => 120, // 图片宽度(像素)
|
||||
'imageHeight' => 120, // 图片高度(像素)
|
||||
'imageColumnWidth' => 18, // 图片列宽(Excel单位)
|
||||
'rowHeight' => 130, // 行高(像素)
|
||||
'columnWidths' => [ // 特定列的固定宽度
|
||||
'date' => 15, // 日期列宽
|
||||
'postTime' => 12, // 投放时间列宽
|
||||
'functionCategory' => 15, // 作用分类列宽
|
||||
'content' => 40, // 朋友圈文案列宽(自动调整可能不够)
|
||||
'selfReply' => 30, // 自回评内容列宽
|
||||
'displayForm' => 18, // 朋友圈展示形式列宽
|
||||
],
|
||||
'titleRow' => [ // 标题行内容(第一行)
|
||||
'朋友圈投放',
|
||||
'我能提供什么价值? (40%) 有谁正在和我合作 (20%) 如何和我合作? (20%) 你找我合作需要付多少钱? (20%)'
|
||||
]
|
||||
]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('导出失败:' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化朋友圈数据
|
||||
*
|
||||
* @param array $row
|
||||
* @return array
|
||||
*/
|
||||
protected function formatMomentRow(array $row): array
|
||||
{
|
||||
$formatTime = function ($timestamp) {
|
||||
if (empty($timestamp)) {
|
||||
return '';
|
||||
}
|
||||
return is_numeric($timestamp)
|
||||
? date('Y-m-d H:i:s', $timestamp)
|
||||
: date('Y-m-d H:i:s', strtotime($timestamp));
|
||||
};
|
||||
|
||||
return [
|
||||
'id' => (int)$row['id'],
|
||||
'snsId' => $row['snsId'] ?? '',
|
||||
'type' => (int)($row['type'] ?? 0),
|
||||
'content' => $row['content'] ?? '',
|
||||
'commentList' => $this->decodeJson($row['commentList'] ?? null),
|
||||
'likeList' => $this->decodeJson($row['likeList'] ?? null),
|
||||
'resUrls' => $this->decodeJson($row['resUrls'] ?? null),
|
||||
'createTime' => $formatTime($row['createTime'] ?? null),
|
||||
'momentEntity' => [
|
||||
'lat' => $row['lat'] ?? 0,
|
||||
'lng' => $row['lng'] ?? 0,
|
||||
'location' => $row['location'] ?? '',
|
||||
'picSize' => $row['picSize'] ?? 0,
|
||||
'userName' => $row['userName'] ?? '',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON字段解析
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return array
|
||||
*/
|
||||
protected function decodeJson($value): array
|
||||
{
|
||||
if (empty($value)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$decoded = json_decode($value, true);
|
||||
return $decoded ?: [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\wechat;
|
||||
|
||||
use app\common\model\WechatAccount as WechatAccountModel;
|
||||
use app\common\model\WechatFriendShip as WechatFriendShipModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
|
||||
/**
|
||||
* 设备微信控制器
|
||||
*/
|
||||
class GetWechatOnDeviceFriendsV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 构建返回数据
|
||||
*
|
||||
* @param \think\Paginator $result
|
||||
* @return array
|
||||
*/
|
||||
protected function makeResultedSet(\think\Paginator $result): array
|
||||
{
|
||||
$resultSets = [];
|
||||
|
||||
foreach ($result->items() as $item) {
|
||||
$item->tags = json_decode($item->tags);
|
||||
array_push($resultSets, $item->toArray());
|
||||
}
|
||||
|
||||
return $resultSets;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据微信账号ID获取好友列表
|
||||
*
|
||||
* @param array $where
|
||||
* @return \think\Paginator 分页对象
|
||||
*/
|
||||
protected function getFriendsByWechatIdAndQueryParams(array $where): \think\Paginator
|
||||
{
|
||||
$query = WechatFriendShipModel::alias('f')
|
||||
->field(
|
||||
[
|
||||
'w.id', 'w.nickname', 'w.avatar', 'w.wechatId',
|
||||
'CASE WHEN w.alias IS NULL OR w.alias = "" THEN w.wechatId ELSE w.alias END AS wechatAccount',
|
||||
'f.memo', 'f.tags',
|
||||
'ff.accountUserName', 'ff.accountRealName','ff.id AS friendId'
|
||||
]
|
||||
)
|
||||
->join('wechat_account w', 'w.wechatId = f.wechatId')
|
||||
->join(['s2_wechat_friend' => 'ff'], 'ff.id = f.id');
|
||||
|
||||
foreach ($where as $key => $value) {
|
||||
if (is_numeric($key) && is_array($value) && isset($value[0]) && $value[0] === 'exp') {
|
||||
$query->whereExp('', $value[1]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$query->where($key, $value);
|
||||
}
|
||||
|
||||
return $query->paginate($this->request->param('limit/d', 10), false, ['page' => $this->request->param('page/d', 1)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询条件
|
||||
*
|
||||
* @param array $params
|
||||
* @return array
|
||||
*/
|
||||
protected function makeWhere(array $params = []): array
|
||||
{
|
||||
// 关键词搜索(同时搜索好友备注和标签)
|
||||
if (!empty($keyword = $this->request->param('keyword'))) {
|
||||
$where[] = ['exp', "f.memo LIKE '%{$keyword}%' OR f.tags LIKE '%{$keyword}%'"];
|
||||
}
|
||||
|
||||
$where['f.ownerWechatId'] = $this->request->param('id/s') ?: 'x_x';
|
||||
|
||||
return array_merge($where, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取微信好友列表
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$result = $this->getFriendsByWechatIdAndQueryParams(
|
||||
$this->makeWhere()
|
||||
);
|
||||
|
||||
return ResponseHelper::success(
|
||||
[
|
||||
'list' => $this->makeResultedSet($result),
|
||||
'total' => $result->total(),
|
||||
]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\wechat;
|
||||
|
||||
use AccountWeight\WechatAccountWeightAssessment as WeightAssessment;
|
||||
use AccountWeight\WechatFriendAddLimitAssessment as LimitAssessment;
|
||||
use app\common\model\WechatCustomer as WechatCustomerModel;
|
||||
use app\common\model\WechatFriendShip as WechatFriendShipModel;
|
||||
use app\common\model\WechatRestricts as WechatRestrictsModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use Eison\Utils\Helper\ArrHelper;
|
||||
use library\ResponseHelper;
|
||||
|
||||
/**
|
||||
* 设备微信控制器
|
||||
*/
|
||||
class GetWechatOnDeviceSummarizeV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取微信客服信息
|
||||
*
|
||||
* @param string $wechatId
|
||||
* @return WechatCustomerModel|null
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function getWechatCustomerModel(string $wechatId): ?WechatCustomerModel
|
||||
{
|
||||
if (!isset($this->WechatCustomerModel)) {
|
||||
$this->WechatCustomerModel = WechatCustomerModel::where(
|
||||
[
|
||||
'wechatId' => $wechatId,
|
||||
'companyId' => $this->getUserInfo('companyId')
|
||||
]
|
||||
)
|
||||
->find();
|
||||
}
|
||||
|
||||
return $this->WechatCustomerModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算账号年龄(从创建时间到现在)
|
||||
*
|
||||
* @param string $wechatId
|
||||
* @return string
|
||||
*/
|
||||
protected function getRegisterDate(string $wechatId): string
|
||||
{
|
||||
return $this->getWechatCustomerModel($wechatId)->basic->registerDate ?? date('Y-m-d', time());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取昨日聊天次数
|
||||
*
|
||||
* @param WechatCustomerModel $customer
|
||||
* @return int
|
||||
*/
|
||||
protected function getChatTimesPerDay(?WechatCustomerModel $customer): int
|
||||
{
|
||||
return $customer->activity->yesterdayMsgCount ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 总聊天数量
|
||||
*
|
||||
* @param WechatCustomerModel $customer
|
||||
* @return int
|
||||
*/
|
||||
protected function getChatTimesTotal(?WechatCustomerModel $customer): int
|
||||
{
|
||||
return $customer->activity->totalMsgCount ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算活跃程度(根据消息数)
|
||||
*
|
||||
* @param string $wechatId
|
||||
* @return string
|
||||
*/
|
||||
protected function getActivityLevel(string $wechatId): array
|
||||
{
|
||||
$customer = $this->getWechatCustomerModel($wechatId);
|
||||
|
||||
return [
|
||||
'allTimes' => $this->getChatTimesTotal($customer),
|
||||
'dayTimes' => $this->getChatTimesPerDay($customer),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取限制记录
|
||||
*
|
||||
* @param string $wechatId
|
||||
* @return array
|
||||
*/
|
||||
protected function getRestrict(string $wechatId): array
|
||||
{
|
||||
return WechatRestrictsModel::alias('r')
|
||||
->field(
|
||||
[
|
||||
'r.id', 'r.restrictTime date', 'r.level', 'r.reason'
|
||||
]
|
||||
)
|
||||
->where('r.wechatId', $wechatId)->select()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取账号权重
|
||||
*
|
||||
* @param string $wechatId
|
||||
* @return array
|
||||
*/
|
||||
protected function getAccountWeight(string $wechatId): array
|
||||
{
|
||||
$customer = $this->getWechatCustomerModel($wechatId);
|
||||
$seeders = $customer ? (array)$customer->weight : array();
|
||||
|
||||
// 严谨返回
|
||||
return ArrHelper::getValue('ageWeight,activityWeigth,restrictWeight,realNameWeight,scope', $seeders, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当日最高添加好友记录
|
||||
*
|
||||
* @param string $wechatId
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getAccountWeightAddLimit(string $wechatId): int
|
||||
{
|
||||
return $this->getWechatCustomerModel($wechatId)->weight->addLimit ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算今日新增好友数量
|
||||
*
|
||||
* @param string $ownerWechatId
|
||||
* @return int
|
||||
*/
|
||||
protected function getTodayNewFriendCount(string $ownerWechatId): int
|
||||
{
|
||||
return WechatFriendShipModel::where(compact('ownerWechatId'))
|
||||
->whereBetween('createTime',
|
||||
[
|
||||
strtotime(date('Y-m-d 00:00:00')),
|
||||
strtotime(date('Y-m-d 23:59:59'))
|
||||
]
|
||||
)
|
||||
->count('*');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取账号加友统计数据.
|
||||
*
|
||||
* @param string $wechatId
|
||||
* @return array
|
||||
*/
|
||||
protected function getStatistics(string $wechatId): array
|
||||
{
|
||||
return [
|
||||
'todayAdded' => $this->getTodayNewFriendCount($wechatId),
|
||||
'addLimit' => $this->getAccountWeightAddLimit($wechatId)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取微信号详情
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$wechatId = $this->request->param('id/s');
|
||||
|
||||
return ResponseHelper::success(
|
||||
[
|
||||
'accountAge' => $this->getRegisterDate($wechatId),
|
||||
'activityLevel' => $this->getActivityLevel($wechatId),
|
||||
'accountWeight' => $this->getAccountWeight($wechatId),
|
||||
'statistics' => $this->getStatistics($wechatId),
|
||||
'restrictions' => $this->getRestrict($wechatId),
|
||||
]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,411 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\wechat;
|
||||
|
||||
use app\common\service\WechatAccountHealthScoreService;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 微信账号概览控制器
|
||||
* 提供账号概览页面的所有数据接口
|
||||
*/
|
||||
class GetWechatOverviewV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取微信账号概览数据
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$wechatId = $this->request->param('wechatId', '');
|
||||
|
||||
if (empty($wechatId)) {
|
||||
return ResponseHelper::error('微信ID不能为空');
|
||||
}
|
||||
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
// 获取微信账号ID(accountId)
|
||||
$account = Db::table('s2_wechat_account')
|
||||
->where('wechatId', $wechatId)
|
||||
->find();
|
||||
|
||||
if (empty($account)) {
|
||||
return ResponseHelper::error('微信账号不存在');
|
||||
}
|
||||
|
||||
$accountId = $account['id'];
|
||||
|
||||
// 1. 健康分评估
|
||||
$healthScoreData = $this->getHealthScoreAssessment($accountId, $wechatId);
|
||||
|
||||
// 2. 账号价值(模拟数据)
|
||||
$accountValue = $this->getAccountValue($accountId);
|
||||
|
||||
// 3. 今日价值变化(模拟数据)
|
||||
$todayValueChange = $this->getTodayValueChange($accountId);
|
||||
|
||||
// 4. 好友总数
|
||||
$totalFriends = $this->getTotalFriends($wechatId, $companyId);
|
||||
|
||||
// 5. 今日新增好友
|
||||
$todayNewFriends = $this->getTodayNewFriends($wechatId);
|
||||
|
||||
// 6. 高价群聊
|
||||
$highValueChatrooms = $this->getHighValueChatrooms($wechatId, $companyId);
|
||||
|
||||
// 7. 今日新增群聊
|
||||
$todayNewChatrooms = $this->getTodayNewChatrooms($wechatId, $companyId);
|
||||
|
||||
$result = [
|
||||
'healthScoreAssessment' => $healthScoreData,
|
||||
'accountValue' => $accountValue,
|
||||
'todayValueChange' => $todayValueChange,
|
||||
'totalFriends' => $totalFriends,
|
||||
'todayNewFriends' => $todayNewFriends,
|
||||
'highValueChatrooms' => $highValueChatrooms,
|
||||
'todayNewChatrooms' => $todayNewChatrooms,
|
||||
];
|
||||
|
||||
return ResponseHelper::success($result);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode() ?: 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取健康分评估数据
|
||||
*
|
||||
* @param int $accountId 账号ID
|
||||
* @param string $wechatId 微信ID
|
||||
* @return array
|
||||
*/
|
||||
protected function getHealthScoreAssessment($accountId, $wechatId)
|
||||
{
|
||||
// 获取健康分信息
|
||||
$healthScoreService = new WechatAccountHealthScoreService();
|
||||
$healthScoreInfo = $healthScoreService->getHealthScore($accountId);
|
||||
|
||||
$healthScore = $healthScoreInfo['healthScore'] ?? 0;
|
||||
$maxAddFriendPerDay = $healthScoreInfo['maxAddFriendPerDay'] ?? 0;
|
||||
|
||||
// 获取今日已加好友数
|
||||
$todayAdded = $this->getTodayAddedCount($wechatId);
|
||||
|
||||
// 获取最后添加时间
|
||||
$lastAddTime = $this->getLastAddTime($wechatId);
|
||||
|
||||
// 判断状态标签
|
||||
$statusTag = $todayAdded > 0 ? '已添加加人' : '';
|
||||
|
||||
// 获取基础构成
|
||||
$baseComposition = $this->getBaseComposition($healthScoreInfo);
|
||||
|
||||
// 获取动态记录
|
||||
$dynamicRecords = $this->getDynamicRecords($healthScoreInfo);
|
||||
|
||||
return [
|
||||
'score' => $healthScore,
|
||||
'dailyLimit' => $maxAddFriendPerDay,
|
||||
'todayAdded' => $todayAdded,
|
||||
'lastAddTime' => $lastAddTime,
|
||||
'statusTag' => $statusTag,
|
||||
'baseComposition' => $baseComposition,
|
||||
'dynamicRecords' => $dynamicRecords,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取基础构成数据
|
||||
*
|
||||
* @param array $healthScoreInfo 健康分信息
|
||||
* @return array
|
||||
*/
|
||||
protected function getBaseComposition($healthScoreInfo)
|
||||
{
|
||||
$baseScore = $healthScoreInfo['baseScore'] ?? 0;
|
||||
$baseInfoScore = $healthScoreInfo['baseInfoScore'] ?? 0;
|
||||
$friendCountScore = $healthScoreInfo['friendCountScore'] ?? 0;
|
||||
$friendCount = $healthScoreInfo['friendCount'] ?? 0;
|
||||
|
||||
// 账号基础分(默认60分)
|
||||
$accountBaseScore = 60;
|
||||
|
||||
// 已修改微信号(如果baseInfoScore > 0,说明已修改)
|
||||
$isModifiedAlias = $baseInfoScore > 0;
|
||||
|
||||
$composition = [
|
||||
[
|
||||
'name' => '账号基础分',
|
||||
'score' => $accountBaseScore,
|
||||
'formatted' => '+' . $accountBaseScore,
|
||||
]
|
||||
];
|
||||
|
||||
// 如果已修改微信号,添加基础信息分
|
||||
if ($isModifiedAlias) {
|
||||
$composition[] = [
|
||||
'name' => '已修改微信号',
|
||||
'score' => $baseInfoScore,
|
||||
'formatted' => '+' . $baseInfoScore,
|
||||
];
|
||||
}
|
||||
|
||||
// 好友数量加成
|
||||
if ($friendCountScore > 0) {
|
||||
$composition[] = [
|
||||
'name' => '好友数量加成',
|
||||
'score' => $friendCountScore,
|
||||
'formatted' => '+' . $friendCountScore,
|
||||
'friendCount' => $friendCount, // 显示好友总数
|
||||
];
|
||||
}
|
||||
|
||||
return $composition;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取动态记录数据
|
||||
*
|
||||
* @param array $healthScoreInfo 健康分信息
|
||||
* @return array
|
||||
*/
|
||||
protected function getDynamicRecords($healthScoreInfo)
|
||||
{
|
||||
$records = [];
|
||||
|
||||
$frequentPenalty = $healthScoreInfo['frequentPenalty'] ?? 0;
|
||||
$frequentCount = $healthScoreInfo['frequentCount'] ?? 0;
|
||||
$banPenalty = $healthScoreInfo['banPenalty'] ?? 0;
|
||||
$isBanned = $healthScoreInfo['isBanned'] ?? 0;
|
||||
$noFrequentBonus = $healthScoreInfo['noFrequentBonus'] ?? 0;
|
||||
$consecutiveNoFrequentDays = $healthScoreInfo['consecutiveNoFrequentDays'] ?? 0;
|
||||
$lastFrequentTime = $healthScoreInfo['lastFrequentTime'] ?? null;
|
||||
|
||||
// 频繁扣分记录
|
||||
// 根据frequentCount判断是首次还是再次
|
||||
// frequentPenalty存储的是当前状态的扣分(-15或-25),不是累计值
|
||||
if ($frequentCount > 0 && $frequentPenalty < 0) {
|
||||
if ($frequentCount == 1) {
|
||||
// 首次频繁:-15分
|
||||
$records[] = [
|
||||
'name' => '首次触发限额',
|
||||
'score' => $frequentPenalty,
|
||||
'formatted' => (string)$frequentPenalty,
|
||||
'type' => 'penalty',
|
||||
'time' => $lastFrequentTime ? date('Y-m-d H:i:s', $lastFrequentTime) : null,
|
||||
];
|
||||
} else {
|
||||
// 再次频繁:-25分
|
||||
$records[] = [
|
||||
'name' => '再次触发限额',
|
||||
'score' => $frequentPenalty,
|
||||
'formatted' => (string)$frequentPenalty,
|
||||
'type' => 'penalty',
|
||||
'time' => $lastFrequentTime ? date('Y-m-d H:i:s', $lastFrequentTime) : null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 封号扣分记录
|
||||
if ($isBanned && $banPenalty < 0) {
|
||||
$lastBanTime = $healthScoreInfo['lastBanTime'] ?? null;
|
||||
$records[] = [
|
||||
'name' => '封号',
|
||||
'score' => $banPenalty,
|
||||
'formatted' => (string)$banPenalty,
|
||||
'type' => 'penalty',
|
||||
'time' => $lastBanTime ? date('Y-m-d H:i:s', $lastBanTime) : null,
|
||||
];
|
||||
}
|
||||
|
||||
// 不频繁加分记录
|
||||
if ($noFrequentBonus > 0 && $consecutiveNoFrequentDays >= 3) {
|
||||
$lastNoFrequentTime = $healthScoreInfo['lastNoFrequentTime'] ?? null;
|
||||
$records[] = [
|
||||
'name' => '连续' . $consecutiveNoFrequentDays . '天不触发频繁',
|
||||
'score' => $noFrequentBonus,
|
||||
'formatted' => '+' . $noFrequentBonus,
|
||||
'type' => 'bonus',
|
||||
'time' => $lastNoFrequentTime ? date('Y-m-d H:i:s', $lastNoFrequentTime) : null,
|
||||
];
|
||||
}
|
||||
|
||||
return $records;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取今日已加好友数
|
||||
*
|
||||
* @param string $wechatId 微信ID
|
||||
* @return int
|
||||
*/
|
||||
protected function getTodayAddedCount($wechatId)
|
||||
{
|
||||
$start = strtotime(date('Y-m-d 00:00:00'));
|
||||
$end = strtotime(date('Y-m-d 23:59:59'));
|
||||
|
||||
return Db::table('s2_friend_task')
|
||||
->where('wechatId', $wechatId)
|
||||
->whereBetween('createTime', [$start, $end])
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最后添加时间
|
||||
*
|
||||
* @param string $wechatId 微信ID
|
||||
* @return string
|
||||
*/
|
||||
protected function getLastAddTime($wechatId)
|
||||
{
|
||||
$lastTask = Db::table('s2_friend_task')
|
||||
->where('wechatId', $wechatId)
|
||||
->order('createTime', 'desc')
|
||||
->find();
|
||||
|
||||
if (empty($lastTask) || empty($lastTask['createTime'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return date('H:i:s', $lastTask['createTime']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取账号价值(模拟数据)
|
||||
*
|
||||
* @param int $accountId 账号ID
|
||||
* @return array
|
||||
*/
|
||||
protected function getAccountValue($accountId)
|
||||
{
|
||||
// TODO: 后续替换为真实计算逻辑
|
||||
// 模拟数据:¥29,800
|
||||
$value = 29800;
|
||||
|
||||
return [
|
||||
'value' => $value,
|
||||
'formatted' => '¥' . number_format($value, 0, '.', ','),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取今日价值变化(模拟数据)
|
||||
*
|
||||
* @param int $accountId 账号ID
|
||||
* @return array
|
||||
*/
|
||||
protected function getTodayValueChange($accountId)
|
||||
{
|
||||
// TODO: 后续替换为真实计算逻辑
|
||||
// 模拟数据:+500
|
||||
$change = 500;
|
||||
|
||||
return [
|
||||
'change' => $change,
|
||||
'formatted' => $change > 0 ? '+' . $change : (string)$change,
|
||||
'isPositive' => $change > 0,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取好友总数
|
||||
*
|
||||
* @param string $wechatId 微信ID
|
||||
* @param int $companyId 公司ID
|
||||
* @return int
|
||||
*/
|
||||
protected function getTotalFriends($wechatId, $companyId)
|
||||
{
|
||||
// 优先从 s2_wechat_account 表获取
|
||||
$account = Db::table('s2_wechat_account')
|
||||
->where('wechatId', $wechatId)
|
||||
->field('totalFriend')
|
||||
->find();
|
||||
|
||||
if (!empty($account) && isset($account['totalFriend'])) {
|
||||
return (int)$account['totalFriend'];
|
||||
}
|
||||
|
||||
// 如果 totalFriend 为空,则从 s2_wechat_friend 表统计
|
||||
return Db::table('s2_wechat_friend')
|
||||
->where('ownerWechatId', $wechatId)
|
||||
->where('isDeleted', 0)
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取今日新增好友数
|
||||
*
|
||||
* @param string $wechatId 微信ID
|
||||
* @return int
|
||||
*/
|
||||
protected function getTodayNewFriends($wechatId)
|
||||
{
|
||||
$start = strtotime(date('Y-m-d 00:00:00'));
|
||||
$end = strtotime(date('Y-m-d 23:59:59'));
|
||||
|
||||
// 从 s2_wechat_friend 表统计今日新增
|
||||
return Db::table('s2_wechat_friend')
|
||||
->where('ownerWechatId', $wechatId)
|
||||
->whereBetween('createTime', [$start, $end])
|
||||
->where('isDeleted', 0)
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取高价群聊数量
|
||||
* 高价群聊定义:群成员数 >= 50 的群聊
|
||||
*
|
||||
* @param string $wechatId 微信ID
|
||||
* @param int $companyId 公司ID
|
||||
* @return int
|
||||
*/
|
||||
protected function getHighValueChatrooms($wechatId, $companyId)
|
||||
{
|
||||
// 高价群聊定义:群成员数 >= 50
|
||||
$minMemberCount = 50;
|
||||
|
||||
// 查询该微信账号下的高价群聊
|
||||
// 使用子查询统计每个群的成员数
|
||||
$result = Db::query("
|
||||
SELECT COUNT(DISTINCT c.chatroomId) as count
|
||||
FROM s2_wechat_chatroom c
|
||||
INNER JOIN (
|
||||
SELECT chatroomId, COUNT(*) as memberCount
|
||||
FROM s2_wechat_chatroom_member
|
||||
GROUP BY chatroomId
|
||||
HAVING memberCount >= ?
|
||||
) m ON c.chatroomId = m.chatroomId
|
||||
WHERE c.wechatAccountWechatId = ?
|
||||
AND c.isDeleted = 0
|
||||
", [$minMemberCount, $wechatId]);
|
||||
|
||||
return !empty($result) ? (int)$result[0]['count'] : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取今日新增群聊数
|
||||
*
|
||||
* @param string $wechatId 微信ID
|
||||
* @param int $companyId 公司ID
|
||||
* @return int
|
||||
*/
|
||||
protected function getTodayNewChatrooms($wechatId, $companyId)
|
||||
{
|
||||
$start = strtotime(date('Y-m-d 00:00:00'));
|
||||
$end = strtotime(date('Y-m-d 23:59:59'));
|
||||
|
||||
return Db::table('s2_wechat_chatroom')
|
||||
->where('wechatAccountWechatId', $wechatId)
|
||||
->whereBetween('createTime', [$start, $end])
|
||||
->where('isDeleted', 0)
|
||||
->count();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\wechat;
|
||||
|
||||
use app\common\model\TrafficPool as TrafficPoolModel;
|
||||
use app\common\model\WechatAccount as WechatAccountModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
|
||||
/**
|
||||
* 设备微信控制器
|
||||
*/
|
||||
class GetWechatProfileV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取最近互动时间
|
||||
*
|
||||
* @param string $wechatId
|
||||
* @return string
|
||||
*/
|
||||
protected function getLastPlayTime(string $wechatId): string
|
||||
{
|
||||
return date('Y-m-d', strtotime('-1 day'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取好友标签
|
||||
*
|
||||
* @param string $tags
|
||||
* @return array
|
||||
*/
|
||||
protected function getWechatTags(string $tags): array
|
||||
{
|
||||
return json_decode($tags, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取添加时间
|
||||
*
|
||||
* @param int|string $timestamp
|
||||
* @return string
|
||||
*/
|
||||
protected function getAddShipDate($timestamp): string
|
||||
{
|
||||
return is_numeric($timestamp) ? date('Y-m-d', $timestamp) : date('Y-m-d', strtotime($timestamp));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取流量来源
|
||||
*
|
||||
* @param string $wechatId
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getTrafficSource(string $wechatId): string
|
||||
{
|
||||
return (string)TrafficPoolModel::alias('p')
|
||||
->field('t.id')
|
||||
->join('traffic_source s', 's.identifier = p.identifier')
|
||||
->where('p.wechatId', $wechatId)
|
||||
->value('fromd');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取微信账号
|
||||
*
|
||||
* @param string $wechatId
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getWechatAccountProfileByWechatId(string $wechatId): array
|
||||
{
|
||||
$account = WechatAccountModel::alias('w')
|
||||
->field(
|
||||
[
|
||||
'w.id', 'w.avatar', 'w.nickname', 'w.region', 'w.wechatId',
|
||||
'CASE WHEN w.alias IS NULL OR w.alias = "" THEN w.wechatId ELSE w.alias END AS wechatId',
|
||||
'f.createTime', 'f.tags', 'f.memo'
|
||||
]
|
||||
)
|
||||
->join('wechat_friendship f', 'w.wechatId=f.wechatId')
|
||||
->where('w.wechatId', $wechatId)
|
||||
->find();
|
||||
|
||||
if (is_null($account)) {
|
||||
throw new \Exception('未获取到微信账号数据', 404);
|
||||
}
|
||||
|
||||
return $account->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取微信好友详情
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$results = $this->getWechatAccountProfileByWechatId(
|
||||
$this->request->param('wechatId/s')
|
||||
);
|
||||
|
||||
return ResponseHelper::success(
|
||||
array_merge($results, [
|
||||
'playDate' => $this->getLastPlayTime($results['wechatId']),
|
||||
'source' => $this->getTrafficSource($results['wechatId']),
|
||||
'tags' => $this->getWechatTags($results['tags']),
|
||||
'addDate' => $this->getAddShipDate($results['createTime']),
|
||||
])
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\wechat;
|
||||
|
||||
use app\common\model\Device as DeviceModel;
|
||||
use app\common\model\Device as DevicesModel;
|
||||
use app\common\model\DeviceUser as DeviceUserModel;
|
||||
use app\common\model\DeviceWechatLogin as DeviceWechatLoginModel;
|
||||
use app\common\model\User as UserModel;
|
||||
use app\common\model\WechatAccount as WechatAccountModel;
|
||||
// 不再使用WechatFriendShipModel和WechatCustomerModel,改为直接查询s2_wechat_friend和s2_wechat_account_score表
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 微信控制器
|
||||
*
|
||||
* 性能优化建议:
|
||||
* 1. 为以下字段添加索引以提高查询性能:
|
||||
* - device_wechat_login表: (companyId, wechatId), (deviceId)
|
||||
* - wechat_account表: (wechatId)
|
||||
* - wechat_customer表: (companyId, wechatId)
|
||||
* - wechat_friend_ship表: (ownerWechatId), (createTime)
|
||||
* - s2_wechat_message表: (wechatAccountId, wechatTime)
|
||||
*
|
||||
* 2. 考虑创建以下复合索引:
|
||||
* - device_wechat_login表: (companyId, deviceId, wechatId)
|
||||
* - wechat_friend_ship表: (ownerWechatId, createTime)
|
||||
*/
|
||||
class GetWechatsOnDevicesV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 主操盘手获取项目下所有设备的id
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getCompanyDevicesId(): array
|
||||
{
|
||||
return DevicesModel::where(
|
||||
[
|
||||
'companyId' => $this->getUserInfo('companyId')
|
||||
]
|
||||
)
|
||||
->column('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 非主操盘手获取分配的设备
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getUserDevicesId(): array
|
||||
{
|
||||
return DeviceUserModel::where(
|
||||
[
|
||||
'userId' => $this->getUserInfo('id'),
|
||||
'companyId' => $this->getUserInfo('companyId')
|
||||
]
|
||||
)
|
||||
->column('deviceId');
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据不同角色,显示的设备数量不同
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getDevicesId(): array
|
||||
{
|
||||
return ($this->getUserInfo('isAdmin') == UserModel::ADMIN_STP)
|
||||
? $this->getCompanyDevicesId() // 主操盘手获取所有的设备
|
||||
: $this->getUserDevicesId(); // 非主操盘手获取分配的设备
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取有登录设备的微信id
|
||||
* 优化:使用索引字段,减少数据查询量
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getWechatIdsOnDevices(): array
|
||||
{
|
||||
// 关联设备id查询,过滤掉已删除的设备
|
||||
if (empty($deviceIds = $this->getDevicesId())) {
|
||||
throw new \Exception('暂无设备数据', 200);
|
||||
}
|
||||
|
||||
// 优化:直接使用DISTINCT减少数据传输量
|
||||
return DeviceWechatLoginModel::distinct(true)
|
||||
->where([
|
||||
'companyId' => $this->getUserInfo('companyId'),
|
||||
// 'alive' => DeviceWechatLoginModel::ALIVE_WECHAT_ACTIVE,
|
||||
])
|
||||
->where('deviceId', 'in', $deviceIds)
|
||||
->column('wechatId');
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询条件
|
||||
*
|
||||
* @param array $params
|
||||
* @return array
|
||||
*/
|
||||
protected function makeWhere(array $params = []): array
|
||||
{
|
||||
if (empty($wechatIds = $this->getWechatIdsOnDevices())) {
|
||||
throw new \Exception('设备尚未有登录微信', 200);
|
||||
}
|
||||
|
||||
// 关键词搜索(同时搜索微信号和昵称)
|
||||
if (!empty($keyword = $this->request->param('keyword'))) {
|
||||
$where[] = ["w.wechatId|w.alias|w.nickname", 'LIKE', '%' . $keyword . '%'];
|
||||
}
|
||||
|
||||
$where['w.wechatId'] = array('in', implode(',', $wechatIds));
|
||||
|
||||
return array_merge($where, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取在线微信账号列表
|
||||
* 优化:减少查询字段,使用索引,优化JOIN条件
|
||||
*
|
||||
* @param array $where
|
||||
* @return \think\Paginator 分页对象
|
||||
*/
|
||||
protected function getOnlineWechatList(array $where): \think\Paginator
|
||||
{
|
||||
// 获取微信在线状态筛选参数(1=在线,0=离线,不传=全部)
|
||||
$wechatStatus = $this->request->param('wechatStatus');
|
||||
|
||||
// 优化:只查询必要字段,使用FORCE INDEX提示数据库使用索引
|
||||
$query = WechatAccountModel::alias('w')
|
||||
->field(
|
||||
[
|
||||
'w.id', 'w.nickname', 'w.avatar', 'w.wechatId',
|
||||
'CASE WHEN w.alias IS NULL OR w.alias = "" THEN w.wechatId ELSE w.alias END AS wechatAccount',
|
||||
'MAX(l.deviceId) as deviceId', 'MAX(l.alive) as alive' // 使用MAX确保GROUP BY时获取正确的在线状态
|
||||
]
|
||||
)
|
||||
// 优化:使用INNER JOIN代替LEFT JOIN,并添加索引提示
|
||||
->join('device_wechat_login l', 'w.wechatId = l.wechatId AND l.companyId = '. $this->getUserInfo('companyId'), 'INNER')
|
||||
// 添加s2_wechat_account表的LEFT JOIN,用于筛选微信在线状态
|
||||
->join(['s2_wechat_account' => 'sa'], 'w.wechatId = sa.wechatId', 'LEFT')
|
||||
->group('w.wechatId')
|
||||
// 优化:在线状态优先排序(alive=1的排在前面),然后按wechatId排序
|
||||
// 注意:ORDER BY使用SELECT中定义的别名alive,而不是聚合函数
|
||||
->order('alive desc, w.wechatId desc');
|
||||
|
||||
// 根据wechatStatus参数筛选(1=在线,0=离线,不传=全部)
|
||||
if ($wechatStatus !== null && $wechatStatus !== '') {
|
||||
$wechatStatus = (int)$wechatStatus;
|
||||
if ($wechatStatus === 1) {
|
||||
// 筛选在线:wechatAlive = 1
|
||||
$query->where('sa.wechatAlive', 1);
|
||||
} elseif ($wechatStatus === 0) {
|
||||
// 筛选离线:wechatAlive = 0 或 NULL
|
||||
$query->where(function($query) {
|
||||
$query->where('sa.wechatAlive', 0)
|
||||
->whereOr('sa.wechatAlive', 'exp', 'IS NULL');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 应用查询条件
|
||||
foreach ($where as $key => $value) {
|
||||
if (is_numeric($key) && is_array($value) && isset($value[0]) && $value[0] === 'exp') {
|
||||
$query->whereExp('', $value[1]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
$query->where($key, ...$value);
|
||||
continue;
|
||||
}
|
||||
|
||||
$query->where($key, $value);
|
||||
}
|
||||
|
||||
// 优化:使用简单计数查询
|
||||
return $query->paginate(
|
||||
$this->request->param('limit/d', 10),
|
||||
false,
|
||||
['page' => $this->request->param('page/d', 1)]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建返回数据
|
||||
*
|
||||
* @param \think\Paginator $result
|
||||
* @return array
|
||||
*/
|
||||
protected function makeResultedSet(\think\Paginator $result): array
|
||||
{
|
||||
$resultSets = [];
|
||||
$items = $result->items();
|
||||
|
||||
if (empty($items)) {
|
||||
return $resultSets;
|
||||
}
|
||||
|
||||
$wechatIds = array_values(array_unique(array_map(function ($item) {
|
||||
return $item->wechatId ?? ($item['wechatId'] ?? '');
|
||||
}, $items)));
|
||||
|
||||
$metrics = $this->collectWechatMetrics($wechatIds);
|
||||
|
||||
foreach ($items as $item) {
|
||||
$addLimit = $metrics['addLimit'][$item->wechatId] ?? 0;
|
||||
$todayAdded = $metrics['todayAdded'][$item->wechatId] ?? 0;
|
||||
// 计算今日可添加数量 = 可添加额度 - 今日已添加
|
||||
$todayCanAdd = max(0, $addLimit - $todayAdded);
|
||||
|
||||
$sections = $item->toArray() + [
|
||||
'times' => $addLimit,
|
||||
'addedCount' => $todayAdded,
|
||||
'todayCanAdd' => $todayCanAdd, // 今日可添加数量
|
||||
'wechatStatus' => $metrics['wechatStatus'][$item->wechatId] ?? 0,
|
||||
'totalFriend' => $metrics['totalFriend'][$item->wechatId] ?? 0,
|
||||
'deviceMemo' => $metrics['deviceMemo'][$item->wechatId] ?? '',
|
||||
'activeTime' => $metrics['activeTime'][$item->wechatId] ?? '-',
|
||||
];
|
||||
|
||||
array_push($resultSets, $sections);
|
||||
}
|
||||
|
||||
return $resultSets;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量收集微信账号的统计信息
|
||||
* 优化:合并查询,减少数据库访问次数,使用缓存
|
||||
*
|
||||
* @param array $wechatIds
|
||||
* @return array
|
||||
*/
|
||||
protected function collectWechatMetrics(array $wechatIds): array
|
||||
{
|
||||
$metrics = [
|
||||
'addLimit' => [],
|
||||
'todayAdded' => [],
|
||||
'totalFriend' => [],
|
||||
'wechatStatus' => [],
|
||||
'deviceMemo' => [],
|
||||
'activeTime' => [],
|
||||
];
|
||||
|
||||
if (empty($wechatIds)) {
|
||||
return $metrics;
|
||||
}
|
||||
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
// 使用缓存键,避免短时间内重复查询
|
||||
$cacheKey = 'wechat_metrics_' . md5(implode(',', $wechatIds) . '_' . $companyId);
|
||||
|
||||
// 尝试从缓存获取数据(缓存5分钟)
|
||||
$cachedMetrics = cache($cacheKey);
|
||||
if ($cachedMetrics) {
|
||||
return $cachedMetrics;
|
||||
}
|
||||
|
||||
// 优化1:可添加好友额度 - 从s2_wechat_account_score表获取maxAddFriendPerDay
|
||||
$scoreRows = Db::table('s2_wechat_account_score')
|
||||
->whereIn('wechatId', $wechatIds)
|
||||
->column('maxAddFriendPerDay', 'wechatId');
|
||||
foreach ($scoreRows as $wechatId => $maxAddFriendPerDay) {
|
||||
$metrics['addLimit'][$wechatId] = (int)($maxAddFriendPerDay ?? 0);
|
||||
}
|
||||
|
||||
// 优化2:今日新增好友 - 使用索引字段和预计算
|
||||
$start = strtotime(date('Y-m-d 00:00:00'));
|
||||
$end = strtotime(date('Y-m-d 23:59:59'));
|
||||
|
||||
// 使用单次查询获取所有wechatIds的今日新增和总好友数
|
||||
// 根据数据库结构使用s2_wechat_friend表而不是wechat_friend_ship
|
||||
$friendshipStats = Db::query("
|
||||
SELECT
|
||||
ownerWechatId,
|
||||
SUM(IF(createTime BETWEEN {$start} AND {$end}, 1, 0)) as today_added,
|
||||
COUNT(*) as total_friend
|
||||
FROM
|
||||
s2_wechat_friend
|
||||
WHERE
|
||||
ownerWechatId IN ('" . implode("','", $wechatIds) . "')
|
||||
AND isDeleted = 0
|
||||
GROUP BY
|
||||
ownerWechatId
|
||||
");
|
||||
|
||||
// 处理结果
|
||||
foreach ($friendshipStats as $row) {
|
||||
$wechatId = $row['ownerWechatId'] ?? '';
|
||||
if ($wechatId) {
|
||||
$metrics['todayAdded'][$wechatId] = (int)($row['today_added'] ?? 0);
|
||||
$metrics['totalFriend'][$wechatId] = (int)($row['total_friend'] ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
// 优化3:微信在线状态 - 从s2_wechat_account表获取wechatAlive
|
||||
$wechatAccountRows = Db::table('s2_wechat_account')
|
||||
->whereIn('wechatId', $wechatIds)
|
||||
->field('wechatId, wechatAlive')
|
||||
->select();
|
||||
|
||||
foreach ($wechatAccountRows as $row) {
|
||||
$wechatId = $row['wechatId'] ?? '';
|
||||
if (!empty($wechatId)) {
|
||||
$metrics['wechatStatus'][$wechatId] = (int)($row['wechatAlive'] ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
// 优化4:设备状态与备注 - 使用INNER JOIN和索引
|
||||
$loginRows = Db::name('device_wechat_login')
|
||||
->alias('l')
|
||||
->join('device d', 'd.id = l.deviceId', 'LEFT')
|
||||
->field('l.wechatId, l.alive, d.memo')
|
||||
->where('l.companyId', $companyId)
|
||||
->whereIn('l.wechatId', $wechatIds)
|
||||
->order('l.id', 'desc')
|
||||
->select();
|
||||
|
||||
// 使用临时数组避免重复处理
|
||||
$processedWechatIds = [];
|
||||
foreach ($loginRows as $row) {
|
||||
$wechatId = $row['wechatId'] ?? '';
|
||||
// 只处理每个wechatId的第一条记录(最新的)
|
||||
if (!empty($wechatId) && !in_array($wechatId, $processedWechatIds)) {
|
||||
// 如果s2_wechat_account表中没有wechatAlive,则使用device_wechat_login的alive作为备用
|
||||
if (!isset($metrics['wechatStatus'][$wechatId])) {
|
||||
$metrics['wechatStatus'][$wechatId] = (int)($row['alive'] ?? 0);
|
||||
}
|
||||
$metrics['deviceMemo'][$wechatId] = $row['memo'] ?? '';
|
||||
$processedWechatIds[] = $wechatId;
|
||||
}
|
||||
}
|
||||
|
||||
// 优化5:活跃时间 - 使用JOIN减少查询次数
|
||||
$activeTimeResults = Db::query("
|
||||
SELECT
|
||||
a.wechatId,
|
||||
MAX(m.wechatTime) as lastTime
|
||||
FROM
|
||||
s2_wechat_account a
|
||||
LEFT JOIN
|
||||
s2_wechat_message m ON a.id = m.wechatAccountId
|
||||
WHERE
|
||||
a.wechatId IN ('" . implode("','", $wechatIds) . "')
|
||||
GROUP BY
|
||||
a.wechatId
|
||||
");
|
||||
|
||||
foreach ($activeTimeResults as $row) {
|
||||
$wechatId = $row['wechatId'] ?? '';
|
||||
$lastTime = (int)($row['lastTime'] ?? 0);
|
||||
if (!empty($wechatId) && $lastTime > 0) {
|
||||
$metrics['activeTime'][$wechatId] = date('Y-m-d H:i:s', $lastTime);
|
||||
} else {
|
||||
$metrics['activeTime'][$wechatId] = '-';
|
||||
}
|
||||
}
|
||||
|
||||
// 确保所有wechatId都有wechatStatus值(默认0)
|
||||
foreach ($wechatIds as $wechatId) {
|
||||
if (!isset($metrics['wechatStatus'][$wechatId])) {
|
||||
$metrics['wechatStatus'][$wechatId] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 存入缓存,有效期5分钟
|
||||
cache($cacheKey, $metrics, 300);
|
||||
|
||||
return $metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取在线微信账号列表
|
||||
* 优化:添加缓存,优化分页逻辑
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
// 获取分页参数
|
||||
$page = $this->request->param('page/d', 1);
|
||||
$limit = $this->request->param('limit/d', 10);
|
||||
$keyword = $this->request->param('keyword');
|
||||
$wechatStatus = $this->request->param('wechatStatus');
|
||||
|
||||
// 创建缓存键(基于用户、分页、搜索条件和在线状态筛选)
|
||||
$cacheKey = 'wechat_list_' . $this->getUserInfo('id') . '_' . $page . '_' . $limit . '_' . md5($keyword ?? '') . '_' . ($wechatStatus ?? 'all');
|
||||
|
||||
// 尝试从缓存获取数据(缓存2分钟)
|
||||
$cachedData = cache($cacheKey);
|
||||
if ($cachedData) {
|
||||
return ResponseHelper::success($cachedData);
|
||||
}
|
||||
|
||||
// 如果没有缓存,执行查询
|
||||
$result = $this->getOnlineWechatList(
|
||||
$this->makeWhere()
|
||||
);
|
||||
|
||||
$responseData = [
|
||||
'list' => $this->makeResultedSet($result),
|
||||
'total' => $result->total(),
|
||||
];
|
||||
|
||||
// 存入缓存,有效期2分钟
|
||||
cache($cacheKey, $responseData, 120);
|
||||
|
||||
return ResponseHelper::success($responseData);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\wechat;
|
||||
|
||||
use app\common\model\DeviceUser as DeviceUserModel;
|
||||
use app\common\model\DeviceWechatLogin as DeviceWechatLoginModel;
|
||||
use app\common\model\User as UserModel;
|
||||
use app\common\model\WechatAccount as WechatAccountModel;
|
||||
use app\common\model\WechatFriendShip as WechatFriendShipModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
use think\model\Collection as ResultCollection;
|
||||
|
||||
/**
|
||||
* 设备管理控制器
|
||||
*/
|
||||
class GetWechatsRelatedDeviceV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 检查用户是否有权限操作指定设备
|
||||
*
|
||||
* @param int $deviceId
|
||||
* @return void
|
||||
*/
|
||||
protected function checkUserDevicePermission(int $deviceId): void
|
||||
{
|
||||
$hasPermission = DeviceUserModel::where(
|
||||
[
|
||||
'deviceId' => $deviceId,
|
||||
'userId' => $this->getUserInfo('id'),
|
||||
'companyId' => $this->getUserInfo('companyId')
|
||||
]
|
||||
)
|
||||
->count() > 0;
|
||||
|
||||
if (!$hasPermission) {
|
||||
throw new \Exception('您没有权限查看该设备', 403);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询设备关联的微信ID列表
|
||||
*
|
||||
* @param int $deviceId
|
||||
* @return array
|
||||
*/
|
||||
protected function getDeviceWechatIds(int $deviceId): array
|
||||
{
|
||||
return DeviceWechatLoginModel::where(
|
||||
[
|
||||
'deviceId' => $deviceId,
|
||||
'companyId' => $this->getUserInfo('companyId')
|
||||
]
|
||||
)
|
||||
->group('wechatId')->column('wechatId');
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过设备关联的微信号列表获取微信账号
|
||||
*
|
||||
* @param array $wechatIds
|
||||
* @return ResultCollection
|
||||
*/
|
||||
protected function getWechatAccountsByIds(array $wechatIds): ResultCollection
|
||||
{
|
||||
return WechatAccountModel::alias('w')
|
||||
->field([
|
||||
'w.wechatId', 'w.nickname', 'w.avatar', 'w.gender', 'w.createTime',
|
||||
'CASE WHEN w.alias IS NULL OR w.alias = "" THEN w.wechatId ELSE w.alias END AS wechatAccount',
|
||||
])
|
||||
->whereIn('w.wechatId', $wechatIds)
|
||||
->select();
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO 通过微信id获取微信最后活跃时间
|
||||
*
|
||||
* @param int $time
|
||||
* @return string
|
||||
*/
|
||||
protected function getWechatLastActiveTime(string $wechatId): string
|
||||
{
|
||||
return date('Y-m-d H:i:s', time());
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO 加友状态
|
||||
*
|
||||
* @param string $wechatId
|
||||
* @return string
|
||||
*/
|
||||
protected function getWechatStatusText(string $wechatId): string
|
||||
{
|
||||
return 1 ? '可加友' : '已停用';
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO 账号状态
|
||||
*
|
||||
* @param string $wechatId
|
||||
* @return string
|
||||
*/
|
||||
protected function getWechatAliveText(string $wechatId): string
|
||||
{
|
||||
return 1 ? '正常' : '异常';
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计微信好友
|
||||
*
|
||||
* @param string $ownerWechatId
|
||||
* @return int
|
||||
*/
|
||||
protected function getCountFriend(string $ownerWechatId): int
|
||||
{
|
||||
return WechatFriendShipModel::where(
|
||||
[
|
||||
'ownerWechatId' => $ownerWechatId,
|
||||
'companyId' => $this->getUserInfo('companyId')
|
||||
]
|
||||
)
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备关联的微信账号信息
|
||||
*
|
||||
* @param int $deviceId
|
||||
* @return array
|
||||
*/
|
||||
protected function getDeviceRelatedAccounts(int $deviceId): array
|
||||
{
|
||||
// 获取设备关联的微信ID列表
|
||||
$wechatIds = $this->getDeviceWechatIds($deviceId);
|
||||
|
||||
if (!empty($wechatIds)) {
|
||||
$collection = $this->getWechatAccountsByIds($wechatIds);
|
||||
|
||||
foreach ($collection as $account) {
|
||||
$account->lastActive = $this->getWechatLastActiveTime($account->wechatId);
|
||||
$account->statusText = $this->getWechatStatusText($account->wechatId);
|
||||
$account->totalFriend = $this->getCountFriend($account->wechatId);
|
||||
$account->wechatAliveText = $this->getWechatAliveText($account->wechatId);
|
||||
}
|
||||
|
||||
return $collection->toArray();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备关联的微信账号
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$deviceId = $this->request->param('id/d');
|
||||
|
||||
if ($this->getUserInfo('isAdmin') != UserModel::ADMIN_STP) {
|
||||
$this->checkUserDevicePermission($deviceId);
|
||||
}
|
||||
|
||||
// 获取设备关联的微信账号
|
||||
$wechatAccounts = $this->getDeviceRelatedAccounts($deviceId);
|
||||
|
||||
return ResponseHelper::success(
|
||||
[
|
||||
'deviceId' => $deviceId,
|
||||
'accounts' => $wechatAccounts,
|
||||
'total' => count($wechatAccounts)
|
||||
]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
157
application/cunkebao/controller/wechat/PostTransferFriends.php
Normal file
157
application/cunkebao/controller/wechat/PostTransferFriends.php
Normal file
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\wechat;
|
||||
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use app\cunkebao\controller\plan\PostCreateAddFriendPlanV1Controller;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
class PostTransferFriends extends BaseController
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$wechatId = $this->request->param('wechatId', '');
|
||||
$inherit = $this->request->param('inherit', '');
|
||||
$greeting = $this->request->param('greeting', '');
|
||||
$firstMessage = $this->request->param('firstMessage', '');
|
||||
$devices = $this->request->param('devices', []);
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
|
||||
if (empty($wechatId)){
|
||||
return ResponseHelper::error('迁移的微信不能为空');
|
||||
}
|
||||
|
||||
if (empty($devices)){
|
||||
return ResponseHelper::error('迁移的设备不能为空');
|
||||
}
|
||||
if (empty($greeting)){
|
||||
return ResponseHelper::error('打招呼不能为空');
|
||||
}
|
||||
if (!is_array($devices)){
|
||||
return ResponseHelper::error('迁移的设备必须为数组');
|
||||
}
|
||||
|
||||
$wechat = Db::name('wechat_customer')->alias('wc')
|
||||
->join('wechat_account wa', 'wc.wechatId = wa.wechatId')
|
||||
->where(['wc.wechatId' => $wechatId])
|
||||
->field('wa.*')
|
||||
->find();
|
||||
|
||||
if (empty($wechat)) {
|
||||
return ResponseHelper::error('该微信不存在');
|
||||
}
|
||||
|
||||
$devices = Db::name('device')
|
||||
->where(['companyId' => $companyId,'deleteTime' => 0])
|
||||
->whereIn('id', $devices)
|
||||
->column('id');
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
try {
|
||||
$sceneConf = [
|
||||
'enabled' => true,
|
||||
'posters' => [
|
||||
'id' => 'poster-3',
|
||||
'name' => '点击咨询',
|
||||
'src' => 'https://hebbkx1anhila5yf.public.blob.vercel-storage.com/%E7%82%B9%E5%87%BB%E5%92%A8%E8%AF%A2-FTiyAMAPop2g9LvjLOLDz0VwPg3KVu.gif'
|
||||
]
|
||||
];
|
||||
$reqConf = [
|
||||
'device' => $devices,
|
||||
'startTime' => '09:00',
|
||||
'endTime' => '18:00',
|
||||
'remarkType' => 'phone',
|
||||
'addFriendInterval' => 60,
|
||||
'greeting' => !empty($greeting) ? $greeting :'我是'. $wechat['nickname'] .'的新号,请通过'
|
||||
];
|
||||
|
||||
if (!empty($firstMessage)){
|
||||
$msgConf = [
|
||||
[
|
||||
'day' => 0,
|
||||
'messages' => [
|
||||
[
|
||||
'id' => 1,
|
||||
'type' => 'text',
|
||||
'content' => $firstMessage,
|
||||
'intervalUnit' => 'seconds',
|
||||
'sendInterval' => 5,
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
}else{
|
||||
$msgConf = [];
|
||||
}
|
||||
|
||||
// 使用容器获取控制器实例,而不是直接实例化
|
||||
$createAddFriendPlan = app('app\cunkebao\controller\plan\PostCreateAddFriendPlanV1Controller');
|
||||
|
||||
$taskId = Db::name('customer_acquisition_task')->insertGetId([
|
||||
'name' => '迁移好友('. $wechat['nickname'] .')',
|
||||
'sceneId' => 10,
|
||||
'sceneConf' => json_encode($sceneConf,256),
|
||||
'reqConf' => json_encode($reqConf,256),
|
||||
'tagConf' => json_encode([]),
|
||||
'msgConf' => json_encode($msgConf,256),
|
||||
'userId' => $this->getUserInfo('id'),
|
||||
'companyId' => $companyId,
|
||||
'status' => 0,
|
||||
'createTime' => time(),
|
||||
'apiKey' => $createAddFriendPlan->generateApiKey(),
|
||||
]);
|
||||
|
||||
$friends = Db::table('s2_wechat_friend')
|
||||
->where(['ownerWechatId' => $wechatId])
|
||||
->group('wechatId')
|
||||
->order('id DESC')
|
||||
->column('id', 'wechatId,alias,phone,labels,conRemark');
|
||||
|
||||
// 1000条为一组进行批量处理
|
||||
$batchSize = 1000;
|
||||
$totalRows = count($friends);
|
||||
|
||||
for ($i = 0; $i < $totalRows; $i += $batchSize) {
|
||||
$batchRows = array_slice($friends, $i, $batchSize);
|
||||
if (!empty($batchRows)) {
|
||||
$newData = [];
|
||||
foreach ($batchRows as $row) {
|
||||
if (!empty($row['phone'])) {
|
||||
$phone = $row['phone'];
|
||||
} elseif (!empty($row['alias'])) {
|
||||
$phone = $row['alias'];
|
||||
} else {
|
||||
$phone = $row['wechatId'];
|
||||
}
|
||||
|
||||
$tags = !empty($row['labels']) ? json_decode($row['labels'], true) : [];
|
||||
$newData[] = [
|
||||
'task_id' => $taskId,
|
||||
'name' => '',
|
||||
'source' => '迁移好友('. $wechat['nickname'] .')',
|
||||
'phone' => $phone,
|
||||
'remark' => !empty($inherit) ? $row['conRemark'] : '',
|
||||
'tags' => !empty($inherit) ? json_encode($tags, JSON_UNESCAPED_UNICODE) : json_encode([]),
|
||||
'siteTags' => json_encode([]),
|
||||
'status' => 0,
|
||||
'createTime' => time(),
|
||||
];
|
||||
}
|
||||
Db::name('task_customer')->insertAll($newData);
|
||||
}
|
||||
}
|
||||
return ResponseHelper::success('好友迁移创建成功' );
|
||||
} catch (\Exception $e) {
|
||||
// 回滚事务
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('好友迁移创建失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\workbench;
|
||||
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 常用功能控制器
|
||||
*/
|
||||
class CommonFunctionsController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取常用功能列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getList()
|
||||
{
|
||||
try {
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
// 从数据库查询常用功能列表
|
||||
$functions = Db::name('workbench_function')
|
||||
->where('status', 1)
|
||||
->order('sort ASC, id ASC')
|
||||
->select();
|
||||
|
||||
|
||||
// 处理数据,判断是否显示New标签(创建时间近1个月)
|
||||
$oneMonthAgo = time() - 30 * 24 * 60 * 60; // 30天前的时间戳
|
||||
foreach ($functions as &$function) {
|
||||
// 判断是否显示New标签:创建时间在近1个月内
|
||||
$function['isNew'] = ($function['createTime'] >= $oneMonthAgo) ? true : false;
|
||||
$function['labels'] = json_decode($function['labels'],true);
|
||||
}
|
||||
unset($function);
|
||||
|
||||
return ResponseHelper::success([
|
||||
'list' => $functions
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('获取常用功能列表失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\workbench;
|
||||
|
||||
use think\Controller;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 工作台 - 自动点赞相关功能
|
||||
*/
|
||||
class WorkbenchAutoLikeController extends Controller
|
||||
{
|
||||
/**
|
||||
* 获取点赞记录列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getLikeRecords()
|
||||
{
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$workbenchId = $this->request->param('workbenchId', 0);
|
||||
|
||||
$where = [
|
||||
['wali.workbenchId', '=', $workbenchId]
|
||||
];
|
||||
|
||||
// 查询点赞记录
|
||||
$list = Db::name('workbench_auto_like_item')->alias('wali')
|
||||
->join(['s2_wechat_moments' => 'wm'], 'wali.snsId = wm.snsId')
|
||||
->field([
|
||||
'wali.id',
|
||||
'wali.workbenchId',
|
||||
'wali.momentsId',
|
||||
'wali.snsId',
|
||||
'wali.wechatAccountId',
|
||||
'wali.wechatFriendId',
|
||||
'wali.createTime as likeTime',
|
||||
'wm.content',
|
||||
'wm.resUrls',
|
||||
'wm.createTime as momentTime',
|
||||
'wm.userName',
|
||||
])
|
||||
->where($where)
|
||||
->order('wali.createTime', 'desc')
|
||||
->group('wali.id')
|
||||
->page($page, $limit)
|
||||
->select();
|
||||
|
||||
|
||||
// 处理数据
|
||||
foreach ($list as &$item) {
|
||||
//处理用户信息
|
||||
$friend = Db::table('s2_wechat_friend')
|
||||
->where(['id' => $item['wechatFriendId']])
|
||||
->field('nickName,avatar')
|
||||
->find();
|
||||
if (!empty($friend)) {
|
||||
$item['friendName'] = $friend['nickName'];
|
||||
$item['friendAvatar'] = $friend['avatar'];
|
||||
} else {
|
||||
$item['friendName'] = '';
|
||||
$item['friendAvatar'] = '';
|
||||
}
|
||||
|
||||
|
||||
//处理客服
|
||||
$friend = Db::table('s2_wechat_account')
|
||||
->where(['id' => $item['wechatAccountId']])
|
||||
->field('nickName,avatar')
|
||||
->find();
|
||||
if (!empty($friend)) {
|
||||
$item['operatorName'] = $friend['nickName'];
|
||||
$item['operatorAvatar'] = $friend['avatar'];
|
||||
} else {
|
||||
$item['operatorName'] = '';
|
||||
$item['operatorAvatar'] = '';
|
||||
}
|
||||
|
||||
// 处理时间格式
|
||||
$item['likeTime'] = date('Y-m-d H:i:s', $item['likeTime']);
|
||||
$item['momentTime'] = !empty($item['momentTime']) ? date('Y-m-d H:i:s', $item['momentTime']) : '';
|
||||
|
||||
// 处理资源链接
|
||||
if (!empty($item['resUrls'])) {
|
||||
$item['resUrls'] = json_decode($item['resUrls'], true);
|
||||
} else {
|
||||
$item['resUrls'] = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 获取总记录数
|
||||
$total = Db::name('workbench_auto_like_item')->alias('wali')
|
||||
->where($where)
|
||||
->count();
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'list' => $list,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'limit' => $limit
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
3353
application/cunkebao/controller/workbench/WorkbenchController.php
Normal file
3353
application/cunkebao/controller/workbench/WorkbenchController.php
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,313 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\workbench;
|
||||
|
||||
use think\Controller;
|
||||
use think\Db;
|
||||
use think\facade\Env;
|
||||
|
||||
/**
|
||||
* 工作台 - 辅助功能
|
||||
*/
|
||||
class WorkbenchHelperController extends Controller
|
||||
{
|
||||
/**
|
||||
* 获取所有微信好友标签及数量统计
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getDeviceLabels()
|
||||
{
|
||||
$deviceIds = $this->request->param('deviceIds', '');
|
||||
$companyId = $this->request->userInfo['companyId'];
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
|
||||
$where = [
|
||||
['wc.companyId', '=', $companyId],
|
||||
];
|
||||
|
||||
if (!empty($deviceIds)) {
|
||||
$deviceIds = explode(',', $deviceIds);
|
||||
$where[] = ['dwl.deviceId', 'in', $deviceIds];
|
||||
}
|
||||
|
||||
$wechatAccounts = Db::name('wechat_customer')->alias('wc')
|
||||
->join('device_wechat_login dwl', 'dwl.wechatId = wc.wechatId AND dwl.companyId = wc.companyId AND dwl.alive = 1')
|
||||
->join(['s2_wechat_account' => 'wa'], 'wa.wechatId = wc.wechatId')
|
||||
->where($where)
|
||||
->field('wa.id,wa.wechatId,wa.nickName,wa.labels')
|
||||
->select();
|
||||
$labels = [];
|
||||
$wechatIds = [];
|
||||
foreach ($wechatAccounts as $account) {
|
||||
$labelArr = json_decode($account['labels'], true);
|
||||
if (is_array($labelArr)) {
|
||||
foreach ($labelArr as $label) {
|
||||
if ($label !== '' && $label !== null) {
|
||||
$labels[] = $label;
|
||||
}
|
||||
}
|
||||
}
|
||||
$wechatIds[] = $account['wechatId'];
|
||||
}
|
||||
// 去重(只保留一个)
|
||||
$labels = array_values(array_unique($labels));
|
||||
$wechatIds = array_unique($wechatIds);
|
||||
|
||||
// 搜索过滤
|
||||
if (!empty($keyword)) {
|
||||
$labels = array_filter($labels, function ($label) use ($keyword) {
|
||||
return mb_stripos($label, $keyword) !== false;
|
||||
});
|
||||
$labels = array_values($labels); // 重新索引数组
|
||||
}
|
||||
|
||||
// 分页处理
|
||||
$labels2 = array_slice($labels, ($page - 1) * $limit, $limit);
|
||||
|
||||
// 统计数量
|
||||
$newLabel = [];
|
||||
foreach ($labels2 as $label) {
|
||||
$friendCount = Db::table('s2_wechat_friend')
|
||||
->whereIn('ownerWechatId', $wechatIds)
|
||||
->where('labels', 'like', '%"' . $label . '"%')
|
||||
->count();
|
||||
$newLabel[] = [
|
||||
'label' => $label,
|
||||
'count' => $friendCount
|
||||
];
|
||||
}
|
||||
|
||||
// 返回结果
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'list' => $newLabel,
|
||||
'total' => count($labels),
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取群列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getGroupList()
|
||||
{
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
|
||||
$where = [
|
||||
['wg.deleteTime', '=', 0],
|
||||
['wg.companyId', '=', $this->request->userInfo['companyId']],
|
||||
];
|
||||
|
||||
if (!empty($keyword)) {
|
||||
$where[] = ['wg.name', 'like', '%' . $keyword . '%'];
|
||||
}
|
||||
|
||||
$query = Db::name('wechat_group')->alias('wg')
|
||||
->join('wechat_account wa', 'wa.wechatId = wg.ownerWechatId')
|
||||
->where($where);
|
||||
|
||||
$total = $query->count();
|
||||
$list = $query->order('wg.id', 'desc')
|
||||
->field('wg.id,wg.name as groupName,wg.ownerWechatId,wa.nickName,wg.createTime,wa.avatar,wa.alias,wg.avatar as groupAvatar')
|
||||
->page($page, $limit)
|
||||
->select();
|
||||
|
||||
// 优化:格式化时间,头像兜底
|
||||
$defaultGroupAvatar = '';
|
||||
$defaultAvatar = '';
|
||||
foreach ($list as &$item) {
|
||||
$item['createTime'] = $item['createTime'] ? date('Y-m-d H:i:s', $item['createTime']) : '';
|
||||
$item['groupAvatar'] = $item['groupAvatar'] ?: $defaultGroupAvatar;
|
||||
$item['avatar'] = $item['avatar'] ?: $defaultAvatar;
|
||||
}
|
||||
|
||||
return json(['code' => 200, 'msg' => '获取成功', 'data' => ['total' => $total, 'list' => $list]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取流量池列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getTrafficPoolList()
|
||||
{
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
$companyId = $this->request->userInfo['companyId'];
|
||||
|
||||
$baseQuery = Db::name('traffic_source_package')->alias('tsp')
|
||||
->where('tsp.isDel', 0)
|
||||
->whereIn('tsp.companyId', [$companyId, 0]);
|
||||
|
||||
if (!empty($keyword)) {
|
||||
$baseQuery->whereLike('tsp.name', '%' . $keyword . '%');
|
||||
}
|
||||
|
||||
$total = (clone $baseQuery)->count();
|
||||
|
||||
$list = $baseQuery
|
||||
->leftJoin('traffic_source_package_item tspi', 'tspi.packageId = tsp.id and tspi.isDel = 0')
|
||||
->field('tsp.id,tsp.name,tsp.description,tsp.pic,tsp.companyId,COUNT(tspi.id) as itemCount,max(tspi.createTime) as latestImportTime')
|
||||
->group('tsp.id')
|
||||
->order('tsp.id', 'desc')
|
||||
->page($page, $limit)
|
||||
->select();
|
||||
|
||||
foreach ($list as &$item) {
|
||||
$item['latestImportTime'] = !empty($item['latestImportTime']) ? date('Y-m-d H:i:s', $item['latestImportTime']) : '';
|
||||
}
|
||||
unset($item);
|
||||
|
||||
return json(['code' => 200, 'msg' => '获取成功', 'data' => ['total' => $total, 'list' => $list]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取账号列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getAccountList()
|
||||
{
|
||||
$companyId = $this->request->userInfo['companyId'];
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$query = Db::table('s2_company_account')
|
||||
->alias('a')
|
||||
->where(['a.departmentId' => $companyId, 'a.status' => 0])
|
||||
->whereNotLike('a.userName', '%_offline%')
|
||||
->whereNotLike('a.userName', '%_delete%');
|
||||
|
||||
$total = $query->count();
|
||||
$list = $query->field('a.id,a.userName,a.realName,a.nickname,a.memo')
|
||||
->page($page, $limit)
|
||||
->select();
|
||||
|
||||
return json(['code' => 200, 'msg' => '获取成功', 'data' => ['total' => $total, 'list' => $list]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取京东联盟导购媒体
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getJdSocialMedia()
|
||||
{
|
||||
$data = Db::name('jd_social_media')->order('id DESC')->select();
|
||||
return json(['code' => 200, 'msg' => '获取成功', 'data' => $data]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取京东联盟广告位
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getJdPromotionSite()
|
||||
{
|
||||
$id = $this->request->param('id', '');
|
||||
if (empty($id)) {
|
||||
return json(['code' => 500, 'msg' => '参数缺失']);
|
||||
}
|
||||
|
||||
$data = Db::name('jd_promotion_site')->where('jdSocialMediaId', $id)->order('id DESC')->select();
|
||||
return json(['code' => 200, 'msg' => '获取成功', 'data' => $data]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 京东转链-京推推
|
||||
* @param string $content
|
||||
* @param string $positionid
|
||||
* @return string
|
||||
*/
|
||||
public function changeLink($content = '', $positionid = '')
|
||||
{
|
||||
$unionId = Env::get('jd.unionId', '');
|
||||
$jttAppId = Env::get('jd.jttAppId', '');
|
||||
$appKey = Env::get('jd.appKey', '');
|
||||
$apiUrl = Env::get('jd.apiUrl', '');
|
||||
|
||||
$content = !empty($content) ? $content : $this->request->param('content', '');
|
||||
$positionid = !empty($positionid) ? $positionid : $this->request->param('positionid', '');
|
||||
|
||||
if (empty($content)) {
|
||||
return json_encode(['code' => 500, 'msg' => '转链的内容为空']);
|
||||
}
|
||||
|
||||
// 验证是否包含链接
|
||||
if (!$this->containsLink($content)) {
|
||||
return json_encode(['code' => 500, 'msg' => '内容中未检测到有效链接']);
|
||||
}
|
||||
|
||||
if (empty($unionId) || empty($jttAppId) || empty($appKey) || empty($apiUrl)) {
|
||||
return json_encode(['code' => 500, 'msg' => '参数缺失']);
|
||||
}
|
||||
$params = [
|
||||
'unionid' => $unionId,
|
||||
'content' => $content,
|
||||
'appid' => $jttAppId,
|
||||
'appkey' => $appKey,
|
||||
'v' => 'v2'
|
||||
];
|
||||
|
||||
if (!empty($positionid)) {
|
||||
$params['positionid'] = $positionid;
|
||||
}
|
||||
|
||||
$res = requestCurl($apiUrl, $params, 'GET', [], 'json');
|
||||
$res = json_decode($res, true);
|
||||
if (empty($res)) {
|
||||
return json_encode(['code' => 500, 'msg' => '未知错误']);
|
||||
}
|
||||
$result = $res['result'];
|
||||
if ($res['return'] == 0) {
|
||||
return json_encode(['code' => 200, 'data' => $result['chain_content'], 'msg' => $result['msg']]);
|
||||
} else {
|
||||
return json_encode(['code' => 500, 'msg' => $result['msg']]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证内容是否包含链接
|
||||
* @param string $content 要检测的内容
|
||||
* @return bool
|
||||
*/
|
||||
private function containsLink($content)
|
||||
{
|
||||
// 定义各种链接的正则表达式模式
|
||||
$patterns = [
|
||||
// HTTP/HTTPS链接
|
||||
'/https?:\/\/[^\s]+/i',
|
||||
// 京东商品链接
|
||||
'/item\.jd\.com\/\d+/i',
|
||||
// 京东短链接
|
||||
'/u\.jd\.com\/[a-zA-Z0-9]+/i',
|
||||
// 淘宝商品链接
|
||||
'/item\.taobao\.com\/item\.htm\?id=\d+/i',
|
||||
// 天猫商品链接
|
||||
'/detail\.tmall\.com\/item\.htm\?id=\d+/i',
|
||||
// 淘宝短链接
|
||||
'/m\.tb\.cn\/[a-zA-Z0-9]+/i',
|
||||
// 拼多多链接
|
||||
'/mobile\.yangkeduo\.com\/goods\.html\?goods_id=\d+/i',
|
||||
// 苏宁易购链接
|
||||
'/product\.suning\.com\/\d+\/\d+\.html/i',
|
||||
// 通用域名模式(包含常见电商域名)
|
||||
'/(?:jd|taobao|tmall|yangkeduo|suning|amazon|dangdang)\.com[^\s]*/i',
|
||||
// 通用短链接模式
|
||||
'/[a-zA-Z0-9-]+\.[a-zA-Z]{2,}\/[a-zA-Z0-9\-._~:\/?#\[\]@!$&\'()*+,;=]+/i'
|
||||
];
|
||||
|
||||
// 遍历所有模式进行匹配
|
||||
foreach ($patterns as $pattern) {
|
||||
if (preg_match($pattern, $content)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\workbench;
|
||||
|
||||
use think\Controller;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 工作台 - 联系人导入相关功能
|
||||
*/
|
||||
class WorkbenchImportContactController extends Controller
|
||||
{
|
||||
/**
|
||||
* 获取通讯录导入记录列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getImportContact()
|
||||
{
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$workbenchId = $this->request->param('workbenchId', 0);
|
||||
|
||||
$where = [
|
||||
['wici.workbenchId', '=', $workbenchId]
|
||||
];
|
||||
|
||||
// 查询发布记录
|
||||
$list = Db::name('workbench_import_contact_item')->alias('wici')
|
||||
->join('traffic_pool tp', 'tp.id = wici.poolId', 'left')
|
||||
->join('traffic_source tc', 'tc.identifier = tp.identifier', 'left')
|
||||
->join('wechat_account wa', 'wa.wechatId = tp.wechatId', 'left')
|
||||
->field([
|
||||
'wici.id',
|
||||
'wici.workbenchId',
|
||||
'wici.createTime',
|
||||
'tp.identifier',
|
||||
'tp.mobile',
|
||||
'tp.wechatId',
|
||||
'tc.name',
|
||||
'wa.nickName',
|
||||
'wa.avatar',
|
||||
'wa.alias',
|
||||
])
|
||||
->where($where)
|
||||
->order('tc.name DESC,wici.createTime DESC')
|
||||
->group('tp.identifier')
|
||||
->page($page, $limit)
|
||||
->select();
|
||||
|
||||
foreach ($list as &$item) {
|
||||
$item['createTime'] = date('Y-m-d H:i:s', $item['createTime']);
|
||||
}
|
||||
|
||||
// 获取总记录数
|
||||
$total = Db::name('workbench_import_contact_item')->alias('wici')
|
||||
->where($where)
|
||||
->count();
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'list' => $list,
|
||||
'total' => $total,
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\workbench;
|
||||
|
||||
use think\Controller;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 工作台 - 朋友圈同步相关功能
|
||||
*/
|
||||
class WorkbenchMomentsController extends Controller
|
||||
{
|
||||
/**
|
||||
* 获取朋友圈发布记录列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getMomentsRecords()
|
||||
{
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$workbenchId = $this->request->param('workbenchId', 0);
|
||||
|
||||
$where = [
|
||||
['wmsi.workbenchId', '=', $workbenchId]
|
||||
];
|
||||
|
||||
// 查询发布记录
|
||||
$list = Db::name('workbench_moments_sync_item')->alias('wmsi')
|
||||
->join('content_item ci', 'ci.id = wmsi.contentId', 'left')
|
||||
->join(['s2_wechat_account' => 'wa'], 'wa.id = wmsi.wechatAccountId', 'left')
|
||||
->field([
|
||||
'wmsi.id',
|
||||
'wmsi.workbenchId',
|
||||
'wmsi.createTime as publishTime',
|
||||
'ci.contentType',
|
||||
'ci.content',
|
||||
'ci.resUrls',
|
||||
'ci.urls',
|
||||
'wa.nickName as operatorName',
|
||||
'wa.avatar as operatorAvatar'
|
||||
])
|
||||
->where($where)
|
||||
->order('wmsi.createTime', 'desc')
|
||||
->page($page, $limit)
|
||||
->select();
|
||||
|
||||
foreach ($list as &$item) {
|
||||
$item['resUrls'] = json_decode($item['resUrls'], true);
|
||||
$item['urls'] = json_decode($item['urls'], true);
|
||||
}
|
||||
|
||||
|
||||
// 获取总记录数
|
||||
$total = Db::name('workbench_moments_sync_item')->alias('wmsi')
|
||||
->where($where)
|
||||
->count();
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'list' => $list,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'limit' => $limit
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取朋友圈发布统计
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getMomentsStats()
|
||||
{
|
||||
$workbenchId = $this->request->param('workbenchId', 0);
|
||||
if (empty($workbenchId)) {
|
||||
return json(['code' => 400, 'msg' => '参数错误']);
|
||||
}
|
||||
|
||||
// 获取今日数据
|
||||
$todayStart = strtotime(date('Y-m-d') . ' 00:00:00');
|
||||
$todayEnd = strtotime(date('Y-m-d') . ' 23:59:59');
|
||||
|
||||
$todayStats = Db::name('workbench_moments_sync_item')
|
||||
->where([
|
||||
['workbenchId', '=', $workbenchId],
|
||||
['createTime', 'between', [$todayStart, $todayEnd]]
|
||||
])
|
||||
->field([
|
||||
'COUNT(*) as total',
|
||||
'SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) as success',
|
||||
'SUM(CASE WHEN status = 2 THEN 1 ELSE 0 END) as failed'
|
||||
])
|
||||
->find();
|
||||
|
||||
// 获取总数据
|
||||
$totalStats = Db::name('workbench_moments_sync_item')
|
||||
->where('workbenchId', $workbenchId)
|
||||
->field([
|
||||
'COUNT(*) as total',
|
||||
'SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) as success',
|
||||
'SUM(CASE WHEN status = 2 THEN 1 ELSE 0 END) as failed'
|
||||
])
|
||||
->find();
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'today' => [
|
||||
'total' => intval($todayStats['total']),
|
||||
'success' => intval($todayStats['success']),
|
||||
'failed' => intval($todayStats['failed'])
|
||||
],
|
||||
'total' => [
|
||||
'total' => intval($totalStats['total']),
|
||||
'success' => intval($totalStats['success']),
|
||||
'failed' => intval($totalStats['failed'])
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\workbench;
|
||||
|
||||
use think\Controller;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 工作台 - 流量分发相关功能
|
||||
*/
|
||||
class WorkbenchTrafficController extends Controller
|
||||
{
|
||||
/**
|
||||
* 获取流量分发记录列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getTrafficDistributionRecords()
|
||||
{
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$workbenchId = $this->request->param('workbenchId', 0);
|
||||
|
||||
$where = [
|
||||
['wtdi.workbenchId', '=', $workbenchId]
|
||||
];
|
||||
|
||||
// 查询分发记录
|
||||
$list = Db::name('workbench_traffic_distribution_item')->alias('wtdi')
|
||||
->join(['s2_wechat_account' => 'wa'], 'wa.id = wtdi.wechatAccountId', 'left')
|
||||
->join(['s2_wechat_friend' => 'wf'], 'wf.id = wtdi.wechatFriendId', 'left')
|
||||
->field([
|
||||
'wtdi.id',
|
||||
'wtdi.workbenchId',
|
||||
'wtdi.wechatAccountId',
|
||||
'wtdi.wechatFriendId',
|
||||
'wtdi.createTime as distributeTime',
|
||||
'wtdi.status',
|
||||
'wtdi.errorMsg',
|
||||
'wa.nickName as operatorName',
|
||||
'wa.avatar as operatorAvatar',
|
||||
'wf.nickName as friendName',
|
||||
'wf.avatar as friendAvatar',
|
||||
'wf.gender',
|
||||
'wf.province',
|
||||
'wf.city'
|
||||
])
|
||||
->where($where)
|
||||
->order('wtdi.createTime', 'desc')
|
||||
->page($page, $limit)
|
||||
->select();
|
||||
|
||||
// 处理数据
|
||||
foreach ($list as &$item) {
|
||||
// 处理时间格式
|
||||
$item['distributeTime'] = date('Y-m-d H:i:s', $item['distributeTime']);
|
||||
|
||||
// 处理性别
|
||||
$genderMap = [
|
||||
0 => '未知',
|
||||
1 => '男',
|
||||
2 => '女'
|
||||
];
|
||||
$item['genderText'] = $genderMap[$item['gender']] ?? '未知';
|
||||
|
||||
// 处理状态文字
|
||||
$statusMap = [
|
||||
0 => '待分发',
|
||||
1 => '分发成功',
|
||||
2 => '分发失败'
|
||||
];
|
||||
$item['statusText'] = $statusMap[$item['status']] ?? '未知状态';
|
||||
}
|
||||
|
||||
// 获取总记录数
|
||||
$total = Db::name('workbench_traffic_distribution_item')->alias('wtdi')
|
||||
->where($where)
|
||||
->count();
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'list' => $list,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'limit' => $limit
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取流量分发统计
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getTrafficDistributionStats()
|
||||
{
|
||||
$workbenchId = $this->request->param('workbenchId', 0);
|
||||
if (empty($workbenchId)) {
|
||||
return json(['code' => 400, 'msg' => '参数错误']);
|
||||
}
|
||||
|
||||
// 获取今日数据
|
||||
$todayStart = strtotime(date('Y-m-d') . ' 00:00:00');
|
||||
$todayEnd = strtotime(date('Y-m-d') . ' 23:59:59');
|
||||
|
||||
$todayStats = Db::name('workbench_traffic_distribution_item')
|
||||
->where([
|
||||
['workbenchId', '=', $workbenchId],
|
||||
['createTime', 'between', [$todayStart, $todayEnd]]
|
||||
])
|
||||
->field([
|
||||
'COUNT(*) as total',
|
||||
'SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) as success',
|
||||
'SUM(CASE WHEN status = 2 THEN 1 ELSE 0 END) as failed'
|
||||
])
|
||||
->find();
|
||||
|
||||
// 获取总数据
|
||||
$totalStats = Db::name('workbench_traffic_distribution_item')
|
||||
->where('workbenchId', $workbenchId)
|
||||
->field([
|
||||
'COUNT(*) as total',
|
||||
'SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) as success',
|
||||
'SUM(CASE WHEN status = 2 THEN 1 ELSE 0 END) as failed'
|
||||
])
|
||||
->find();
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'today' => [
|
||||
'total' => intval($todayStats['total']),
|
||||
'success' => intval($todayStats['success']),
|
||||
'failed' => intval($todayStats['failed'])
|
||||
],
|
||||
'total' => [
|
||||
'total' => intval($totalStats['total']),
|
||||
'success' => intval($totalStats['success']),
|
||||
'failed' => intval($totalStats['failed'])
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取流量分发详情
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getTrafficDistributionDetail()
|
||||
{
|
||||
$id = $this->request->param('id', 0);
|
||||
if (empty($id)) {
|
||||
return json(['code' => 400, 'msg' => '参数错误']);
|
||||
}
|
||||
|
||||
$detail = Db::name('workbench_traffic_distribution_item')->alias('wtdi')
|
||||
->join(['s2_wechat_account' => 'wa'], 'wa.id = wtdi.wechatAccountId', 'left')
|
||||
->join(['s2_wechat_friend' => 'wf'], 'wf.id = wtdi.wechatFriendId', 'left')
|
||||
->field([
|
||||
'wtdi.id',
|
||||
'wtdi.workbenchId',
|
||||
'wtdi.wechatAccountId',
|
||||
'wtdi.wechatFriendId',
|
||||
'wtdi.createTime as distributeTime',
|
||||
'wtdi.status',
|
||||
'wtdi.errorMsg',
|
||||
'wa.nickName as operatorName',
|
||||
'wa.avatar as operatorAvatar',
|
||||
'wf.nickName as friendName',
|
||||
'wf.avatar as friendAvatar',
|
||||
'wf.gender',
|
||||
'wf.province',
|
||||
'wf.city',
|
||||
'wf.signature',
|
||||
'wf.remark'
|
||||
])
|
||||
->where('wtdi.id', $id)
|
||||
->find();
|
||||
|
||||
if (empty($detail)) {
|
||||
return json(['code' => 404, 'msg' => '记录不存在']);
|
||||
}
|
||||
|
||||
// 处理数据
|
||||
$detail['distributeTime'] = date('Y-m-d H:i:s', $detail['distributeTime']);
|
||||
|
||||
// 处理性别
|
||||
$genderMap = [
|
||||
0 => '未知',
|
||||
1 => '男',
|
||||
2 => '女'
|
||||
];
|
||||
$detail['genderText'] = $genderMap[$detail['gender']] ?? '未知';
|
||||
|
||||
// 处理状态文字
|
||||
$statusMap = [
|
||||
0 => '待分发',
|
||||
1 => '分发成功',
|
||||
2 => '分发失败'
|
||||
];
|
||||
$detail['statusText'] = $statusMap[$detail['status']] ?? '未知状态';
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => $detail
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建流量分发计划
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function createTrafficPlan()
|
||||
{
|
||||
$param = $this->request->post();
|
||||
Db::startTrans();
|
||||
try {
|
||||
// 1. 创建主表
|
||||
$planId = Db::name('ck_workbench')->insertGetId([
|
||||
'name' => $param['name'],
|
||||
'type' => 5, // TYPE_TRAFFIC_DISTRIBUTION
|
||||
'status' => 1,
|
||||
'autoStart' => $param['autoStart'] ?? 0,
|
||||
'userId' => $this->request->userInfo['id'],
|
||||
'companyId' => $this->request->userInfo['companyId'],
|
||||
'createTime' => time(),
|
||||
'updateTime' => time()
|
||||
]);
|
||||
// 2. 创建扩展表
|
||||
Db::name('ck_workbench_traffic_config')->insert([
|
||||
'workbenchId' => $planId,
|
||||
'distributeType' => $param['distributeType'],
|
||||
'maxPerDay' => $param['maxPerDay'],
|
||||
'timeType' => $param['timeType'],
|
||||
'startTime' => $param['startTime'],
|
||||
'endTime' => $param['endTime'],
|
||||
'targets' => json_encode($param['targets'], JSON_UNESCAPED_UNICODE),
|
||||
'pools' => json_encode($param['poolGroups'], JSON_UNESCAPED_UNICODE),
|
||||
'createTime' => time(),
|
||||
'updateTime' => time()
|
||||
]);
|
||||
Db::commit();
|
||||
return json(['code' => 200, 'msg' => '创建成功']);
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return json(['code' => 500, 'msg' => '创建失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取流量列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getTrafficList()
|
||||
{
|
||||
$companyId = $this->request->userInfo['companyId'];
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
$workbenchId = $this->request->param('workbenchId', '');
|
||||
$isRecycle = $this->request->param('isRecycle', '');
|
||||
if (empty($workbenchId)) {
|
||||
return json(['code' => 400, 'msg' => '参数错误']);
|
||||
}
|
||||
|
||||
$workbench = Db::name('workbench')->where(['id' => $workbenchId, 'isDel' => 0, 'companyId' => $companyId, 'type' => 5])->find();
|
||||
|
||||
if (empty($workbench)) {
|
||||
return json(['code' => 400, 'msg' => '该任务不存在或已删除']);
|
||||
}
|
||||
$query = Db::name('workbench_traffic_config_item')->alias('wtc')
|
||||
->join(['s2_wechat_friend' => 'wf'], 'wtc.wechatFriendId = wf.id')
|
||||
->join('users u', 'wtc.wechatAccountId = u.s2_accountId', 'left')
|
||||
->field([
|
||||
'wtc.id', 'wtc.isRecycle', 'wtc.isRecycle', 'wtc.createTime','wtc.recycleTime',
|
||||
'wf.wechatId', 'wf.alias', 'wf.nickname', 'wf.avatar', 'wf.gender', 'wf.phone',
|
||||
'u.account', 'u.username'
|
||||
])
|
||||
->where(['wtc.workbenchId' => $workbenchId])
|
||||
->order('wtc.id DESC');
|
||||
|
||||
if (!empty($keyword)) {
|
||||
$query->where('wf.wechatId|wf.alias|wf.nickname|wf.phone|u.account|u.username', 'like', '%' . $keyword . '%');
|
||||
}
|
||||
|
||||
if ($isRecycle != '' || $isRecycle != null) {
|
||||
$query->where('isRecycle',$isRecycle);
|
||||
}
|
||||
|
||||
$total = $query->count();
|
||||
$list = $query->page($page, $limit)->select();
|
||||
|
||||
foreach ($list as &$item) {
|
||||
$item['createTime'] = date('Y-m-d H:i:s', $item['createTime']);
|
||||
$item['recycleTime'] = date('Y-m-d H:i:s', $item['recycleTime']);
|
||||
}
|
||||
unset($item);
|
||||
|
||||
$data = [
|
||||
'total' => $total,
|
||||
'list' => $list,
|
||||
];
|
||||
|
||||
return json(['code' => 200, 'msg' => '获取成功', 'data' => $data]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user