diff --git a/.gitignore b/.gitignore index df8964e0..fca0d36b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,11 @@ Store_vue/node_modules/ Cunkebao/.specstory/ *.cursorindexingignore Server/.specstory/ +Server/thinkphp/ Store_vue/.specstory/ Store_vue/unpackage/ Store_vue/.vscode/ SuperAdmin/.specstory/ Cunkebao/dist Touchkebao/.specstory/ +Serverruntime/ diff --git a/Cunkebao/pnpm-lock.yaml b/Cunkebao/pnpm-lock.yaml index 818ed7d7..99f410fa 100644 --- a/Cunkebao/pnpm-lock.yaml +++ b/Cunkebao/pnpm-lock.yaml @@ -26,9 +26,6 @@ importers: dayjs: specifier: ^1.11.13 version: 1.11.13 - dexie: - specifier: ^4.2.0 - version: 4.2.0 echarts: specifier: ^5.6.0 version: 5.6.0 @@ -1070,9 +1067,6 @@ packages: engines: {node: '>=0.10'} hasBin: true - dexie@4.2.0: - resolution: {integrity: sha512-OSeyyWOUetDy9oFWeddJgi83OnRA3hSFh3RrbltmPgqHszE9f24eUCVLI4mPg0ifsWk0lQTdnS+jyGNrPMvhDA==} - dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -3410,8 +3404,6 @@ snapshots: detect-libc@1.0.3: optional: true - dexie@4.2.0: {} - dir-glob@3.0.1: dependencies: path-type: 4.0.0 diff --git a/Moncter/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/InputMessage/index.module.scss b/Moncter/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/InputMessage/index.module.scss deleted file mode 100644 index b8085ce9..00000000 --- a/Moncter/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/InputMessage/index.module.scss +++ /dev/null @@ -1,147 +0,0 @@ -.chatFooter { - background: #f7f7f7; - border-top: 1px solid #e1e1e1; - padding: 0; - height: auto; - border-radius: 8px; -} - -.inputContainer { - padding: 8px 12px; - display: flex; - flex-direction: column; - gap: 6px; -} - -.inputToolbar { - display: flex; - align-items: center; - padding: 4px 0; -} - -.leftTool { - display: flex; - gap: 4px; - align-items: center; -} - -.toolbarButton { - width: 28px; - height: 28px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 4px; - color: #666; - font-size: 16px; - transition: all 0.15s; - border: none; - background: transparent; - - &:hover { - background: #e6e6e6; - color: #333; - } - - &:active { - background: #d9d9d9; - } -} - -.inputArea { - display: flex; - flex-direction: column; - padding: 4px 0; -} - -.inputWrapper { - border: 1px solid #d1d1d1; - border-radius: 4px; - background: #fff; - overflow: hidden; - - &:focus-within { - border-color: #07c160; - } -} - -.messageInput { - width: 100%; - border: none; - resize: none; - font-size: 13px; - line-height: 1.4; - padding: 8px 10px; - background: transparent; - - &:focus { - box-shadow: none; - outline: none; - } - - &::placeholder { - color: #b3b3b3; - } -} - -.sendButtonArea { - padding: 8px 10px; - display: flex; - justify-content: flex-end; - gap: 8px; -} - -.sendButton { - height: 32px; - border-radius: 4px; - font-weight: normal; - min-width: 60px; - font-size: 13px; - background: #07c160; - border-color: #07c160; - - &:hover { - background: #06ad56; - border-color: #06ad56; - } - - &:active { - background: #059748; - border-color: #059748; - } - - &:disabled { - background: #b3b3b3; - border-color: #b3b3b3; - opacity: 1; - } -} - -.hintButton { - border: none; - background: transparent; - color: #666; - font-size: 12px; - - &:hover { - color: #333; - } -} - -.inputHint { - font-size: 11px; - color: #999; - text-align: right; - margin-top: 2px; -} - -@media (max-width: 768px) { - .inputToolbar { - flex-wrap: wrap; - gap: 8px; - } - - .sendButtonArea { - justify-content: space-between; - } -} diff --git a/Moncter/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.module.scss b/Moncter/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.module.scss deleted file mode 100644 index fee50c0a..00000000 --- a/Moncter/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.module.scss +++ /dev/null @@ -1,265 +0,0 @@ -.stepContent { - .stepHeader { - margin-bottom: 20px; - - h3 { - font-size: 18px; - font-weight: 600; - color: #1a1a1a; - margin: 0 0 8px 0; - } - - p { - font-size: 14px; - color: #666; - margin: 0; - } - } -} - -.step3Content { - display: flex; - gap: 24px; - align-items: flex-start; - - .leftColumn { - flex: 1; - display: flex; - flex-direction: column; - gap: 20px; - } - - .rightColumn { - width: 400px; - flex: 1; - display: flex; - flex-direction: column; - gap: 20px; - } - - .messagePreview { - border: 2px dashed #52c41a; - border-radius: 8px; - padding: 20px; - background: #f6ffed; - - .previewTitle { - font-size: 14px; - color: #52c41a; - font-weight: 500; - margin-bottom: 12px; - } - - .messageBubble { - min-height: 60px; - padding: 12px; - background: #fff; - border-radius: 6px; - color: #666; - font-size: 14px; - line-height: 1.6; - - .currentEditingLabel { - font-size: 12px; - color: #999; - margin-bottom: 8px; - } - - .messageText { - color: #333; - white-space: pre-wrap; - word-break: break-word; - } - } - } - - .savedScriptGroups { - .scriptGroupTitle { - font-size: 14px; - font-weight: 500; - color: #333; - margin-bottom: 12px; - } - - .scriptGroupItem { - border: 1px solid #e8e8e8; - border-radius: 8px; - padding: 12px; - margin-bottom: 12px; - background: #fff; - - .scriptGroupHeader { - display: flex; - justify-content: space-between; - align-items: center; - - .scriptGroupLeft { - display: flex; - align-items: center; - gap: 8px; - flex: 1; - - :global(.ant-radio) { - margin-right: 4px; - } - - .scriptGroupName { - font-size: 14px; - font-weight: 500; - color: #333; - } - - .messageCount { - font-size: 12px; - color: #999; - margin-left: 8px; - } - } - - .scriptGroupActions { - display: flex; - gap: 4px; - - .actionButton { - padding: 4px; - color: #666; - - &:hover { - color: #1890ff; - } - } - } - } - - .scriptGroupContent { - margin-top: 8px; - padding-top: 8px; - border-top: 1px solid #f0f0f0; - font-size: 13px; - color: #666; - } - } - } - - .messageInputArea { - .messageInput { - margin-bottom: 12px; - } - - .attachmentButtons { - display: flex; - gap: 8px; - margin-bottom: 12px; - } - - .aiRewriteSection { - display: flex; - align-items: center; - margin-bottom: 8px; - } - - .messageHint { - font-size: 12px; - color: #999; - } - } - - .settingsPanel { - border: 1px solid #e8e8e8; - border-radius: 8px; - padding: 20px; - background: #fafafa; - - .settingsTitle { - font-size: 14px; - font-weight: 500; - color: #1a1a1a; - margin-bottom: 16px; - } - - .settingItem { - margin-bottom: 20px; - - &:last-child { - margin-bottom: 0; - } - - .settingLabel { - font-size: 14px; - font-weight: 500; - color: #1a1a1a; - margin-bottom: 12px; - } - - .settingControl { - display: flex; - align-items: center; - gap: 8px; - - span { - font-size: 14px; - color: #666; - min-width: 80px; - } - } - } - } - - .tagSection { - .settingLabel { - font-size: 14px; - font-weight: 500; - color: #1a1a1a; - margin-bottom: 12px; - } - } - - .pushPreview { - border: 1px solid #e8e8e8; - border-radius: 8px; - padding: 20px; - background: #f0f7ff; - - .previewTitle { - font-size: 14px; - font-weight: 500; - color: #1a1a1a; - margin-bottom: 12px; - } - - ul { - list-style: none; - padding: 0; - margin: 0; - - li { - font-size: 14px; - color: #666; - line-height: 1.8; - } - } - } -} - -@media (max-width: 1200px) { - .step3Content { - .rightColumn { - width: 350px; - } - } -} - -@media (max-width: 768px) { - .step3Content { - flex-direction: column; - - .leftColumn { - width: 100%; - } - - .rightColumn { - width: 100%; - } - } -} - diff --git a/Moncter/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.tsx b/Moncter/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.tsx deleted file mode 100644 index 082831d5..00000000 --- a/Moncter/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import ContentSelection from "@/components/ContentSelection"; -import { ContentItem } from "@/components/ContentSelection/data"; -import InputMessage from "./InputMessage/InputMessage"; -import styles from "./index.module.scss"; - -interface StepSendMessageProps { diff --git a/Moncter/src/pages/pc/ckbox/weChat/api.ts b/Moncter/src/pages/pc/ckbox/weChat/api.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/Moncter/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/index.tsx b/Moncter/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/index.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/Server/application/api/controller/WebSocketController.php b/Server/application/api/controller/WebSocketController.php index 773cbb3d..b9b8a860 100644 --- a/Server/application/api/controller/WebSocketController.php +++ b/Server/application/api/controller/WebSocketController.php @@ -457,22 +457,16 @@ class WebSocketController extends BaseController // 构建请求参数 $params = [ - "cmdType" => 'CmdDownloadMomentImagesResult', + "cmdType" => 'CmdDownloadMomentImages', "snsId" => $data['snsId'], "urls" => $data['snsUrls'], "wechatAccountId" => $data['wechatAccountId'], "seq" => time(), ]; - // 记录请求日志 Log::info('获取朋友圈资源链接请求:' . json_encode($params, 256)); - // 发送请求 - $this->client->send(json_encode($params)); - - // 接收响应 - $response = $this->client->receive(); - $message = json_decode($response, true); + $message = $this->sendMessage($params); if (empty($message)) { return json_encode(['code' => 500, 'msg' => '获取朋友圈资源链接失败']); @@ -558,15 +552,17 @@ class WebSocketController extends BaseController $dataToSave['create_time'] = time(); $res = WechatMoments::create($dataToSave); } - // // 获取资源链接 - // if(empty($momentEntity['resUrls']) && !empty($momentEntity['urls'])){ - // $snsData = [ - // 'snsId' => $moment['snsId'], - // 'snsUrls' => $momentEntity['urls'], - // 'wechatAccountId' => $wechatAccountId, - // ]; - // $this->getMomentSourceRealUrl($snsData); - // } + + + // 获取资源链接 + if(empty($momentEntity['resUrls']) && !empty($momentEntity['urls']) && $moment['type'] == 1) { + $snsData = [ + 'snsId' => $moment['snsId'], + 'snsUrls' => $momentEntity['urls'], + 'wechatAccountId' => $wechatAccountId, + ]; + $this->getMomentSourceRealUrl($snsData); + } } //Log::write('朋友圈数据已存入数据库,共' . count($momentList) . '条'); diff --git a/Server/application/api/model/WechatAccountModel.php b/Server/application/api/model/WechatAccountModel.php index 2f72b908..919da478 100644 --- a/Server/application/api/model/WechatAccountModel.php +++ b/Server/application/api/model/WechatAccountModel.php @@ -7,5 +7,27 @@ use think\Model; class WechatAccountModel extends Model { // 设置表名 - protected $table = 's2_wechat_account'; + protected $table = 's2_wechat_account'; + + // 定义字段类型 + protected $type = [ + 'healthScore' => 'integer', + 'baseScore' => 'integer', + 'dynamicScore' => 'integer', + 'isModifiedAlias' => 'integer', + 'frequentCount' => 'integer', + 'consecutiveNoFrequentDays' => 'integer', + 'lastFrequentTime' => 'integer', + 'lastNoFrequentTime' => 'integer', + 'scoreUpdateTime' => 'integer', + ]; + + // 允许批量赋值的字段 + protected $field = [ + 'id', 'wechatId', 'alias', 'nickname', 'avatar', 'gender', 'region', 'signature', + 'healthScore', 'baseScore', 'dynamicScore', 'isModifiedAlias', + 'lastFrequentTime', 'frequentCount', 'lastNoFrequentTime', + 'consecutiveNoFrequentDays', 'scoreUpdateTime', + 'createTime', 'updateTime', 'status', 'isDeleted' + ]; } \ No newline at end of file diff --git a/Server/application/command.php b/Server/application/command.php index f1d4d72b..b5c4d0e5 100644 --- a/Server/application/command.php +++ b/Server/application/command.php @@ -39,4 +39,7 @@ return [ 'workbench:groupCreate' => 'app\command\WorkbenchGroupCreateCommand', // 工作台群创建任务 'workbench:import-contact' => 'app\command\WorkbenchImportContactCommand', // 工作台通讯录导入任务 'kf:notice' => 'app\command\KfNoticeCommand', // 客服端消息通知 + + 'wechat:calculate-score' => 'app\command\CalculateWechatAccountScoreCommand', // 统一计算微信账号健康分 + 'wechat:update-score' => 'app\command\UpdateWechatAccountScoreCommand', // 更新微信账号评分记录 ]; diff --git a/Server/application/command/CalculateWechatAccountScoreCommand.php b/Server/application/command/CalculateWechatAccountScoreCommand.php new file mode 100644 index 00000000..aeb73784 --- /dev/null +++ b/Server/application/command/CalculateWechatAccountScoreCommand.php @@ -0,0 +1,558 @@ +setName('wechat:calculate-score') + ->setDescription('统一计算微信账号健康分(包含初始化、更新评分记录、批量计算)') + ->addOption('only-init', null, \think\console\input\Option::VALUE_NONE, '仅执行初始化步骤') + ->addOption('only-update', null, \think\console\input\Option::VALUE_NONE, '仅执行更新评分记录步骤') + ->addOption('only-batch', null, \think\console\input\Option::VALUE_NONE, '仅执行批量更新健康分步骤') + ->addOption('account-id', 'a', \think\console\input\Option::VALUE_OPTIONAL, '指定账号ID,仅处理该账号') + ->addOption('batch-size', 'b', \think\console\input\Option::VALUE_OPTIONAL, '批处理大小', 50) + ->addOption('force-recalculate', 'f', \think\console\input\Option::VALUE_NONE, '强制重新计算基础分'); + } + + /** + * 执行命令 + * + * @param Input $input 输入对象 + * @param Output $output 输出对象 + * @return int 命令执行状态码(0表示成功) + */ + protected function execute(Input $input, Output $output) + { + // 解析命令行参数 + $onlyInit = $input->getOption('only-init'); + $onlyUpdate = $input->getOption('only-update'); + $onlyBatch = $input->getOption('only-batch'); + $accountId = $input->getOption('account-id'); + $batchSize = (int)$input->getOption('batch-size'); + $forceRecalculate = $input->getOption('force-recalculate'); + + // 参数验证 + if ($batchSize <= 0) { + $batchSize = 50; // 默认批处理大小 + } + + // 显示执行参数 + $output->writeln("=========================================="); + $output->writeln("开始统一计算微信账号健康分..."); + $output->writeln("=========================================="); + + if ($accountId) { + $output->writeln("指定账号ID: {$accountId}"); + } + + if ($onlyInit) { + $output->writeln("仅执行初始化步骤"); + } elseif ($onlyUpdate) { + $output->writeln("仅执行更新评分记录步骤"); + } elseif ($onlyBatch) { + $output->writeln("仅执行批量更新健康分步骤"); + } + + if ($forceRecalculate) { + $output->writeln("强制重新计算基础分"); + } + + $output->writeln("批处理大小: {$batchSize}"); + + // 记录命令开始执行的日志 + Log::info('开始执行微信账号健康分计算命令', [ + 'accountId' => $accountId, + 'onlyInit' => $onlyInit ? 'true' : 'false', + 'onlyUpdate' => $onlyUpdate ? 'true' : 'false', + 'onlyBatch' => $onlyBatch ? 'true' : 'false', + 'batchSize' => $batchSize, + 'forceRecalculate' => $forceRecalculate ? 'true' : 'false' + ]); + + $startTime = time(); + + try { + // 实例化服务 + $service = new WechatAccountHealthScoreService(); + } catch (\Exception $e) { + $errorMsg = "实例化WechatAccountHealthScoreService失败: " . $e->getMessage(); + $output->writeln("{$errorMsg}"); + Log::error($errorMsg); + return 1; // 返回非零状态码表示失败 + } + + // 初始化统计数据 + $initStats = ['success' => 0, 'failed' => 0, 'errors' => []]; + $updateStats = ['total' => 0]; + $batchStats = ['success' => 0, 'failed' => 0, 'errors' => []]; + + try { + // 步骤1: 初始化未计算基础分的账号 + if (!$onlyUpdate && !$onlyBatch) { + $output->writeln("\n[步骤1] 初始化未计算基础分的账号..."); + Log::info('[步骤1] 开始初始化未计算基础分的账号'); + $initStats = $this->initUncalculatedAccounts($service, $output, $accountId, $batchSize); + $output->writeln("初始化完成:成功 {$initStats['success']} 条,失败 {$initStats['failed']} 条"); + Log::info("初始化完成:成功 {$initStats['success']} 条,失败 {$initStats['failed']} 条"); + } + + // 步骤2: 更新评分记录(根据wechatId和alias不一致情况) + if (!$onlyInit && !$onlyBatch) { + $output->writeln("\n[步骤2] 更新评分记录(根据wechatId和alias不一致情况)..."); + Log::info('[步骤2] 开始更新评分记录(根据wechatId和alias不一致情况)'); + $updateStats = $this->updateScoreRecords($service, $output, $accountId, $batchSize); + $output->writeln("更新完成:处理了 {$updateStats['total']} 条记录"); + Log::info("更新评分记录完成:处理了 {$updateStats['total']} 条记录"); + } + + // 步骤3: 批量更新健康分(只更新动态分,不重新计算基础分) + if (!$onlyInit && !$onlyUpdate) { + $output->writeln("\n[步骤3] 批量更新健康分(只更新动态分)..."); + Log::info('[步骤3] 开始批量更新健康分(只更新动态分)'); + $batchStats = $this->batchUpdateHealthScore($service, $output, $accountId, $batchSize, $forceRecalculate); + $output->writeln("批量更新完成:成功 {$batchStats['success']} 条,失败 {$batchStats['failed']} 条"); + Log::info("批量更新健康分完成:成功 {$batchStats['success']} 条,失败 {$batchStats['failed']} 条"); + } + + // 统计信息 + $endTime = time(); + $duration = $endTime - $startTime; + + $output->writeln("\n=========================================="); + $output->writeln("任务完成!"); + $output->writeln("=========================================="); + $output->writeln("总耗时: {$duration} 秒"); + $output->writeln("初始化: 成功 {$initStats['success']} 条,失败 {$initStats['failed']} 条"); + $output->writeln("更新评分记录: {$updateStats['total']} 条"); + $output->writeln("批量更新: 成功 {$batchStats['success']} 条,失败 {$batchStats['failed']} 条"); + + // 记录命令执行完成的日志 + Log::info("微信账号健康分计算命令执行完成,总耗时: {$duration} 秒," . + "初始化: 成功 {$initStats['success']} 条,失败 {$initStats['failed']} 条," . + "更新评分记录: {$updateStats['total']} 条," . + "批量更新: 成功 {$batchStats['success']} 条,失败 {$batchStats['failed']} 条"); + + if (!empty($initStats['errors'])) { + $output->writeln("\n初始化错误详情:"); + Log::warning("初始化阶段出现 " . count($initStats['errors']) . " 个错误"); + + foreach (array_slice($initStats['errors'], 0, 10) as $error) { + $output->writeln(" 账号ID {$error['accountId']}: {$error['error']}"); + Log::error("初始化错误 - 账号ID {$error['accountId']}: {$error['error']}"); + } + + if (count($initStats['errors']) > 10) { + $output->writeln(" ... 还有 " . (count($initStats['errors']) - 10) . " 个错误"); + Log::warning("初始化错误过多,只记录前10个,还有 " . (count($initStats['errors']) - 10) . " 个错误未显示"); + } + } + + if (!empty($batchStats['errors'])) { + $output->writeln("\n批量更新错误详情:"); + Log::warning("批量更新阶段出现 " . count($batchStats['errors']) . " 个错误"); + + foreach (array_slice($batchStats['errors'], 0, 10) as $error) { + $output->writeln(" 账号ID {$error['accountId']}: {$error['error']}"); + Log::error("批量更新错误 - 账号ID {$error['accountId']}: {$error['error']}"); + } + + if (count($batchStats['errors']) > 10) { + $output->writeln(" ... 还有 " . (count($batchStats['errors']) - 10) . " 个错误"); + Log::warning("批量更新错误过多,只记录前10个,还有 " . (count($batchStats['errors']) - 10) . " 个错误未显示"); + } + } + + } catch (\PDOException $e) { + // 数据库异常 + $errorMsg = "数据库操作失败: " . $e->getMessage(); + $output->writeln("\n数据库错误: " . $errorMsg . ""); + $output->writeln($e->getTraceAsString()); + + // 记录数据库错误 + Log::error("数据库错误: " . $errorMsg); + Log::error("错误堆栈: " . $e->getTraceAsString()); + + return 2; // 数据库错误状态码 + } catch (\Exception $e) { + // 一般异常 + $errorMsg = "命令执行失败: " . $e->getMessage(); + $output->writeln("\n错误: " . $errorMsg . ""); + $output->writeln($e->getTraceAsString()); + + // 记录严重错误 + Log::error($errorMsg); + Log::error("错误堆栈: " . $e->getTraceAsString()); + + return 1; // 一般错误状态码 + } catch (\Throwable $e) { + // 其他所有错误 + $errorMsg = "严重错误: " . $e->getMessage(); + $output->writeln("\n严重错误: " . $errorMsg . ""); + $output->writeln($e->getTraceAsString()); + + // 记录严重错误 + Log::critical($errorMsg); + Log::critical("错误堆栈: " . $e->getTraceAsString()); + + return 3; // 严重错误状态码 + } + + return 0; // 成功执行 + } + + /** + * 初始化未计算基础分的账号 + * + * @param WechatAccountHealthScoreService $service 健康分服务实例 + * @param Output $output 输出对象 + * @return array 处理结果统计 + * @throws \Exception 如果查询或处理过程中出现错误 + */ + private function initUncalculatedAccounts($service, $output, $accountId = null, $batchSize = 50) + { + $stats = [ + 'total' => 0, + 'success' => 0, + 'failed' => 0, + 'errors' => [] + ]; + + try { + // 获取所有未计算基础分的账号 + // 优化查询:使用索引字段,只查询必要的字段 + $query = Db::table(self::TABLE_WECHAT_ACCOUNT) + ->alias('a') + ->leftJoin([self::TABLE_WECHAT_ACCOUNT_SCORE => 's'], 's.accountId = a.id') + ->where('a.isDeleted', 0) + ->where(function($query) { + $query->whereNull('s.id') + ->whereOr('s.baseScoreCalculated', 0); + }); + + // 如果指定了账号ID,则只处理该账号 + if ($accountId) { + $query->where('a.id', $accountId); + } + + $accounts = $query->field('a.id, a.wechatId') // 只查询必要的字段 + ->select(); + } catch (\Exception $e) { + Log::error("查询未计算基础分的账号失败: " . $e->getMessage()); + throw new \Exception("查询未计算基础分的账号失败: " . $e->getMessage(), 0, $e); + } + + $stats['total'] = count($accounts); + + if ($stats['total'] == 0) { + $output->writeln("没有需要初始化的账号"); + Log::info("没有需要初始化的账号"); + return $stats; + } + + $output->writeln("找到 {$stats['total']} 个需要初始化的账号"); + Log::info("找到 {$stats['total']} 个需要初始化的账号"); + + // 优化批处理:使用传入的批处理大小 + $batches = array_chunk($accounts, $batchSize); + $batchCount = count($batches); + + Log::info("将分 {$batchCount} 批处理,每批 {$batchSize} 个账号"); + + foreach ($batches as $batchIndex => $batch) { + $batchStartTime = microtime(true); + $batchSuccessCount = 0; + $batchFailedCount = 0; + + foreach ($batch as $account) { + try { + $service->calculateAndUpdate($account['id']); + $stats['success']++; + $batchSuccessCount++; + + if ($stats['success'] % 20 == 0) { // 更频繁地显示进度 + $output->write("."); + Log::debug("已成功初始化 {$stats['success']} 个账号"); + } + } catch (\Exception $e) { + $stats['failed']++; + $batchFailedCount++; + $errorMsg = "初始化账号 {$account['id']} 失败: " . $e->getMessage(); + Log::error($errorMsg); + $stats['errors'][] = [ + 'accountId' => $account['id'], + 'error' => $e->getMessage() + ]; + } + } + + $batchEndTime = microtime(true); + $batchDuration = round($batchEndTime - $batchStartTime, 2); + + // 每批次完成后输出进度信息 + $output->writeln(" 批次 " . ($batchIndex + 1) . "/{$batchCount} 完成,耗时 {$batchDuration} 秒,成功 {$batchSuccessCount},失败 {$batchFailedCount}"); + Log::info("初始化批次 " . ($batchIndex + 1) . "/{$batchCount} 完成,耗时 {$batchDuration} 秒,成功 {$batchSuccessCount},失败 {$batchFailedCount}"); + } + + return $stats; + } + + /** + * 更新评分记录(根据wechatId和alias不一致情况) + * + * @param WechatAccountHealthScoreService $service 健康分服务实例 + * @param Output $output 输出对象 + * @return array 处理结果统计 + * @throws \Exception 如果查询或处理过程中出现错误 + */ + private function updateScoreRecords($service, $output, $accountId = null, $batchSize = 50) + { + $stats = ['total' => 0]; + + try { + // 优化查询:合并两次查询为一次,减少数据库访问次数 + $query = Db::table(self::TABLE_WECHAT_ACCOUNT) + ->where('isDeleted', 0) + ->where('wechatId', '<>', '') + ->where('alias', '<>', ''); + + // 如果指定了账号ID,则只处理该账号 + if ($accountId) { + $query->where('id', $accountId); + } + + $accounts = $query->field('id, wechatId, alias, IF(wechatId = alias, 0, 1) as isModifiedAlias') + ->select(); + + // 分类处理查询结果 + $inconsistentAccounts = []; + $consistentAccounts = []; + + foreach ($accounts as $account) { + if ($account['isModifiedAlias'] == 1) { + $inconsistentAccounts[] = $account; + } else { + $consistentAccounts[] = $account; + } + } + } catch (\Exception $e) { + Log::error("查询需要更新评分记录的账号失败: " . $e->getMessage()); + throw new \Exception("查询需要更新评分记录的账号失败: " . $e->getMessage(), 0, $e); + } + + $allAccounts = array_merge($inconsistentAccounts, $consistentAccounts); + $stats['total'] = count($allAccounts); + + if ($stats['total'] == 0) { + $output->writeln("没有需要更新的账号"); + Log::info("没有需要更新的评分记录"); + return $stats; + } + + $output->writeln("找到 {$stats['total']} 个需要更新的账号(不一致: " . count($inconsistentAccounts) . ",一致: " . count($consistentAccounts) . ")"); + Log::info("找到 {$stats['total']} 个需要更新的账号(不一致: " . count($inconsistentAccounts) . ",一致: " . count($consistentAccounts) . ")"); + + $updatedCount = 0; + + // 优化批处理:使用传入的批处理大小 + $batches = array_chunk($allAccounts, $batchSize); + $batchCount = count($batches); + + Log::info("将分 {$batchCount} 批更新评分记录,每批 {$batchSize} 个账号"); + + foreach ($batches as $batchIndex => $batch) { + $batchStartTime = microtime(true); + $batchUpdatedCount = 0; + + foreach ($batch as $account) { + $isModifiedAlias = isset($account['isModifiedAlias']) ? + ($account['isModifiedAlias'] == 1) : + in_array($account['id'], array_column($inconsistentAccounts, 'id')); + + $this->updateScoreRecord($account['id'], $isModifiedAlias, $service); + $updatedCount++; + $batchUpdatedCount++; + + if ($batchUpdatedCount % 20 == 0) { + $output->write("."); + } + } + + $batchEndTime = microtime(true); + $batchDuration = round($batchEndTime - $batchStartTime, 2); + + // 每批次完成后输出进度信息 + $output->writeln(" 批次 " . ($batchIndex + 1) . "/{$batchCount} 完成,耗时 {$batchDuration} 秒,更新 {$batchUpdatedCount} 条记录"); + Log::info("更新评分记录批次 " . ($batchIndex + 1) . "/{$batchCount} 完成,耗时 {$batchDuration} 秒,更新 {$batchUpdatedCount} 条记录"); + } + + if ($updatedCount > 0 && $updatedCount % 100 == 0) { + $output->writeln(""); + } + + return $stats; + } + + /** + * 批量更新健康分(只更新动态分) + * + * @param WechatAccountHealthScoreService $service 健康分服务实例 + * @param Output $output 输出对象 + * @return array 处理结果统计 + * @throws \Exception 如果查询或处理过程中出现错误 + */ + private function batchUpdateHealthScore($service, $output, $accountId = null, $batchSize = 50, $forceRecalculate = false) + { + try { + // 获取所有已计算基础分的账号 + // 优化查询:只查询必要的字段,使用索引字段 + $query = Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE) + ->where('baseScoreCalculated', 1); + + // 如果指定了账号ID,则只处理该账号 + if ($accountId) { + $query->where('accountId', $accountId); + } + + $accountIds = $query->column('accountId'); + } catch (\Exception $e) { + Log::error("查询需要批量更新健康分的账号失败: " . $e->getMessage()); + throw new \Exception("查询需要批量更新健康分的账号失败: " . $e->getMessage(), 0, $e); + } + + $total = count($accountIds); + + if ($total == 0) { + $output->writeln("没有需要更新的账号"); + Log::info("没有需要批量更新健康分的账号"); + return ['success' => 0, 'failed' => 0, 'errors' => []]; + } + + $output->writeln("找到 {$total} 个需要更新动态分的账号"); + Log::info("找到 {$total} 个需要更新动态分的账号"); + + // 使用传入的批处理大小和强制重新计算标志 + Log::info("使用批量大小 {$batchSize} 进行批量更新健康分,强制重新计算基础分: " . ($forceRecalculate ? 'true' : 'false')); + $stats = $service->batchCalculateAndUpdate($accountIds, $batchSize, $forceRecalculate); + + return $stats; + } + + /** + * 更新评分记录 + * + * @param int $accountId 账号ID + * @param bool $isModifiedAlias 是否已修改微信号 + * @param WechatAccountHealthScoreService $service 评分服务 + */ + /** + * 更新评分记录 + * + * @param int $accountId 账号ID + * @param bool $isModifiedAlias 是否已修改微信号 + * @param WechatAccountHealthScoreService $service 评分服务 + * @return bool 是否成功更新 + */ + private function updateScoreRecord($accountId, $isModifiedAlias, $service) + { + Log::debug("开始更新账号 {$accountId} 的评分记录,isModifiedAlias: " . ($isModifiedAlias ? 'true' : 'false')); + + try { + // 获取账号数据 - 只查询必要的字段 + $accountData = Db::table(self::TABLE_WECHAT_ACCOUNT) + ->where('id', $accountId) + ->field('id, wechatId, alias') // 只查询必要的字段 + ->find(); + + if (empty($accountData)) { + Log::warning("账号 {$accountId} 不存在,跳过更新评分记录"); + return false; + } + + // 确保评分记录存在 - 只查询必要的字段 + $scoreRecord = Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE) + ->where('accountId', $accountId) + ->field('accountId, baseScore, baseScoreCalculated, baseInfoScore, dynamicScore') // 只查询必要的字段 + ->find(); + + if (empty($scoreRecord)) { + // 如果记录不存在,创建并计算基础分 + Log::info("账号 {$accountId} 的评分记录不存在,创建并计算基础分"); + $service->calculateAndUpdate($accountId); + $scoreRecord = Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE) + ->where('accountId', $accountId) + ->find(); + } + + if (empty($scoreRecord)) { + Log::warning("账号 {$accountId} 的评分记录创建失败,跳过更新"); + return; + } + + // 更新isModifiedAlias字段 + $updateData = [ + 'isModifiedAlias' => $isModifiedAlias ? 1 : 0, + 'updateTime' => time() + ]; + + // 如果基础分已计算,需要更新基础信息分和基础分 + if ($scoreRecord['baseScoreCalculated']) { + $oldBaseInfoScore = $scoreRecord['baseInfoScore'] ?? 0; + $newBaseInfoScore = $isModifiedAlias ? 10 : 0; // 已修改微信号得10分 + + if ($oldBaseInfoScore != $newBaseInfoScore) { + $oldBaseScore = $scoreRecord['baseScore'] ?? 60; + $newBaseScore = $oldBaseScore - $oldBaseInfoScore + $newBaseInfoScore; + + $updateData['baseInfoScore'] = $newBaseInfoScore; + $updateData['baseScore'] = $newBaseScore; + + // 重新计算健康分 + $dynamicScore = $scoreRecord['dynamicScore'] ?? 0; + $healthScore = $newBaseScore + $dynamicScore; + $healthScore = max(0, min(100, $healthScore)); + $updateData['healthScore'] = $healthScore; + $updateData['maxAddFriendPerDay'] = (int)floor($healthScore * 0.2); + + Log::info("账号 {$accountId} 的基础信息分从 {$oldBaseInfoScore} 更新为 {$newBaseInfoScore}," . + "基础分从 {$oldBaseScore} 更新为 {$newBaseScore},健康分更新为 {$healthScore}"); + } + } else { + // 基础分未计算,只更新标记和基础信息分 + $updateData['baseInfoScore'] = $isModifiedAlias ? 10 : 0; + } + + $result = Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE) + ->where('accountId', $accountId) + ->update($updateData); + + Log::debug("账号 {$accountId} 的评分记录更新" . ($result !== false ? "成功" : "失败")); + + return $result !== false; + } catch (\Exception $e) { + Log::error("更新账号 {$accountId} 的评分记录失败: " . $e->getMessage()); + return false; + } + } +} + diff --git a/Server/application/command/UpdateWechatAccountScoreCommand.php b/Server/application/command/UpdateWechatAccountScoreCommand.php new file mode 100644 index 00000000..13ded8d4 --- /dev/null +++ b/Server/application/command/UpdateWechatAccountScoreCommand.php @@ -0,0 +1,168 @@ +setName('wechat:update-score') + ->setDescription('更新微信账号评分记录,根据wechatId和alias不一致情况更新isModifiedAlias字段(仅用于评分)'); + } + + protected function execute(Input $input, Output $output) + { + $output->writeln("开始更新微信账号评分记录..."); + + try { + // 1. 查找所有需要更新的账号 + $output->writeln("步骤1: 查找需要更新的账号..."); + + // 查找wechatId和alias不一致的账号 + $inconsistentAccounts = Db::table('s2_wechat_account') + ->where('isDeleted', 0) + ->where('wechatId', '<>', '') + ->where('alias', '<>', '') + ->whereRaw('wechatId != alias') + ->field('id, wechatId, alias') + ->select(); + + // 查找wechatId和alias一致的账号 + $consistentAccounts = Db::table('s2_wechat_account') + ->where('isDeleted', 0) + ->where('wechatId', '<>', '') + ->where('alias', '<>', '') + ->whereRaw('wechatId = alias') + ->field('id, wechatId, alias') + ->select(); + + $output->writeln("发现 " . count($inconsistentAccounts) . " 条不一致记录(已修改微信号)"); + $output->writeln("发现 " . count($consistentAccounts) . " 条一致记录(未修改微信号)"); + + // 2. 更新评分记录表中的isModifiedAlias字段 + $output->writeln("步骤2: 更新评分记录表..."); + $updatedCount = 0; + $healthScoreService = new WechatAccountHealthScoreService(); + + // 更新不一致的记录 + foreach ($inconsistentAccounts as $account) { + $this->updateScoreRecord($account['id'], true, $healthScoreService); + $updatedCount++; + } + + // 更新一致的记录 + foreach ($consistentAccounts as $account) { + $this->updateScoreRecord($account['id'], false, $healthScoreService); + $updatedCount++; + } + + $output->writeln("已更新 " . $updatedCount . " 条评分记录"); + + // 3. 重新计算健康分(只更新基础信息分,不重新计算基础分) + $output->writeln("步骤3: 重新计算健康分..."); + $allAccountIds = array_merge( + array_column($inconsistentAccounts, 'id'), + array_column($consistentAccounts, 'id') + ); + + if (!empty($allAccountIds)) { + $stats = $healthScoreService->batchCalculateAndUpdate($allAccountIds, 100, false); + $output->writeln("健康分计算完成:成功 " . $stats['success'] . " 条,失败 " . $stats['failed'] . " 条"); + + if (!empty($stats['errors'])) { + $output->writeln("错误详情:"); + foreach ($stats['errors'] as $error) { + $output->writeln(" 账号ID {$error['accountId']}: {$error['error']}"); + } + } + } + + $output->writeln("任务完成!"); + + } catch (\Exception $e) { + $output->writeln("错误: " . $e->getMessage()); + $output->writeln($e->getTraceAsString()); + } + } + + /** + * 更新评分记录 + * + * @param int $accountId 账号ID + * @param bool $isModifiedAlias 是否已修改微信号 + * @param WechatAccountHealthScoreService $service 评分服务 + */ + private function updateScoreRecord($accountId, $isModifiedAlias, $service) + { + // 获取或创建评分记录 + $accountData = Db::table('s2_wechat_account') + ->where('id', $accountId) + ->find(); + + if (empty($accountData)) { + return; + } + + // 确保评分记录存在 + $scoreRecord = Db::table('s2_wechat_account_score') + ->where('accountId', $accountId) + ->find(); + + if (empty($scoreRecord)) { + // 如果记录不存在,创建并计算基础分 + $service->calculateAndUpdate($accountId); + $scoreRecord = Db::table('s2_wechat_account_score') + ->where('accountId', $accountId) + ->find(); + } + + if (empty($scoreRecord)) { + return; + } + + // 更新isModifiedAlias字段 + $updateData = [ + 'isModifiedAlias' => $isModifiedAlias ? 1 : 0, + 'updateTime' => time() + ]; + + // 如果基础分已计算,需要更新基础信息分和基础分 + if ($scoreRecord['baseScoreCalculated']) { + $oldBaseInfoScore = $scoreRecord['baseInfoScore'] ?? 0; + $newBaseInfoScore = $isModifiedAlias ? 10 : 0; // 已修改微信号得10分 + + if ($oldBaseInfoScore != $newBaseInfoScore) { + $oldBaseScore = $scoreRecord['baseScore'] ?? 60; + $newBaseScore = $oldBaseScore - $oldBaseInfoScore + $newBaseInfoScore; + + $updateData['baseInfoScore'] = $newBaseInfoScore; + $updateData['baseScore'] = $newBaseScore; + + // 重新计算健康分 + $dynamicScore = $scoreRecord['dynamicScore'] ?? 0; + $healthScore = $newBaseScore + $dynamicScore; + $healthScore = max(0, min(100, $healthScore)); + $updateData['healthScore'] = $healthScore; + $updateData['maxAddFriendPerDay'] = (int)floor($healthScore * 0.2); + } + } else { + // 基础分未计算,只更新标记和基础信息分 + $updateData['baseInfoScore'] = $isModifiedAlias ? 10 : 0; + } + + Db::table('s2_wechat_account_score') + ->where('accountId', $accountId) + ->update($updateData); + } +} + diff --git a/Server/application/common.php b/Server/application/common.php index 37a12739..8785f12c 100644 --- a/Server/application/common.php +++ b/Server/application/common.php @@ -75,7 +75,18 @@ if (!function_exists('requestCurl')) { if (!function_exists('dataBuild')) { function dataBuild($array) { - return is_array($array) ? http_build_query($array) : $array; + if (!is_array($array)) { + return $array; + } + + // 处理嵌套数组 + foreach ($array as $key => $value) { + if (is_array($value)) { + $array[$key] = json_encode($value); + } + } + + return http_build_query($array); } } @@ -550,14 +561,15 @@ if (!function_exists('exit_data')) { exit(); } } - -/** - * 调试打印变量但不终止程序 - * @return void - */ -function dump() -{ - call_user_func_array(['app\\common\\helper\\Debug', 'dump'], func_get_args()); +if (!function_exists('dump')) { + /** + * 调试打印变量但不终止程序 + * @return void + */ + function dump() + { + call_user_func_array(['app\\common\\helper\\Debug', 'dump'], func_get_args()); + } } if (!function_exists('artificialAllotWechatFriend')) { diff --git a/Server/application/common/model/WechatAccountScore.php b/Server/application/common/model/WechatAccountScore.php new file mode 100644 index 00000000..43170f78 --- /dev/null +++ b/Server/application/common/model/WechatAccountScore.php @@ -0,0 +1,44 @@ + 'integer', + 'baseScore' => 'integer', + 'baseScoreCalculated' => 'integer', + 'baseInfoScore' => 'integer', + 'friendCountScore' => 'integer', + 'friendCount' => 'integer', + 'dynamicScore' => 'integer', + 'frequentCount' => 'integer', + 'frequentPenalty' => 'integer', + 'consecutiveNoFrequentDays' => 'integer', + 'noFrequentBonus' => 'integer', + 'banPenalty' => 'integer', + 'healthScore' => 'integer', + 'maxAddFriendPerDay' => 'integer', + 'isModifiedAlias' => 'integer', + 'isBanned' => 'integer', + 'lastFrequentTime' => 'integer', + 'lastNoFrequentTime' => 'integer', + 'baseScoreCalcTime' => 'integer', + 'createTime' => 'integer', + 'updateTime' => 'integer', + ]; +} + diff --git a/Server/application/common/service/WechatAccountHealthScoreService.php b/Server/application/common/service/WechatAccountHealthScoreService.php new file mode 100644 index 00000000..d6246a26 --- /dev/null +++ b/Server/application/common/service/WechatAccountHealthScoreService.php @@ -0,0 +1,1064 @@ +where('id', $accountId) + ->find(); + + Log::debug("查询账号数据: " . ($accountData ? "成功" : "失败")); + } + + if (empty($accountData)) { + $errorMsg = "账号不存在:{$accountId}"; + Log::error($errorMsg); + throw new Exception($errorMsg); + } + + $wechatId = $accountData['wechatId'] ?? ''; + if (empty($wechatId)) { + $errorMsg = "账号wechatId为空:{$accountId}"; + Log::error($errorMsg); + throw new Exception($errorMsg); + } + + Log::debug("账号数据: accountId={$accountId}, wechatId={$wechatId}"); + + // 获取或创建评分记录 + $scoreRecord = $this->getOrCreateScoreRecord($accountId, $wechatId); + Log::debug("获取评分记录: " . ($scoreRecord ? "成功" : "失败")); + + // 计算基础分(只计算一次,除非强制重新计算) + if (!$scoreRecord['baseScoreCalculated'] || $forceRecalculateBase) { + Log::info("计算基础分,accountId: {$accountId}, baseScoreCalculated: " . + ($scoreRecord['baseScoreCalculated'] ? 'true' : 'false') . + ", forceRecalculateBase: " . ($forceRecalculateBase ? 'true' : 'false')); + + $baseScoreData = $this->calculateBaseScore($accountData, $scoreRecord); + $this->updateBaseScore($accountId, $baseScoreData); + + Log::debug("基础分计算结果: " . json_encode($baseScoreData)); + + // 重新获取记录以获取最新数据 + $scoreRecord = $this->getScoreRecord($accountId); + } + + // 计算动态分(每次都要重新计算) + Log::info("计算动态分,accountId: {$accountId}"); + $dynamicScoreData = $this->calculateDynamicScore($accountData, $scoreRecord); + + // 计算总分 + $baseScore = $scoreRecord['baseScore']; + $dynamicScore = $dynamicScoreData['total']; + $healthScore = $baseScore + $dynamicScore; + + // 确保健康分在合理范围内(0-100) + $healthScore = max(0, min(100, $healthScore)); + + // 计算每日最大加人次数 + $maxAddFriendPerDay = $this->getMaxAddFriendPerDay($healthScore); + + Log::info("健康分计算结果,accountId: {$accountId}, baseScore: {$baseScore}, dynamicScore: {$dynamicScore}, " . + "healthScore: {$healthScore}, maxAddFriendPerDay: {$maxAddFriendPerDay}"); + + // 更新评分记录 + $updateData = [ + 'dynamicScore' => $dynamicScore, + 'frequentPenalty' => $dynamicScoreData['frequentPenalty'], + 'noFrequentBonus' => $dynamicScoreData['noFrequentBonus'], + 'banPenalty' => $dynamicScoreData['banPenalty'], + 'lastFrequentTime' => $dynamicScoreData['lastFrequentTime'], + 'frequentCount' => $dynamicScoreData['frequentCount'], + 'lastNoFrequentTime' => $dynamicScoreData['lastNoFrequentTime'], + 'consecutiveNoFrequentDays' => $dynamicScoreData['consecutiveNoFrequentDays'], + 'isBanned' => $dynamicScoreData['isBanned'], + 'healthScore' => $healthScore, + 'maxAddFriendPerDay' => $maxAddFriendPerDay, + 'updateTime' => time() + ]; + + $updateResult = Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE) + ->where('accountId', $accountId) + ->update($updateData); + + // 更新成功后,清除缓存 + if ($updateResult !== false) { + $this->clearScoreCache($accountId); + } + + $result = [ + 'accountId' => $accountId, + 'wechatId' => $wechatId, + 'healthScore' => $healthScore, + 'baseScore' => $baseScore, + 'baseInfoScore' => $scoreRecord['baseInfoScore'], + 'friendCountScore' => $scoreRecord['friendCountScore'], + 'dynamicScore' => $dynamicScore, + 'frequentPenalty' => $dynamicScoreData['frequentPenalty'], + 'noFrequentBonus' => $dynamicScoreData['noFrequentBonus'], + 'banPenalty' => $dynamicScoreData['banPenalty'], + 'maxAddFriendPerDay' => $maxAddFriendPerDay + ]; + + Log::debug("健康分计算完成,返回结果: " . json_encode($result)); + return $result; + + } catch (\PDOException $e) { + // 数据库异常 + $errorMsg = "数据库操作失败,accountId: {$accountId}, 错误: " . $e->getMessage(); + Log::error($errorMsg); + throw new Exception($errorMsg, $e->getCode(), $e); + } catch (\Throwable $e) { + // 其他所有异常 + $errorMsg = "计算健康分失败,accountId: {$accountId}, 错误: " . $e->getMessage(); + Log::error($errorMsg); + throw new Exception($errorMsg, $e->getCode(), $e); + } + } + + /** + * 获取或创建评分记录 + * + * @param int $accountId 账号ID + * @param string $wechatId 微信ID + * @return array 评分记录 + */ + private function getOrCreateScoreRecord($accountId, $wechatId) + { + // 尝试获取现有记录 + $record = $this->getScoreRecord($accountId); + + // 如果记录不存在,创建新记录 + if (empty($record)) { + Log::info("为账号 {$accountId} 创建新的评分记录"); + + // 创建新记录 + $data = [ + 'accountId' => $accountId, + 'wechatId' => $wechatId, + 'baseScore' => 0, + 'baseScoreCalculated' => 0, + 'baseInfoScore' => 0, + 'friendCountScore' => 0, + 'dynamicScore' => 0, + 'frequentCount' => 0, + 'consecutiveNoFrequentDays' => 0, + 'healthScore' => 0, + 'maxAddFriendPerDay' => 0, + 'createTime' => time(), + 'updateTime' => time() + ]; + + Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE)->insert($data); + + return $data; + } + + return $record; + } + + /** + * 获取评分记录 + * + * @param int $accountId 账号ID + * @param bool $useCache 是否使用缓存(默认true) + * @return array 评分记录,如果不存在则返回空数组 + */ + private function getScoreRecord($accountId, $useCache = true) + { + // 生成缓存键 + $cacheKey = self::CACHE_PREFIX . 'score:' . $accountId; + + // 如果使用缓存且缓存存在,则直接返回缓存数据 + if ($useCache && Cache::has($cacheKey)) { + $cachedData = Cache::get($cacheKey); + Log::debug("从缓存获取评分记录,accountId: {$accountId}"); + return $cachedData ?: []; + } + + // 从数据库获取记录 + $record = Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE) + ->where('accountId', $accountId) + ->find(); + + // 如果记录存在且使用缓存,则缓存记录 + if ($record && $useCache) { + Cache::set($cacheKey, $record, self::CACHE_TTL); + Log::debug("缓存评分记录,accountId: {$accountId}"); + } + + return $record ?: []; + } + + /** + * 计算基础分(只计算一次) + * 基础分 = 默认60分 + 基础信息分(10分) + 好友数量分(3-12分) + * + * @param array $accountData 账号数据 + * @param array $scoreRecord 现有评分记录 + * @return array 基础分数据 + */ + private function calculateBaseScore($accountData, $scoreRecord = []) + { + $baseScore = self::DEFAULT_BASE_SCORE; + + // 基础信息分(已修改微信号得10分) + $baseInfoScore = $this->getBaseInfoScore($accountData); + $baseScore += $baseInfoScore; + + // 好友数量分(特殊处理:使用快照值,避免同步问题) + $friendCountScore = 0; + $friendCount = 0; + $friendCountSource = 'manual'; + + // 如果已有评分记录且好友数量分已计算,使用历史值 + if (!empty($scoreRecord['friendCountScore']) && $scoreRecord['friendCountScore'] > 0) { + $friendCountScore = $scoreRecord['friendCountScore']; + $friendCount = $scoreRecord['friendCount'] ?? 0; + $friendCountSource = $scoreRecord['friendCountSource'] ?? 'manual'; + } else { + // 首次计算:使用当前好友数量,但标记为手动计算 + $totalFriend = $accountData['totalFriend'] ?? 0; + $friendCountScore = $this->getFriendCountScore($totalFriend); + $friendCount = $totalFriend; + $friendCountSource = 'manual'; + } + + $baseScore += $friendCountScore; + + // 检查是否已修改微信号 + $isModifiedAlias = $this->checkIsModifiedAlias($accountData); + + return [ + 'baseScore' => $baseScore, + 'baseInfoScore' => $baseInfoScore, + 'friendCountScore' => $friendCountScore, + 'friendCount' => $friendCount, + 'friendCountSource' => $friendCountSource, + 'isModifiedAlias' => $isModifiedAlias ? 1 : 0, + 'baseScoreCalculated' => 1, + 'baseScoreCalcTime' => time() + ]; + } + + /** + * 更新基础分 + * + * @param int $accountId 账号ID + * @param array $baseScoreData 基础分数据 + * @return bool 更新是否成功 + */ + private function updateBaseScore($accountId, $baseScoreData) + { + try { + $result = Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE) + ->where('accountId', $accountId) + ->update($baseScoreData); + + Log::debug("更新基础分,accountId: {$accountId}, 结果: " . ($result ? "成功" : "失败")); + + // 更新成功后,清除缓存 + if ($result !== false) { + $this->clearScoreCache($accountId); + } + + return $result !== false; + } catch (Exception $e) { + Log::error("更新基础分失败,accountId: {$accountId}, 错误: " . $e->getMessage()); + return false; + } + } + + /** + * 清除评分记录缓存 + * + * @param int $accountId 账号ID + * @return bool 是否成功清除缓存 + */ + private function clearScoreCache($accountId) + { + $cacheKey = self::CACHE_PREFIX . 'score:' . $accountId; + $result = Cache::rm($cacheKey); + Log::debug("清除评分记录缓存,accountId: {$accountId}, 结果: " . ($result ? "成功" : "失败")); + return $result; + } + + /** + * 获取基础信息分 + * 已修改微信号:10分 + * + * @param array $accountData 账号数据 + * @return int 基础信息分 + */ + private function getBaseInfoScore($accountData) + { + if ($this->checkIsModifiedAlias($accountData)) { + return self::BASE_INFO_SCORE; + } + return 0; + } + + /** + * 检查是否已修改微信号 + * 判断标准:wechatId和alias不一致且都不为空,则认为已修改微信号 + * 注意:这里只用于评分,不修复数据 + * + * @param array $accountData 账号数据 + * @return bool + */ + private function checkIsModifiedAlias($accountData) + { + $wechatId = trim($accountData['wechatId'] ?? ''); + $alias = trim($accountData['alias'] ?? ''); + + // 如果wechatId和alias不一致且都不为空,则认为已修改微信号(用于评分) + if (!empty($wechatId) && !empty($alias) && $wechatId !== $alias) { + return true; + } + + return false; + } + + /** + * 获取好友数量分 + * 根据好友数量区间得分(最高12分) + * + * @param int $totalFriend 总好友数 + * @return int 好友数量分 + */ + private function getFriendCountScore($totalFriend) + { + if ($totalFriend <= 50) { + return self::FRIEND_COUNT_SCORE_0_50; + } elseif ($totalFriend <= 500) { + return self::FRIEND_COUNT_SCORE_51_500; + } elseif ($totalFriend <= 3000) { + return self::FRIEND_COUNT_SCORE_501_3000; + } else { + return self::FRIEND_COUNT_SCORE_3001_PLUS; + } + } + + /** + * 手动更新好友数量分(用于处理同步问题) + * + * @param int $accountId 账号ID + * @param int $friendCount 好友数量 + * @param string $source 来源(manual=手动,sync=同步) + * @return bool 更新是否成功 + * @throws Exception 如果参数无效或更新过程中出现错误 + */ + public function updateFriendCountScore($accountId, $friendCount, $source = 'manual') + { + // 参数验证 + if (empty($accountId) || !is_numeric($accountId)) { + $errorMsg = "无效的账号ID: " . (is_scalar($accountId) ? $accountId : gettype($accountId)); + Log::error($errorMsg); + throw new Exception($errorMsg); + } + + if (!is_numeric($friendCount) || $friendCount < 0) { + $errorMsg = "无效的好友数量: {$friendCount}"; + Log::error($errorMsg); + throw new Exception($errorMsg); + } + + if (!in_array($source, ['manual', 'sync'])) { + $errorMsg = "无效的来源: {$source},必须是 'manual' 或 'sync'"; + Log::error($errorMsg); + throw new Exception($errorMsg); + } + + try { + $scoreRecord = $this->getScoreRecord($accountId); + + // 如果基础分已计算,不允许修改好友数量分(除非是手动更新) + if (!empty($scoreRecord['baseScoreCalculated']) && $source === 'sync') { + // 同步数据不允许修改已计算的基础分 + Log::warning("同步数据不允许修改已计算的基础分,accountId: {$accountId}"); + return false; + } + } + catch (\Exception $e) { + $errorMsg = "获取评分记录失败,accountId: {$accountId}, 错误: " . $e->getMessage(); + Log::error($errorMsg); + throw new Exception($errorMsg, $e->getCode(), $e); + } + + $friendCountScore = $this->getFriendCountScore($friendCount); + + // 重新计算基础分 + $oldBaseScore = $scoreRecord['baseScore'] ?? self::DEFAULT_BASE_SCORE; + $oldFriendCountScore = $scoreRecord['friendCountScore'] ?? 0; + $baseInfoScore = $scoreRecord['baseInfoScore'] ?? 0; + + $newBaseScore = self::DEFAULT_BASE_SCORE + $baseInfoScore + $friendCountScore; + + $updateData = [ + 'friendCountScore' => $friendCountScore, + 'friendCount' => $friendCount, + 'friendCountSource' => $source, + 'baseScore' => $newBaseScore, + 'updateTime' => time() + ]; + + // 如果基础分已计算,需要更新总分 + if (!empty($scoreRecord['baseScoreCalculated'])) { + $dynamicScore = $scoreRecord['dynamicScore'] ?? 0; + $healthScore = $newBaseScore + $dynamicScore; + $healthScore = max(0, min(100, $healthScore)); + $updateData['healthScore'] = $healthScore; + $updateData['maxAddFriendPerDay'] = $this->getMaxAddFriendPerDay($healthScore); + } + + try { + $result = Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE) + ->where('accountId', $accountId) + ->update($updateData); + + // 更新成功后,清除缓存 + if ($result !== false) { + $this->clearScoreCache($accountId); + $this->clearHealthScoreCache($accountId); + Log::info("更新好友数量分成功,accountId: {$accountId}, friendCount: {$friendCount}, source: {$source}"); + } else { + Log::warning("更新好友数量分失败,accountId: {$accountId}, friendCount: {$friendCount}, source: {$source}"); + } + + return $result !== false; + } catch (\PDOException $e) { + $errorMsg = "数据库操作失败,accountId: {$accountId}, 错误: " . $e->getMessage(); + Log::error($errorMsg); + throw new Exception($errorMsg, $e->getCode(), $e); + } catch (\Throwable $e) { + $errorMsg = "更新好友数量分失败,accountId: {$accountId}, 错误: " . $e->getMessage(); + Log::error($errorMsg); + throw new Exception($errorMsg, $e->getCode(), $e); + } + } + + /** + * 计算动态分 + * 动态分 = 扣分 + 加分 + * 如果添加好友记录表没有记录,则动态分为0 + * + * @param array $accountData 账号数据 + * @param array $scoreRecord 现有评分记录 + * @return array 动态分数据 + */ + private function calculateDynamicScore($accountData, $scoreRecord) + { + $accountId = $accountData['id'] ?? 0; + $wechatId = $accountData['wechatId'] ?? ''; + + Log::debug("开始计算动态分,accountId: {$accountId}, wechatId: {$wechatId}"); + + $result = [ + 'total' => 0, + 'frequentPenalty' => 0, + 'noFrequentBonus' => 0, + 'banPenalty' => 0, + 'lastFrequentTime' => null, + 'frequentCount' => 0, + 'lastNoFrequentTime' => null, + 'consecutiveNoFrequentDays' => 0, + 'isBanned' => 0 + ]; + + if (empty($accountId) || empty($wechatId)) { + Log::warning("计算动态分失败: accountId或wechatId为空"); + return $result; + } + + // 计算30天前的时间戳(在多个方法中使用) + $thirtyDaysAgo = time() - (30 * 24 * 3600); + + // 检查添加好友记录表是否有记录,如果没有记录则动态分为0 + // 使用EXISTS子查询优化性能,只检查是否存在记录,不需要计数 + $hasFriendTask = Db::table(self::TABLE_FRIEND_TASK) + ->where('wechatAccountId', $accountId) + ->where(function($query) use ($wechatId) { + if (!empty($wechatId)) { + $query->where('wechatId', $wechatId); + } + }) + ->value('id'); // 只获取ID,比count()更高效 + + // 如果添加好友记录表没有记录,则动态分为0 + if (empty($hasFriendTask)) { + Log::info("账号没有添加好友记录,动态分为0,accountId: {$accountId}"); + return $result; + } + + Log::debug("账号有添加好友记录,继续计算动态分,accountId: {$accountId}"); + + // 继承现有数据 + if (!empty($scoreRecord)) { + $result['lastFrequentTime'] = $scoreRecord['lastFrequentTime'] ?? null; + $result['frequentCount'] = $scoreRecord['frequentCount'] ?? 0; + $result['lastNoFrequentTime'] = $scoreRecord['lastNoFrequentTime'] ?? null; + $result['consecutiveNoFrequentDays'] = $scoreRecord['consecutiveNoFrequentDays'] ?? 0; + $result['frequentPenalty'] = $scoreRecord['frequentPenalty'] ?? 0; + $result['noFrequentBonus'] = $scoreRecord['noFrequentBonus'] ?? 0; + $result['banPenalty'] = $scoreRecord['banPenalty'] ?? 0; + } + + // 1. 检查频繁记录(从s2_friend_task表查询,只统计近30天) + $frequentData = $this->checkFrequentFromFriendTask($accountId, $wechatId, $scoreRecord, $thirtyDaysAgo); + $result['lastFrequentTime'] = $frequentData['lastFrequentTime'] ?? null; + $result['frequentCount'] = $frequentData['frequentCount'] ?? 0; + $result['frequentPenalty'] = $frequentData['frequentPenalty'] ?? 0; + + // 2. 检查封号记录(从s2_wechat_message表查询) + $banData = $this->checkBannedFromMessage($accountId, $wechatId, $thirtyDaysAgo); + if (!empty($banData)) { + $result['isBanned'] = $banData['isBanned']; + $result['banPenalty'] = $banData['banPenalty']; + } + + // 3. 计算不频繁加分(基于近30天的频繁记录,反向参考频繁规则) + $noFrequentData = $this->calculateNoFrequentBonus($accountId, $wechatId, $frequentData, $thirtyDaysAgo); + $result['noFrequentBonus'] = $noFrequentData['bonus'] ?? 0; + $result['consecutiveNoFrequentDays'] = $noFrequentData['consecutiveDays'] ?? 0; + $result['lastNoFrequentTime'] = $noFrequentData['lastNoFrequentTime'] ?? null; + + // 计算总分 + $result['total'] = $result['frequentPenalty'] + $result['noFrequentBonus'] + $result['banPenalty']; + + Log::debug("动态分计算结果,accountId: {$accountId}, frequentPenalty: {$result['frequentPenalty']}, " . + "noFrequentBonus: {$result['noFrequentBonus']}, banPenalty: {$result['banPenalty']}, " . + "total: {$result['total']}"); + + return $result; + } + + /** + * 从s2_friend_task表检查频繁记录 + * extra字段包含"操作过于频繁"即需要扣分 + * 只统计近30天的数据 + * + * @param int $accountId 账号ID + * @param string $wechatId 微信ID + * @param array $scoreRecord 现有评分记录 + * @param int $thirtyDaysAgo 30天前的时间戳(可选,如果已计算则传入以避免重复计算) + * @return array|null + */ + private function checkFrequentFromFriendTask($accountId, $wechatId, $scoreRecord, $thirtyDaysAgo = null) + { + // 如果没有传入30天前的时间戳,则计算 + if ($thirtyDaysAgo === null) { + $thirtyDaysAgo = time() - (30 * 24 * 3600); + } + + // 查询包含"操作过于频繁"的记录(只统计近30天) + // extra字段可能是文本或JSON格式,使用LIKE查询 + // 优化查询:只查询必要的字段,减少数据传输量 + $frequentTasks = Db::table(self::TABLE_FRIEND_TASK) + ->where('wechatAccountId', $accountId) + ->where('createTime', '>=', $thirtyDaysAgo) + ->where(function($query) use ($wechatId) { + if (!empty($wechatId)) { + $query->where('wechatId', $wechatId); + } + }) + ->where(function($query) { + // 检查extra字段是否包含"操作过于频繁"(可能是文本或JSON) + $query->where('extra', 'like', '%操作过于频繁%') + ->whereOr('extra', 'like', '%"当前账号存在安全风险"%'); + }) + ->order('createTime', 'desc') + ->field('id, createTime, extra') + ->select(); + + // 获取最新的频繁时间 + $latestFrequentTime = !empty($frequentTasks) ? $frequentTasks[0]['createTime'] : null; + + // 计算频繁次数(统计近30天内包含"操作过于频繁"的记录) + $frequentCount = count($frequentTasks); + + // 如果30天内没有频繁记录,清除扣分 + if (empty($frequentTasks)) { + return [ + 'lastFrequentTime' => null, + 'frequentCount' => 0, + 'frequentPenalty' => 0 + ]; + } + + // 根据30天内的频繁次数计算扣分 + $penalty = 0; + if ($frequentCount == 1) { + $penalty = self::PENALTY_FIRST_FREQUENT; // 首次频繁-15分 + } elseif ($frequentCount >= 2) { + $penalty = self::PENALTY_SECOND_FREQUENT; // 再次频繁-25分 + } + + return [ + 'lastFrequentTime' => $latestFrequentTime, + 'frequentCount' => $frequentCount, + 'frequentPenalty' => $penalty + ]; + } + + /** + * 从s2_wechat_message表检查封号记录 + * content包含"你的账号被限制"且msgType为10000 + * 只统计近30天的数据 + * + * @param int $accountId 账号ID + * @param string $wechatId 微信ID + * @param int $thirtyDaysAgo 30天前的时间戳(可选,如果已计算则传入以避免重复计算) + * @return array|null + */ + private function checkBannedFromMessage($accountId, $wechatId, $thirtyDaysAgo = null) + { + // 如果没有传入30天前的时间戳,则计算 + if ($thirtyDaysAgo === null) { + $thirtyDaysAgo = time() - (30 * 24 * 3600); + } + + // 查询封号消息(只统计近30天) + // 优化查询:只查询必要的字段,减少数据传输量 + $banMessage = Db::table(self::TABLE_WECHAT_MESSAGE) + ->where('wechatAccountId', $accountId) + ->where('msgType', 10000) + ->where('content', 'like', '%你的账号被限制%') + ->where('isDeleted', 0) + ->where('createTime', '>=', $thirtyDaysAgo) + ->field('id, createTime') // 只查询必要的字段 + ->order('createTime', 'desc') + ->find(); + + if (!empty($banMessage)) { + return [ + 'isBanned' => 1, + 'banPenalty' => self::PENALTY_BANNED // 封号-60分 + ]; + } + + return [ + 'isBanned' => 0, + 'banPenalty' => 0 + ]; + } + + /** + * 计算不频繁加分 + * 反向参考频繁规则:查询近30天的频繁记录,计算连续不频繁天数 + * 规则:30天内连续不频繁的,只要有一次频繁就得重新计算(重置连续不频繁天数) + * 如果连续3天没有频繁,则每天+5分 + * + * @param int $accountId 账号ID + * @param string $wechatId 微信ID + * @param array $frequentData 频繁数据(包含lastFrequentTime和frequentCount) + * @param int $thirtyDaysAgo 30天前的时间戳(可选,如果已计算则传入以避免重复计算) + * @return array 包含bonus、consecutiveDays、lastNoFrequentTime + */ + private function calculateNoFrequentBonus($accountId, $wechatId, $frequentData, $thirtyDaysAgo = null) + { + $result = [ + 'bonus' => 0, + 'consecutiveDays' => 0, + 'lastNoFrequentTime' => null + ]; + + if (empty($accountId) || empty($wechatId)) { + return $result; + } + + // 如果没有传入30天前的时间戳,则计算 + if ($thirtyDaysAgo === null) { + $thirtyDaysAgo = time() - (30 * 24 * 3600); + } + $currentTime = time(); + + // 获取最后一次频繁时间(30天内最后一次频繁的时间) + $lastFrequentTime = $frequentData['lastFrequentTime'] ?? null; + + // 规则:30天内连续不频繁的,只要有一次频繁就得重新计算(重置连续不频繁天数) + if (empty($lastFrequentTime)) { + // 情况1:30天内没有频繁记录,说明30天内连续不频繁 + // 计算从30天前到现在的连续不频繁天数(最多30天) + $consecutiveDays = min(30, floor(($currentTime - $thirtyDaysAgo) / 86400)); + } else { + // 情况2:30天内有频繁记录,从最后一次频繁时间开始重新计算连续不频繁天数 + // 只要有一次频繁,连续不频繁天数就从最后一次频繁时间开始重新计算 + // 计算从最后一次频繁时间到现在,连续多少天没有频繁 + $timeDiff = $currentTime - $lastFrequentTime; + $consecutiveDays = floor($timeDiff / 86400); // 向下取整,得到完整的天数 + + // 边界情况:如果最后一次频繁时间在30天前(理论上不应该发生,因为查询已经限制了30天),则按30天处理 + if ($lastFrequentTime < $thirtyDaysAgo) { + $consecutiveDays = min(30, floor(($currentTime - $thirtyDaysAgo) / 86400)); + } + } + + // 如果连续3天或以上没有频繁,则每天+5分 + if ($consecutiveDays >= 3) { + $bonus = $consecutiveDays * self::BONUS_NO_FREQUENT_PER_DAY; + $result['bonus'] = $bonus; + $result['consecutiveDays'] = $consecutiveDays; + $result['lastNoFrequentTime'] = $currentTime; + } else { + $result['consecutiveDays'] = $consecutiveDays; + } + + return $result; + } + + /** + * 根据健康分计算每日最大加人次数 + * 公式:每日最大加人次数 = 健康分 * 0.2 + * + * @param int $healthScore 健康分 + * @return int 每日最大加人次数 + */ + public function getMaxAddFriendPerDay($healthScore) + { + return (int)floor($healthScore * 0.2); + } + + /** + * 批量计算并更新多个账号的健康分 + * + * @param array $accountIds 账号ID数组(为空则处理所有账号) + * @param int $batchSize 每批处理数量 + * @param bool $forceRecalculateBase 是否强制重新计算基础分 + * @return array 处理结果统计 + * @throws Exception 如果参数无效或批量处理过程中出现严重错误 + */ + public function batchCalculateAndUpdate($accountIds = [], $batchSize = 100, $forceRecalculateBase = false) + { + // 参数验证 + if (!is_array($accountIds)) { + $errorMsg = "无效的账号ID数组: " . gettype($accountIds); + Log::error($errorMsg); + throw new Exception($errorMsg); + } + + if (!is_numeric($batchSize) || $batchSize <= 0) { + $errorMsg = "无效的批处理大小: {$batchSize}"; + Log::error($errorMsg); + throw new Exception($errorMsg); + } + + try { + $startTime = microtime(true); + Log::info("开始批量计算健康分,batchSize: {$batchSize}, forceRecalculateBase: " . ($forceRecalculateBase ? 'true' : 'false')); + + $stats = [ + 'total' => 0, + 'success' => 0, + 'failed' => 0, + 'errors' => [] + ]; + + // 如果没有指定账号ID,则处理所有账号 + if (empty($accountIds)) { + Log::info("未指定账号ID,处理所有未删除账号"); + $accountIds = Db::table(self::TABLE_WECHAT_ACCOUNT) + ->where('isDeleted', 0) + ->column('id'); + } + + $stats['total'] = count($accountIds); + Log::info("需要处理的账号总数: {$stats['total']}"); + + // 分批处理 + $batches = array_chunk($accountIds, $batchSize); + $batchCount = count($batches); + Log::info("分批处理,共 {$batchCount} 批"); + + foreach ($batches as $batchIndex => $batch) { + $batchStartTime = microtime(true); + Log::info("开始处理第 " . ($batchIndex + 1) . " 批,共 " . count($batch) . " 个账号"); + + foreach ($batch as $accountId) { + try { + $this->calculateAndUpdate($accountId, null, $forceRecalculateBase); + $stats['success']++; + } catch (Exception $e) { + $stats['failed']++; + $stats['errors'][] = [ + 'accountId' => $accountId, + 'error' => $e->getMessage() + ]; + Log::error("账号 {$accountId} 计算失败: " . $e->getMessage()); + } + } + + $batchEndTime = microtime(true); + $batchDuration = round($batchEndTime - $batchStartTime, 2); + Log::info("第 " . ($batchIndex + 1) . " 批处理完成,耗时: {$batchDuration}秒," . + "成功: {$stats['success']},失败: {$stats['failed']}"); + } + + $endTime = microtime(true); + $totalDuration = round($endTime - $startTime, 2); + Log::info("批量计算健康分完成,总耗时: {$totalDuration}秒,成功: {$stats['success']},失败: {$stats['failed']}"); + + return $stats; + } catch (\PDOException $e) { + $errorMsg = "批量计算健康分过程中数据库操作失败: " . $e->getMessage(); + Log::error($errorMsg); + throw new Exception($errorMsg, $e->getCode(), $e); + } catch (\Throwable $e) { + $errorMsg = "批量计算健康分过程中发生严重错误: " . $e->getMessage(); + Log::error($errorMsg); + throw new Exception($errorMsg, $e->getCode(), $e); + } + } + + /** + * 记录频繁事件(已废弃,改为从s2_friend_task表自动检测) + * 保留此方法以兼容旧代码,实际频繁检测在calculateDynamicScore中完成 + * + * @param int $accountId 账号ID + * @return bool + */ + public function recordFrequent($accountId) + { + // 频繁检测已改为从s2_friend_task表自动检测 + // 直接重新计算健康分即可 + try { + $this->calculateAndUpdate($accountId); + return true; + } catch (\Exception $e) { + return false; + } + } + + /** + * 记录不频繁事件(用于加分) + * + * @param int $accountId 账号ID + * @return bool + */ + public function recordNoFrequent($accountId) + { + $scoreRecord = $this->getScoreRecord($accountId); + + if (empty($scoreRecord)) { + // 如果记录不存在,先创建 + $accountData = Db::table(self::TABLE_WECHAT_ACCOUNT) + ->where('id', $accountId) + ->find(); + + if (empty($accountData)) { + return false; + } + + $this->getOrCreateScoreRecord($accountId, $accountData['wechatId']); + $scoreRecord = $this->getScoreRecord($accountId); + } + + $lastNoFrequentTime = $scoreRecord['lastNoFrequentTime'] ?? null; + $consecutiveNoFrequentDays = $scoreRecord['consecutiveNoFrequentDays'] ?? 0; + $currentTime = time(); + + // 如果上次不频繁时间是昨天或更早,则增加连续天数 + if (empty($lastNoFrequentTime) || ($currentTime - $lastNoFrequentTime) >= 86400) { + // 如果间隔超过2天,重置为1天 + if (!empty($lastNoFrequentTime) && ($currentTime - $lastNoFrequentTime) > 86400 * 2) { + $consecutiveNoFrequentDays = 1; + } else { + $consecutiveNoFrequentDays++; + } + } + + // 计算加分(连续3天及以上才加分) + $bonus = 0; + if ($consecutiveNoFrequentDays >= 3) { + $bonus = $consecutiveNoFrequentDays * self::BONUS_NO_FREQUENT_PER_DAY; + } + + $updateData = [ + 'lastNoFrequentTime' => $currentTime, + 'consecutiveNoFrequentDays' => $consecutiveNoFrequentDays, + 'noFrequentBonus' => $bonus, + 'updateTime' => $currentTime + ]; + + Db::table('s2_wechat_account_score') + ->where('accountId', $accountId) + ->update($updateData); + + // 重新计算健康分 + $this->calculateAndUpdate($accountId); + + return true; + } + + /** + * 获取账号健康分信息 + * + * @param int $accountId 账号ID + * @param bool $useCache 是否使用缓存(默认true) + * @param bool $forceRecalculate 是否强制重新计算(默认false) + * @return array|null + */ + public function getHealthScore($accountId, $useCache = true, $forceRecalculate = false) + { + // 如果强制重新计算,则不使用缓存 + if ($forceRecalculate) { + Log::info("强制重新计算健康分,accountId: {$accountId}"); + return $this->calculateAndUpdate($accountId, null, false); + } + + // 生成缓存键 + $cacheKey = self::CACHE_PREFIX . 'health:' . $accountId; + + // 如果使用缓存且缓存存在,则直接返回缓存数据 + if ($useCache && !$forceRecalculate && Cache::has($cacheKey)) { + $cachedData = Cache::get($cacheKey); + Log::debug("从缓存获取健康分信息,accountId: {$accountId}"); + return $cachedData; + } + + // 从数据库获取记录 + $scoreRecord = $this->getScoreRecord($accountId, $useCache); + + if (empty($scoreRecord)) { + return null; + } + + $healthScoreInfo = [ + 'accountId' => $scoreRecord['accountId'], + 'wechatId' => $scoreRecord['wechatId'], + 'healthScore' => $scoreRecord['healthScore'] ?? 0, + 'baseScore' => $scoreRecord['baseScore'] ?? 0, + 'baseInfoScore' => $scoreRecord['baseInfoScore'] ?? 0, + 'friendCountScore' => $scoreRecord['friendCountScore'] ?? 0, + 'friendCount' => $scoreRecord['friendCount'] ?? 0, + 'dynamicScore' => $scoreRecord['dynamicScore'] ?? 0, + 'frequentPenalty' => $scoreRecord['frequentPenalty'] ?? 0, + 'noFrequentBonus' => $scoreRecord['noFrequentBonus'] ?? 0, + 'banPenalty' => $scoreRecord['banPenalty'] ?? 0, + 'maxAddFriendPerDay' => $scoreRecord['maxAddFriendPerDay'] ?? 0, + 'baseScoreCalculated' => $scoreRecord['baseScoreCalculated'] ?? 0, + 'lastFrequentTime' => $scoreRecord['lastFrequentTime'] ?? null, + 'frequentCount' => $scoreRecord['frequentCount'] ?? 0, + 'isBanned' => $scoreRecord['isBanned'] ?? 0 + ]; + + // 如果使用缓存,则缓存健康分信息 + if ($useCache) { + Cache::set($cacheKey, $healthScoreInfo, self::CACHE_TTL); + Log::debug("缓存健康分信息,accountId: {$accountId}"); + } + + return $healthScoreInfo; + } + + /** + * 清除健康分信息缓存 + * + * @param int $accountId 账号ID + * @return bool 是否成功清除缓存 + */ + public function clearHealthScoreCache($accountId) + { + $cacheKey = self::CACHE_PREFIX . 'health:' . $accountId; + $result = Cache::rm($cacheKey); + + // 同时清除评分记录缓存 + $this->clearScoreCache($accountId); + + Log::debug("清除健康分信息缓存,accountId: {$accountId}, 结果: " . ($result ? "成功" : "失败")); + return $result; + } +} diff --git a/Server/application/cunkebao/controller/ContentLibraryController.php b/Server/application/cunkebao/controller/ContentLibraryController.php index d77c12d8..69291123 100644 --- a/Server/application/cunkebao/controller/ContentLibraryController.php +++ b/Server/application/cunkebao/controller/ContentLibraryController.php @@ -601,12 +601,14 @@ class ContentLibraryController extends Controller $item['urls'] = json_decode($item['urls'] ?: '[]', true); // 格式化时间 - if ($item['createMomentTime']) { - $item['time'] = date('Y-m-d H:i:s', $item['createMomentTime']); - } elseif ($item['createMessageTime']) { - $item['time'] = date('Y-m-d H:i:s', $item['createMessageTime']); + if (!empty($item['createMomentTime']) && is_numeric($item['createMomentTime'])) { + $item['time'] = date('Y-m-d H:i:s', (int)$item['createMomentTime']); + } elseif (!empty($item['createMessageTime']) && is_numeric($item['createMessageTime'])) { + $item['time'] = date('Y-m-d H:i:s', (int)$item['createMessageTime']); + } elseif (!empty($item['createTime']) && is_numeric($item['createTime'])) { + $item['time'] = date('Y-m-d H:i:s', (int)$item['createTime']); } else { - $item['time'] = date('Y-m-d H:i:s', $item['createTime']); + $item['time'] = date('Y-m-d H:i:s'); // 如果没有有效的时间戳,使用当前时间 } // 设置发送者信息 @@ -1068,7 +1070,7 @@ class ContentLibraryController extends Controller ->select()->toArray(); if (empty($libraries)) { - return json(['code' => 200, 'msg' => '没有可用的内容库配置']); + return json_encode(['code' => 200, 'msg' => '没有可用的内容库配置'],256); } $successCount = 0; @@ -1157,7 +1159,7 @@ class ContentLibraryController extends Controller } // 返回采集结果 - return json([ + return json_encode([ 'code' => 200, 'msg' => '采集任务执行完成', 'data' => [ @@ -1167,7 +1169,7 @@ class ContentLibraryController extends Controller 'skipped' => $totalLibraries - $successCount - $failCount, 'results' => $results ] - ]); + ],256); } /** @@ -1204,7 +1206,7 @@ class ContentLibraryController extends Controller ->whereIn('id', $friendIds) ->where('isDeleted', 0) ->select(); - + if (empty($friends)) { return [ 'status' => 'failed', diff --git a/Server/application/cunkebao/controller/WorkbenchController.php b/Server/application/cunkebao/controller/WorkbenchController.php index 62e17116..11681d9d 100644 --- a/Server/application/cunkebao/controller/WorkbenchController.php +++ b/Server/application/cunkebao/controller/WorkbenchController.php @@ -2816,6 +2816,8 @@ class WorkbenchController extends Controller $limit = $this->request->param('limit', 10); $workbenchId = $this->request->param('workbenchId', 0); $keyword = $this->request->param('keyword', ''); + $pushType = $this->request->param('pushType', ''); // 推送类型筛选:''=全部, 'friend'=好友消息, 'group'=群消息, 'announcement'=群公告 + $status = $this->request->param('status', ''); // 状态筛选:''=全部, 'success'=已完成, 'progress'=进行中, 'failed'=失败 $userId = $this->request->userInfo['id']; // 构建工作台查询条件 @@ -2840,10 +2842,11 @@ class WorkbenchController extends Controller $workbenchWhere[] = ['w.id', '=', $workbenchId]; } - // 按内容ID、工作台ID和时间分组,统计每次推送 - $query = Db::name('workbench_group_push_item') + // 1. 先查询所有已执行的推送记录(按推送时间分组) + $pushHistoryQuery = Db::name('workbench_group_push_item') ->alias('wgpi') ->join('workbench w', 'w.id = wgpi.workbenchId', 'left') + ->join('workbench_group_push wgp', 'wgp.workbenchId = wgpi.workbenchId', 'left') ->join('content_item ci', 'ci.id = wgpi.contentId', 'left') ->join('content_library cl', 'cl.id = ci.libraryId', 'left') ->where($workbenchWhere) @@ -2853,52 +2856,57 @@ class WorkbenchController extends Controller 'wgpi.contentId', 'FROM_UNIXTIME(wgpi.createTime, "%Y-%m-%d %H:00:00") as pushTime', 'wgpi.targetType', + 'wgp.groupPushSubType', 'MIN(wgpi.createTime) as createTime', 'COUNT(DISTINCT wgpi.id) as totalCount', 'cl.name as contentLibraryName' ]) - ->group('wgpi.workbenchId, wgpi.contentId, pushTime, wgpi.targetType'); + ->group('wgpi.workbenchId, wgpi.contentId, pushTime, wgpi.targetType, wgp.groupPushSubType'); if (!empty($keyword)) { - $query->where('w.name|cl.name|ci.content', 'like', '%' . $keyword . '%'); + $pushHistoryQuery->where('w.name|cl.name|ci.content', 'like', '%' . $keyword . '%'); } - // 获取分页数据 - $list = $query->order('createTime', 'desc') - ->page($page, $limit) - ->select(); - - // 对于有 group by 的查询,统计总数需要重新查询 - $totalQuery = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->join('workbench w', 'w.id = wgpi.workbenchId', 'left') - ->join('content_item ci', 'ci.id = wgpi.contentId', 'left') - ->join('content_library cl', 'cl.id = ci.libraryId', 'left') - ->where($workbenchWhere); - - if (!empty($keyword)) { - $totalQuery->where('w.name|cl.name|ci.content', 'like', '%' . $keyword . '%'); - } - - // 统计分组后的记录数(使用子查询) - $subQuery = $totalQuery + $pushHistoryList = $pushHistoryQuery->order('createTime', 'desc')->select(); + + // 2. 查询所有任务(包括未执行的) + $allTasksQuery = Db::name('workbench') + ->alias('w') + ->join('workbench_group_push wgp', 'wgp.workbenchId = w.id', 'left') + ->where($workbenchWhere) ->field([ - 'wgpi.workbenchId', - 'wgpi.contentId', - 'FROM_UNIXTIME(wgpi.createTime, "%Y-%m-%d %H:00:00") as pushTime', - 'wgpi.targetType' - ]) - ->group('wgpi.workbenchId, wgpi.contentId, pushTime, wgpi.targetType') - ->buildSql(); - - $total = Db::table('(' . $subQuery . ') as temp')->count(); + 'w.id as workbenchId', + 'w.name as workbenchName', + 'w.createTime', + 'wgp.targetType', + 'wgp.groupPushSubType', + 'wgp.groups', + 'wgp.friends', + 'wgp.trafficPools' + ]); - // 处理每条记录 - foreach ($list as &$item) { + if (!empty($keyword)) { + $allTasksQuery->where('w.name', 'like', '%' . $keyword . '%'); + } + + $allTasks = $allTasksQuery->select(); + + // 3. 合并数据:已执行的推送记录 + 未执行的任务 + $resultList = []; + $executedWorkbenchIds = []; + + // 处理已执行的推送记录 + foreach ($pushHistoryList as $item) { $itemWorkbenchId = $item['workbenchId']; $contentId = $item['contentId']; $pushTime = $item['pushTime']; $targetType = intval($item['targetType']); + $groupPushSubType = isset($item['groupPushSubType']) ? intval($item['groupPushSubType']) : 1; + + // 标记该工作台已有执行记录 + if (!in_array($itemWorkbenchId, $executedWorkbenchIds)) { + $executedWorkbenchIds[] = $itemWorkbenchId; + } // 将时间字符串转换为时间戳范围(小时级别) $pushTimeStart = strtotime($pushTime); @@ -2937,23 +2945,149 @@ class WorkbenchController extends Controller $failCount = 0; // 简化处理,实际需要从发送状态获取 // 状态判断 - $status = $successCount > 0 ? 'success' : 'failed'; + $itemStatus = $successCount > 0 ? 'success' : 'failed'; if ($failCount > 0 && $successCount > 0) { - $status = 'partial'; + $itemStatus = 'partial'; } - $item['pushType'] = $targetType == 1 ? '群推送' : '好友推送'; - $item['pushTypeCode'] = $targetType; - $item['targetCount'] = $targetCount; - $item['successCount'] = $successCount; - $item['failCount'] = $failCount; - $item['status'] = $status; - $item['statusText'] = $status == 'success' ? '成功' : ($status == 'partial' ? '部分成功' : '失败'); - $item['createTime'] = date('Y-m-d H:i:s', $item['createTime']); - // 任务名称(工作台名称) - $item['taskName'] = $item['workbenchName'] ?? ''; + // 推送类型判断 + $pushTypeText = ''; + $pushTypeCode = ''; + if ($targetType == 1) { + // 群推送 + if ($groupPushSubType == 2) { + $pushTypeText = '群公告'; + $pushTypeCode = 'announcement'; + } else { + $pushTypeText = '群消息'; + $pushTypeCode = 'group'; + } + } else { + // 好友推送 + $pushTypeText = '好友消息'; + $pushTypeCode = 'friend'; + } + + $resultList[] = [ + 'workbenchId' => $itemWorkbenchId, + 'taskName' => $item['workbenchName'] ?? '', + 'pushType' => $pushTypeText, + 'pushTypeCode' => $pushTypeCode, + 'targetCount' => $targetCount, + 'successCount' => $successCount, + 'failCount' => $failCount, + 'status' => $itemStatus, + 'statusText' => $this->getStatusText($itemStatus), + 'createTime' => date('Y-m-d H:i:s', $item['createTime']), + 'contentLibraryName' => $item['contentLibraryName'] ?? '' + ]; } - unset($item); + + // 处理未执行的任务 + foreach ($allTasks as $task) { + $taskWorkbenchId = $task['workbenchId']; + + // 如果该任务已有执行记录,跳过(避免重复) + if (in_array($taskWorkbenchId, $executedWorkbenchIds)) { + continue; + } + + $targetType = isset($task['targetType']) ? intval($task['targetType']) : 1; + $groupPushSubType = isset($task['groupPushSubType']) ? intval($task['groupPushSubType']) : 1; + + // 计算目标数量(从配置中获取) + $targetCount = 0; + if ($targetType == 1) { + // 群推送:统计配置的群数量 + $groups = json_decode($task['groups'] ?? '[]', true); + $targetCount = is_array($groups) ? count($groups) : 0; + } else { + // 好友推送:统计配置的好友数量或流量池数量 + $friends = json_decode($task['friends'] ?? '[]', true); + $trafficPools = json_decode($task['trafficPools'] ?? '[]', true); + $friendCount = is_array($friends) ? count($friends) : 0; + $poolCount = is_array($trafficPools) ? count($trafficPools) : 0; + // 如果配置了流量池,目标数量暂时显示为流量池数量(实际数量需要从流量池中统计) + $targetCount = $friendCount > 0 ? $friendCount : $poolCount; + } + + // 推送类型判断 + $pushTypeText = ''; + $pushTypeCode = ''; + if ($targetType == 1) { + // 群推送 + if ($groupPushSubType == 2) { + $pushTypeText = '群公告'; + $pushTypeCode = 'announcement'; + } else { + $pushTypeText = '群消息'; + $pushTypeCode = 'group'; + } + } else { + // 好友推送 + $pushTypeText = '好友消息'; + $pushTypeCode = 'friend'; + } + + $resultList[] = [ + 'workbenchId' => $taskWorkbenchId, + 'taskName' => $task['workbenchName'] ?? '', + 'pushType' => $pushTypeText, + 'pushTypeCode' => $pushTypeCode, + 'targetCount' => $targetCount, + 'successCount' => 0, + 'failCount' => 0, + 'status' => 'pending', + 'statusText' => '进行中', + 'createTime' => date('Y-m-d H:i:s', $task['createTime']), + 'contentLibraryName' => '' + ]; + } + + // 应用筛选条件 + $filteredList = []; + foreach ($resultList as $item) { + // 推送类型筛选 + if (!empty($pushType)) { + if ($pushType === 'friend' && $item['pushTypeCode'] !== 'friend') { + continue; + } + if ($pushType === 'group' && $item['pushTypeCode'] !== 'group') { + continue; + } + if ($pushType === 'announcement' && $item['pushTypeCode'] !== 'announcement') { + continue; + } + } + + // 状态筛选 + if (!empty($status)) { + if ($status === 'success' && $item['status'] !== 'success') { + continue; + } + if ($status === 'progress') { + // 进行中:包括 partial 和 pending + if ($item['status'] !== 'partial' && $item['status'] !== 'pending') { + continue; + } + } + if ($status === 'failed' && $item['status'] !== 'failed') { + continue; + } + } + + $filteredList[] = $item; + } + + // 按创建时间倒序排序 + usort($filteredList, function($a, $b) { + return strtotime($b['createTime']) - strtotime($a['createTime']); + }); + + // 分页处理 + $total = count($filteredList); + $offset = ($page - 1) * $limit; + $list = array_slice($filteredList, $offset, $limit); return json([ 'code' => 200, @@ -2967,5 +3101,21 @@ class WorkbenchController extends Controller ]); } + /** + * 获取状态文本 + * @param string $status 状态码 + * @return string 状态文本 + */ + private function getStatusText($status) + { + $statusMap = [ + 'success' => '已完成', + 'partial' => '进行中', + 'pending' => '进行中', + 'failed' => '失败' + ]; + return $statusMap[$status] ?? '未知'; + } + } \ No newline at end of file diff --git a/Server/application/http/middleware/Jwt.php b/Server/application/http/middleware/Jwt.php new file mode 100644 index 00000000..4f2fb7d1 --- /dev/null +++ b/Server/application/http/middleware/Jwt.php @@ -0,0 +1,49 @@ + 401, + 'msg' => '未授权访问,缺少有效的身份凭证', + 'data' => null + ])->header(['Content-Type' => 'application/json; charset=utf-8']); + } + + $payload = JwtUtil::verifyToken($token); + if (!$payload) { + return json([ + 'code' => 401, + 'msg' => '授权已过期或无效', + 'data' => null + ])->header(['Content-Type' => 'application/json; charset=utf-8']); + } + + // 将用户信息附加到请求中 + $request->userInfo = $payload; + + // 写入日志 + Log::info('JWT认证通过', ['user_id' => $payload['id'] ?? 0, 'username' => $payload['username'] ?? '']); + + return $next($request); + } +} diff --git a/Server/composer.json b/Server/composer.json index 61d3fa7c..26ba92b7 100644 --- a/Server/composer.json +++ b/Server/composer.json @@ -1,33 +1,72 @@ { - "name": "topthink/think", - "description": "the new thinkphp framework", - "type": "project", - "keywords": [ - "framework", - "thinkphp", - "ORM" - ], - "homepage": "http://thinkphp.cn/", - "license": "Apache-2.0", - "authors": [ - { - "name": "liu21st", - "email": "liu21st@gmail.com" - } - ], - "require": { - "php": ">=5.4.0", - "topthink/framework": "5.0.*" + "name": "topthink/think", + "description": "the new thinkphp framework", + "type": "project", + "keywords": [ + "framework", + "thinkphp", + "ORM" + ], + "homepage": "http://thinkphp.cn/", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" }, - "autoload": { - "psr-4": { - "app\\": "application" - } - }, - "extra": { - "think-path": "thinkphp" - }, - "config": { - "preferred-install": "dist" + { + "name": "yunwuxin", + "email": "448901948@qq.com" } + ], + "require": { + "php": ">=5.6.0", + "topthink/framework": "5.1.41", + "topthink/think-installer": "2.*", + "topthink/think-captcha": "^2.0", + "topthink/think-helper": "^3.0", + "topthink/think-image": "^1.0", + "topthink/think-queue": "^2.0", + "topthink/think-worker": "^2.0", + "textalk/websocket": "^1.5", + "aliyuncs/oss-sdk-php": "^2.6", + "monolog/monolog": "^1.27", + "guzzlehttp/guzzle": "^6.5", + "overtrue/wechat": "~4.6", + "endroid/qr-code": "^3.9", + "phpoffice/phpspreadsheet": "^1.29", + "workerman/workerman": "^3.5", + "workerman/gateway-worker": "^3.0", + "hashids/hashids": "^2.0", + "khanamiryan/qrcode-detector-decoder": "^1.0", + "lizhichao/word": "^2.0", + "adbario/php-dot-notation": "^2.2" + }, + "require-dev": { + "symfony/var-dumper": "^3.4|^4.4", + "topthink/think-migration": "^2.0", + "phpunit/phpunit": "^5.0|^6.0" + }, + "autoload": { + "psr-4": { + "app\\": "application", + "Eison\\": "extend/Eison" + }, + "files": [ + "application/common.php" + ], + "classmap": [] + }, + "extra": { + "think-path": "thinkphp" + }, + "config": { + "preferred-install": "dist", + "allow-plugins": { + "topthink/think-installer": true, + "easywechat-composer/easywechat-composer": true + } + }, + "minimum-stability": "dev", + "prefer-stable": true } diff --git a/Server/config/middleware.php b/Server/config/middleware.php index 132ab645..b221b8a1 100644 --- a/Server/config/middleware.php +++ b/Server/config/middleware.php @@ -23,7 +23,8 @@ return [ // 全局中间件 'alias' => [ - 'cors' => 'app\\common\\middleware\\AllowCrossDomain' + 'cors' => 'app\\common\\middleware\\AllowCrossDomain', + 'jwt' => 'app\\http\\middleware\\Jwt' ], // 应用中间件 diff --git a/Server/crontab_tasks.md b/Server/crontab_tasks.md index f5849517..f7d1dfc8 100644 --- a/Server/crontab_tasks.md +++ b/Server/crontab_tasks.md @@ -81,6 +81,8 @@ # 消息提醒 */1 * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think kf:notice >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/kf_notice.log 2>&1 +# 客服评分 +0 2 * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think wechat:calculate-score >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/calculate_score.log 2>&1 @@ -107,4 +109,10 @@ ```bash crontab -l +``` + +```bash +- 本地: php think worker:server +- 线上: php think worker:server -d (自带守护进程,无需搭配Supervisor 之类的工具) +- php think worker:server stop php think worker:server status ``` \ No newline at end of file diff --git a/Server/extend/Eison/Utils/Helper/ArrHelper.php b/Server/extend/Eison/Utils/Helper/ArrHelper.php new file mode 100644 index 00000000..a659c82d --- /dev/null +++ b/Server/extend/Eison/Utils/Helper/ArrHelper.php @@ -0,0 +1,125 @@ +select(); $taskData = array_merge($taskData, $tasks); } - if ($taskData) { foreach ($taskData as $task) { $task_id = $task['task_id']; $task_info = $this->getCustomerAcquisitionTask($task_id); - if (empty($task_info['status']) || empty($task_info['reqConf']) || empty($task_info['reqConf']['device'])) { continue; } @@ -213,9 +212,86 @@ class Adapter implements WeChatServiceInterface continue; } - // 判断24h内加的好友数量,friend_task 先固定10个人 getLast24hAddedFriendsCount + // 根据健康分判断24h内加的好友数量限制 + $healthScoreService = new WechatAccountHealthScoreService(); + $healthScoreInfo = $healthScoreService->getHealthScore($accountId); + + // 如果健康分记录不存在,先计算一次 + if (empty($healthScoreInfo)) { + try { + $healthScoreService->calculateAndUpdate($accountId); + $healthScoreInfo = $healthScoreService->getHealthScore($accountId); + } catch (\Exception $e) { + Log::error("计算健康分失败 (accountId: {$accountId}): " . $e->getMessage()); + // 如果计算失败,使用默认值5作为兜底 + $maxAddFriendPerDay = 5; + } + } + + // 获取每日最大加人次数(基于健康分) + $maxAddFriendPerDay = $healthScoreInfo['maxAddFriendPerDay'] ?? 5; + + // 如果健康分为0或很低,不允许添加好友 + if ($maxAddFriendPerDay <= 0) { + Log::info("账号健康分过低,不允许添加好友 (accountId: {$accountId}, wechatId: {$wechatId}, healthScore: " . ($healthScoreInfo['healthScore'] ?? 0) . ")"); + continue; + } + + // 检查频繁暂停限制:首次频繁或再次频繁,暂停24小时 + $lastFrequentTime = $healthScoreInfo['lastFrequentTime'] ?? null; + $frequentCount = $healthScoreInfo['frequentCount'] ?? 0; + if (!empty($lastFrequentTime) && $frequentCount > 0) { + $frequentPauseHours = 24; // 频繁暂停24小时 + $frequentPauseTime = $lastFrequentTime + ($frequentPauseHours * 3600); + $currentTime = time(); + + if ($currentTime < $frequentPauseTime) { + $remainingHours = ceil(($frequentPauseTime - $currentTime) / 3600); + Log::info("账号频繁,暂停添加好友 (accountId: {$accountId}, wechatId: {$wechatId}, frequentCount: {$frequentCount}, 剩余暂停时间: {$remainingHours}小时)"); + continue; + } + } + + // 检查封号暂停限制:封号暂停72小时 + $isBanned = $healthScoreInfo['isBanned'] ?? 0; + if ($isBanned == 1) { + // 查询封号时间(从s2_wechat_message表查询最近一次封号消息) + $banMessage = Db::table('s2_wechat_message') + ->where('wechatAccountId', $accountId) + ->where('msgType', 10000) + ->where('content', 'like', '%你的账号被限制%') + ->where('isDeleted', 0) + ->order('createTime', 'desc') + ->find(); + + if (!empty($banMessage)) { + $banTime = $banMessage['createTime'] ?? 0; + $banPauseHours = 72; // 封号暂停72小时 + $banPauseTime = $banTime + ($banPauseHours * 3600); + $currentTime = time(); + + if ($currentTime < $banPauseTime) { + $remainingHours = ceil(($banPauseTime - $currentTime) / 3600); + Log::info("账号封号,暂停添加好友 (accountId: {$accountId}, wechatId: {$wechatId}, 剩余暂停时间: {$remainingHours}小时)"); + continue; + } + } + } + + // 判断今天添加的好友数量,使用健康分计算的每日最大加人次数 + // 优先使用今天添加的好友数量(更符合"每日"限制) + $todayAddedFriendsCount = $this->getTodayAddedFriendsCount($wechatId); + if ($todayAddedFriendsCount >= $maxAddFriendPerDay) { + Log::info("今天添加好友数量已达上限 (accountId: {$accountId}, wechatId: {$wechatId}, count: {$todayAddedFriendsCount}, max: {$maxAddFriendPerDay}, healthScore: " . ($healthScoreInfo['healthScore'] ?? 0) . ")"); + continue; + } + + // 如果今天添加数量未达上限,再检查24小时内的数量(作为额外保护) $last24hAddedFriendsCount = $this->getLast24hAddedFriendsCount($wechatId); - if ($last24hAddedFriendsCount >= 20) { + // 24小时内的限制可以稍微宽松一些,设置为每日限制的1.2倍(防止跨天累积) + $max24hLimit = (int)ceil($maxAddFriendPerDay * 1.2); + if ($last24hAddedFriendsCount >= $max24hLimit) { + Log::info("24小时内添加好友数量已达上限 (accountId: {$accountId}, wechatId: {$wechatId}, count: {$last24hAddedFriendsCount}, max24h: {$max24hLimit}, maxDaily: {$maxAddFriendPerDay})"); continue; } @@ -828,6 +904,7 @@ class Adapter implements WeChatServiceInterface if (empty($deviceIds)) { return []; } + $records = Db::table('s2_wechat_account') ->where('deviceAlive', 1) ->where('wechatAlive', 1) diff --git a/Server/sql.sql b/Server/sql.sql new file mode 100644 index 00000000..b49716bc --- /dev/null +++ b/Server/sql.sql @@ -0,0 +1,2301 @@ +/* + Navicat Premium Data Transfer + + Source Server : kr_存客宝 + Source Server Type : MySQL + Source Server Version : 50736 + Source Host : 56b4c23f6853c.gz.cdb.myqcloud.com:14413 + Source Schema : cunkebao_v3 + + Target Server Type : MySQL + Target Server Version : 50736 + File Encoding : 65001 + + Date: 12/11/2025 11:05:39 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for ck_administrator_permissions +-- ---------------------------- +DROP TABLE IF EXISTS `ck_administrator_permissions`; +CREATE TABLE `ck_administrator_permissions` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自动ID', + `adminId` int(10) UNSIGNED NULL DEFAULT NULL COMMENT '超管用户ID', + `permissions` json NULL COMMENT '权限对象', + `createTime` int(10) UNSIGNED NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(10) UNSIGNED NULL DEFAULT NULL COMMENT '更新时间', + `deleteTime` int(10) UNSIGNED NULL DEFAULT 0 COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '超级管理员权限配置表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_administrators +-- ---------------------------- +DROP TABLE IF EXISTS `ck_administrators`; +CREATE TABLE `ck_administrators` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `username` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '管理员名字', + `account` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '登录账号', + `password` char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '登录密码', + `status` tinyint(3) UNSIGNED NULL DEFAULT 1 COMMENT '1->可用,0->禁用', + `lastLoginTime` int(10) UNSIGNED NULL DEFAULT NULL COMMENT '最近登录时间', + `lastLoginIp` char(18) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '最近登录ip', + `authId` int(10) UNSIGNED NULL DEFAULT 0 COMMENT '权限id', + `createTime` int(10) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(10) NULL DEFAULT NULL COMMENT '更新时间', + `deleteTime` int(11) NULL DEFAULT 0 COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '超级管理员表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_ai_knowledge_base +-- ---------------------------- +DROP TABLE IF EXISTS `ck_ai_knowledge_base`; +CREATE TABLE `ck_ai_knowledge_base` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `typeId` int(11) NULL DEFAULT 1 COMMENT '类型id', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称', + `label` json NULL COMMENT '标签', + `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id', + `userId` int(11) NOT NULL DEFAULT 0 COMMENT '创建用户ID', + `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间', + `isDel` tinyint(2) NOT NULL DEFAULT 0 COMMENT '是否删除', + `delTime` int(11) NOT NULL DEFAULT 0 COMMENT '删除时间', + `documentId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '知识库文件id', + `fileUrl` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文件地址', + `size` int(10) NULL DEFAULT NULL COMMENT '文件大小', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ai知识库' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_ai_knowledge_base_type +-- ---------------------------- +DROP TABLE IF EXISTS `ck_ai_knowledge_base_type`; +CREATE TABLE `ck_ai_knowledge_base_type` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `type` tinyint(2) NULL DEFAULT 1 COMMENT '类型 0系统 1用户创建', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称', + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述', + `label` json NULL COMMENT '标签', + `prompt` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '提示词', + `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id', + `userId` int(11) NOT NULL DEFAULT 0 COMMENT '创建用户ID', + `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间', + `isDel` tinyint(2) NOT NULL DEFAULT 0 COMMENT '是否删除', + `delTime` int(11) NOT NULL DEFAULT 0 COMMENT '删除时间', + `status` tinyint(2) NULL DEFAULT 1 COMMENT '状态 1启用 0禁用', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ai知识库类型' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_ai_settings +-- ---------------------------- +DROP TABLE IF EXISTS `ck_ai_settings`; +CREATE TABLE `ck_ai_settings` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id', + `userId` int(11) NOT NULL DEFAULT 0 COMMENT '创建用户ID', + `config` json NULL COMMENT '配置信息', + `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间', + `isRelease` tinyint(2) NULL DEFAULT 0 COMMENT '是否发布 0未发布 1已发布', + `releaseTime` int(11) NULL DEFAULT NULL COMMENT '发布时间', + `botId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '智能体id', + `datasetId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '知识库id', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'AI配置' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_app_version +-- ---------------------------- +DROP TABLE IF EXISTS `ck_app_version`; +CREATE TABLE `ck_app_version` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `forceUpdate` tinyint(2) NULL DEFAULT 0 COMMENT '是否强制更新', + `version` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `downloadUrl` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `updateContent` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `createTime` int(11) NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_attachments +-- ---------------------------- +DROP TABLE IF EXISTS `ck_attachments`; +CREATE TABLE `ck_attachments` ( + `id` int(10) NOT NULL AUTO_INCREMENT COMMENT '自增长ID', + `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '资源名', + `hash_key` char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '资源hash校验值', + `server` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '存储服务商', + `source` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '资源地址', + `dl_count` int(10) NULL DEFAULT 0 COMMENT '下载次数', + `size` int(10) NULL DEFAULT 0 COMMENT '资源大小', + `suffix` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '资源类型', + `scene` tinyint(3) NOT NULL COMMENT '引用场景,获客海报1', + `create_at` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间', + `update_at` timestamp(0) NULL DEFAULT NULL COMMENT '修改时间', + `delete_at` timestamp(0) NULL DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_hash_key`(`hash_key`) USING BTREE, + INDEX `idx_server`(`server`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 481 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '附件表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_call_recording +-- ---------------------------- +DROP TABLE IF EXISTS `ck_call_recording`; +CREATE TABLE `ck_call_recording` ( + `id` int(11) NOT NULL, + `phone` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号', + `isCallOut` tinyint(2) NULL DEFAULT NULL COMMENT '是否外呼', + `companyId` int(11) NULL DEFAULT NULL, + `callType` tinyint(2) NULL DEFAULT NULL, + `beginTime` int(11) NULL DEFAULT NULL, + `endTime` int(11) NULL DEFAULT NULL, + `createTime` int(11) NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_id_phone_isCallOut_companyId`(`id`, `phone`, `isCallOut`, `companyId`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_company +-- ---------------------------- +DROP TABLE IF EXISTS `ck_company`; +CREATE TABLE `ck_company` ( + `id` int(11) UNSIGNED NOT NULL COMMENT '项目真实ID,非自增', + `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '项目名称', + `status` tinyint(1) UNSIGNED NULL DEFAULT 1 COMMENT '状态', + `tenantId` int(11) UNSIGNED NULL DEFAULT 242 COMMENT '触客宝租户ID', + `companyId` int(11) UNSIGNED NOT NULL COMMENT '触客宝部门ID', + `memo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + `createTime` int(11) NULL DEFAULT NULL, + `updateTime` int(11) NULL DEFAULT NULL, + `deleteTime` int(11) UNSIGNED NULL DEFAULT 0, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '部门表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_content_item +-- ---------------------------- +DROP TABLE IF EXISTS `ck_content_item`; +CREATE TABLE `ck_content_item` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `libraryId` int(11) NOT NULL COMMENT '所属内容库ID', + `type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'moment' COMMENT '内容类型(moment:朋友圈)', + `contentType` tinyint(1) NULL DEFAULT 0 COMMENT '0:未知 1:图片 2:链接 3:视频 4:文本 5:小程序 ', + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '内容标题', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '文本内容', + `contentAi` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '文本内容_Ai版', + `contentData` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '完整内容数据(JSON格式)', + `snsId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '朋友圈唯一标识', + `msgId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '群消息唯一标识', + `wechatId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信ID', + `friendId` int(11) NULL DEFAULT NULL COMMENT '微信好友ID', + `createMomentTime` bigint(20) NULL DEFAULT 0 COMMENT '朋友圈创建时间', + `createTime` int(11) NULL DEFAULT NULL COMMENT '记录创建时间', + `updateTime` int(11) NULL DEFAULT NULL COMMENT '记录更新时间', + `coverImage` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '封面图片URL', + `resUrls` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '资源URL列表(JSON格式)', + `urls` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '相对路径URL列表(JSON格式)', + `location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '地理位置名称', + `lat` decimal(10, 6) NULL DEFAULT 0.000000 COMMENT '纬度', + `lng` decimal(10, 6) NULL DEFAULT 0.000000 COMMENT '经度', + `status` tinyint(1) NULL DEFAULT 1 COMMENT '状态(0:禁用,1:启用)', + `isDel` tinyint(1) NULL DEFAULT 0 COMMENT '是否删除(0:否,1:是)', + `delTime` int(11) NULL DEFAULT 0 COMMENT '删除时间', + `wechatChatroomId` int(11) NULL DEFAULT NULL, + `senderNickname` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `createMessageTime` int(11) NULL DEFAULT NULL, + `comment` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '评论', + `sendTime` int(11) NULL DEFAULT 0 COMMENT '预计发布时间', + `sendTimes` int(11) NULL DEFAULT 0 COMMENT '实际发布时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_library`(`libraryId`) USING BTREE, + INDEX `idx_snsid`(`snsId`) USING BTREE, + INDEX `idx_wechatid`(`wechatId`) USING BTREE, + INDEX `idx_friendid`(`friendId`) USING BTREE, + INDEX `idx_create_time`(`createTime`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 5876 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '内容项目表-存储朋友圈采集数据' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_content_library +-- ---------------------------- +DROP TABLE IF EXISTS `ck_content_library`; +CREATE TABLE `ck_content_library` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `sourceType` tinyint(2) NOT NULL DEFAULT 1 COMMENT '类型 1好友 2群 3自定义', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '内容库名称', + `devices` json NULL COMMENT '设备列表,JSON格式:[{\"id\":1,\"name\":\"设备1\"},{\"id\":2,\"name\":\"设备2\"}]', + `catchType` json NULL COMMENT '采集类型', + `sourceFriends` json NULL COMMENT '选择的微信好友', + `sourceGroups` json NULL COMMENT '选择的微信群', + `groupMembers` json NULL COMMENT '选择的微信群的群成员', + `keywordInclude` json NULL COMMENT '包含的关键词', + `keywordExclude` json NULL COMMENT '排除的关键词', + `aiEnabled` tinyint(1) NULL DEFAULT 0 COMMENT '是否启用AI:0=禁用,1=启用', + `aiPrompt` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT 'AI提示词', + `timeEnabled` tinyint(1) NULL DEFAULT 0 COMMENT '是否启用时间限制:0=禁用,1=启用', + `timeStart` int(11) NULL DEFAULT NULL COMMENT '开始时间', + `timeEnd` int(11) NULL DEFAULT NULL COMMENT '结束时间', + `status` tinyint(1) NULL DEFAULT 0 COMMENT '状态:0=禁用,1=启用', + `userId` int(11) NOT NULL COMMENT '用户ID', + `companyId` int(11) NOT NULL COMMENT '公司ID', + `createTime` int(11) NULL DEFAULT 0 COMMENT '创建时间', + `updateTime` int(11) NULL DEFAULT 0 COMMENT '更新时间', + `isDel` tinyint(1) NULL DEFAULT 0 COMMENT '是否删除', + `deleteTime` int(11) NULL DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 87 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '内容库表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_coze_conversation +-- ---------------------------- +DROP TABLE IF EXISTS `ck_coze_conversation`; +CREATE TABLE `ck_coze_conversation` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主键', + `userId` int(11) NOT NULL DEFAULT 0 COMMENT '用户id', + `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id', + `conversation_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '对话ID', + `bot_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '机器人ID', + `created_at` int(11) NOT NULL DEFAULT 0 COMMENT '会话创建时间戳', + `meta_data` json NULL COMMENT '元数据', + `create_time` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间戳', + `update_time` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间戳', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `idx_conversation_id`(`conversation_id`) USING BTREE, + INDEX `idx_bot_id`(`bot_id`) USING BTREE, + INDEX `idx_create_time`(`create_time`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 39 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'Coze AI 会话表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_coze_message +-- ---------------------------- +DROP TABLE IF EXISTS `ck_coze_message`; +CREATE TABLE `ck_coze_message` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `chat_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '消息ID', + `conversation_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '会话ID', + `bot_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '机器人ID', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '消息内容', + `content_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'text' COMMENT '内容类型', + `role` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色', + `type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '消息类型', + `created_at` int(11) NOT NULL COMMENT '消息创建时间', + `updated_at` int(11) NOT NULL COMMENT '消息更新时间', + `create_time` int(11) NOT NULL COMMENT '记录创建时间', + `update_time` int(11) NOT NULL COMMENT '记录更新时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_chat_id`(`chat_id`) USING BTREE, + INDEX `idx_conversation_id`(`conversation_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 184 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '消息记录表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_coze_workspace +-- ---------------------------- +DROP TABLE IF EXISTS `ck_coze_workspace`; +CREATE TABLE `ck_coze_workspace` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `workspace_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '工作区ID', + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '工作区名称', + `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '工作区描述', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `workspace_id`(`workspace_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'Coze空间表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_customer_acquisition_task +-- ---------------------------- +DROP TABLE IF EXISTS `ck_customer_acquisition_task`; +CREATE TABLE `ck_customer_acquisition_task` ( + `id` int(10) NOT NULL AUTO_INCREMENT COMMENT '自增长ID', + `name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '计划名称', + `sceneId` int(11) NULL DEFAULT 1 COMMENT '场景ID', + `sceneConf` json NULL COMMENT '场景具体配置信息', + `reqConf` json NULL COMMENT '好友申请设置', + `msgConf` json NULL COMMENT '消息设置', + `tagConf` json NULL COMMENT '标签设置', + `userId` int(11) NULL DEFAULT 0 COMMENT '创建者', + `companyId` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '公司ID', + `status` tinyint(3) NOT NULL DEFAULT 0 COMMENT '状态 0禁用 1启用', + `createTime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间', + `updateTime` int(11) NULL DEFAULT NULL COMMENT '修改时间', + `deleteTime` int(11) NULL DEFAULT 0 COMMENT '删除时间', + `apiKey` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 162 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '获客计划表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_device +-- ---------------------------- +DROP TABLE IF EXISTS `ck_device`; +CREATE TABLE `ck_device` ( + `id` int(11) UNSIGNED NOT NULL COMMENT '设备真实ID,非自增', + `memo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '设备名称', + `imei` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '设备IMEI', + `deviceImei` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '设备本地IMEI', + `phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号', + `operatingSystem` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '操作系统版本', + `model` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '型号', + `brand` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '品牌', + `rooted` tinyint(1) NULL DEFAULT 0 COMMENT '是否root', + `xPosed` tinyint(1) NULL DEFAULT 0 COMMENT '是否安装xposed', + `softwareVersion` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '软件版本', + `extra` json NULL COMMENT '额外信息JSON', + `alive` tinyint(1) NULL DEFAULT 0 COMMENT '是否在线', + `companyId` int(11) NOT NULL COMMENT '公司ID', + `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间', + `deleteTime` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uni_id_imei`(`imei`, `id`) USING BTREE, + INDEX `idx_group`(`companyId`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '设备表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_device_handle_log +-- ---------------------------- +DROP TABLE IF EXISTS `ck_device_handle_log`; +CREATE TABLE `ck_device_handle_log` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `content` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '操作说明', + `deviceId` int(11) UNSIGNED NULL DEFAULT NULL COMMENT '设备id', + `userId` int(11) NULL DEFAULT NULL COMMENT '用户id', + `companyId` int(11) NULL DEFAULT NULL COMMENT '租户id', + `createTime` int(11) NULL DEFAULT NULL COMMENT '操作时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 304 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_device_taskconf +-- ---------------------------- +DROP TABLE IF EXISTS `ck_device_taskconf`; +CREATE TABLE `ck_device_taskconf` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID', + `deviceId` int(10) UNSIGNED NULL DEFAULT NULL COMMENT '设备ID', + `autoLike` tinyint(3) NULL DEFAULT 0 COMMENT '自动点赞', + `momentsSync` tinyint(3) UNSIGNED NULL DEFAULT 0 COMMENT '朋友圈同步', + `autoCustomerDev` tinyint(3) UNSIGNED NULL DEFAULT 0 COMMENT '自动开发客户', + `groupMessageDeliver` tinyint(3) UNSIGNED NULL DEFAULT 0 COMMENT '群消息推送', + `autoGroup` tinyint(3) UNSIGNED NULL DEFAULT 0 COMMENT '自动建群', + `autoAddFriend` tinyint(3) NULL DEFAULT 0 COMMENT '自动加好友', + `contentSync` tinyint(255) UNSIGNED NULL DEFAULT 0 COMMENT '朋友圈同步', + `aiChat` tinyint(3) UNSIGNED NULL DEFAULT 0 COMMENT 'AI 会话', + `autoReply` tinyint(3) UNSIGNED NULL DEFAULT 0 COMMENT '自动回复', + `companyId` int(10) NULL DEFAULT NULL COMMENT '公司ID', + `createTime` int(11) UNSIGNED NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '更新时间', + `deleteTime` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 29 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '设备任务配置表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_device_user +-- ---------------------------- +DROP TABLE IF EXISTS `ck_device_user`; +CREATE TABLE `ck_device_user` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID', + `companyId` int(11) UNSIGNED NOT NULL COMMENT '公司id', + `userId` int(11) UNSIGNED NOT NULL COMMENT '用户id', + `deviceId` int(11) UNSIGNED NOT NULL COMMENT '设备id', + `deleteTime` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 22 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '设备跟操盘手的关联关系' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_device_wechat_login +-- ---------------------------- +DROP TABLE IF EXISTS `ck_device_wechat_login`; +CREATE TABLE `ck_device_wechat_login` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `deviceId` int(11) NULL DEFAULT NULL COMMENT '设备ID', + `wechatId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信ID', + `alive` tinyint(3) UNSIGNED NULL DEFAULT 0 COMMENT '微信在线否', + `companyId` int(10) UNSIGNED NULL DEFAULT NULL COMMENT '租户ID', + `createTime` int(11) UNSIGNED NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(11) UNSIGNED NULL DEFAULT NULL COMMENT '更新时间', + `isTips` tinyint(2) NOT NULL DEFAULT 0 COMMENT '是否提示迁移', + PRIMARY KEY (`id`) USING BTREE, + INDEX `wechatId`(`wechatId`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 309 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '设备登录微信记录表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_flow_package +-- ---------------------------- +DROP TABLE IF EXISTS `ck_flow_package`; +CREATE TABLE `ck_flow_package` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '套餐名称', + `tag` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '套餐标签', + `originalPrice` decimal(10, 2) NOT NULL DEFAULT 0.00 COMMENT '原价', + `price` decimal(10, 2) NOT NULL DEFAULT 0.00 COMMENT '售价', + `monthlyFlow` int(11) NOT NULL DEFAULT 0 COMMENT '每月流量(人/月)', + `duration` int(11) NOT NULL DEFAULT 1 COMMENT '套餐时长(月)', + `privileges` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '套餐特权,多行文本存储', + `sort` int(11) NOT NULL DEFAULT 0 COMMENT '排序', + `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态: 0=禁用, 1=启用', + `isDel` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除: 0=否, 1=是', + `createTime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间', + `updateTime` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_name`(`name`) USING BTREE, + INDEX `idx_tag`(`tag`) USING BTREE, + INDEX `idx_is_del`(`isDel`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量套餐表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_flow_package_order +-- ---------------------------- +DROP TABLE IF EXISTS `ck_flow_package_order`; +CREATE TABLE `ck_flow_package_order` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `orderNo` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '订单编号', + `userId` int(11) NOT NULL DEFAULT 0 COMMENT '用户ID', + `packageId` int(11) NOT NULL DEFAULT 0 COMMENT '套餐ID', + `packageName` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '套餐名称', + `amount` decimal(10, 2) NOT NULL DEFAULT 0.00 COMMENT '订单金额', + `duration` int(11) NOT NULL DEFAULT 1 COMMENT '购买时长(月)', + `payStatus` tinyint(1) NOT NULL DEFAULT 0 COMMENT '支付状态: 0=未支付, 1=已支付,10=无需支付', + `payTime` int(11) NOT NULL DEFAULT 0 COMMENT '支付时间', + `payType` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '支付方式: wechat=微信, alipay=支付宝,nopay=无需支付', + `transactionId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '第三方支付交易号', + `status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '订单状态: 0=待支付, 1=已支付, 2=已取消, 3=已退款', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '备注', + `isDel` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除: 0=否, 1=是', + `createTime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间', + `updateTime` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_order_no`(`orderNo`) USING BTREE, + INDEX `idx_user_id`(`userId`) USING BTREE, + INDEX `idx_package_id`(`packageId`) USING BTREE, + INDEX `idx_status`(`status`) USING BTREE, + INDEX `idx_pay_status`(`payStatus`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 37 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '套餐订单表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_flow_usage_record +-- ---------------------------- +DROP TABLE IF EXISTS `ck_flow_usage_record`; +CREATE TABLE `ck_flow_usage_record` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `userId` int(11) NOT NULL DEFAULT 0 COMMENT '用户ID', + `packageId` int(11) NOT NULL DEFAULT 0 COMMENT '套餐ID', + `userPackageId` int(11) NOT NULL DEFAULT 0 COMMENT '用户套餐ID', + `taskId` int(11) NOT NULL DEFAULT 0 COMMENT '关联任务ID', + `phone` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '微信号', + `usageAmount` int(11) NOT NULL DEFAULT 0 COMMENT '使用量(人)', + `usageType` tinyint(1) NOT NULL DEFAULT 1 COMMENT '使用类型: 1=添加好友, 2=群发消息, 3=其他', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '备注', + `createTime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间', + `updateTime` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_user_id`(`userId`) USING BTREE, + INDEX `idx_package_id`(`packageId`) USING BTREE, + INDEX `idx_user_package_id`(`userPackageId`) USING BTREE, + INDEX `idx_task_id`(`taskId`) USING BTREE, + INDEX `idx_phone`(`phone`) USING BTREE, + INDEX `idx_create_time`(`createTime`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量使用记录表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_jd_promotion_site +-- ---------------------------- +DROP TABLE IF EXISTS `ck_jd_promotion_site`; +CREATE TABLE `ck_jd_promotion_site` ( + `id` bigint(11) NOT NULL, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `jdSocialMediaId` bigint(11) NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_jd_social_media +-- ---------------------------- +DROP TABLE IF EXISTS `ck_jd_social_media`; +CREATE TABLE `ck_jd_social_media` ( + `id` bigint(11) NOT NULL, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `appkey` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `secretkey` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_kf_ai_push +-- ---------------------------- +DROP TABLE IF EXISTS `ck_kf_ai_push`; +CREATE TABLE `ck_kf_ai_push` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '推送名称', + `tags` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '目标用户标签(JSON数组格式)', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '推送内容(支持变量:{客户名称}{产品功能}{核心价值}等)', + `pushTiming` tinyint(4) NOT NULL DEFAULT 1 COMMENT '推送时机:1=立即推送,2=最佳时机(AI决定),3=定时推送', + `scheduledTime` int(11) NOT NULL DEFAULT 0 COMMENT '定时推送时间(时间戳,仅当pushTiming=3时有效)', + `status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '启用状态:0=禁用,1=启用', + `successRate` decimal(5, 2) NOT NULL DEFAULT 0.00 COMMENT '成功率(百分比,保留两位小数)', + `userId` int(11) NOT NULL DEFAULT 0 COMMENT '用户ID', + `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司ID', + `isDel` tinyint(4) NOT NULL DEFAULT 0 COMMENT '删除标记:0=未删除,1=已删除', + `createTime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间(时间戳)', + `updateTime` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间(时间戳)', + `delTime` int(11) NOT NULL DEFAULT 0 COMMENT '删除时间(时间戳)', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_company_user`(`companyId`, `userId`) USING BTREE, + INDEX `idx_pushTiming`(`pushTiming`) USING BTREE, + INDEX `idx_status`(`status`) USING BTREE, + INDEX `idx_isDel`(`isDel`) USING BTREE, + INDEX `idx_scheduledTime`(`scheduledTime`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'AI智能推送表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_kf_ai_push_record +-- ---------------------------- +DROP TABLE IF EXISTS `ck_kf_ai_push_record`; +CREATE TABLE `ck_kf_ai_push_record` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `pushId` int(11) NOT NULL DEFAULT 0 COMMENT '推送ID', + `userId` int(11) NOT NULL DEFAULT 0 COMMENT '用户ID', + `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司ID', + `wechatAccountId` int(11) NOT NULL DEFAULT 0 COMMENT '微信账号ID', + `friendIdOrGroupId` int(11) NOT NULL DEFAULT 0 COMMENT '好友ID或群ID', + `isSend` tinyint(4) NOT NULL DEFAULT 0 COMMENT '是否发送:0=未发送,1=已发送', + `sendTime` int(11) NOT NULL DEFAULT 0 COMMENT '发送时间(时间戳)', + `receiveTime` int(11) NOT NULL DEFAULT 0 COMMENT '接收时间(时间戳)', + `createTime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间(时间戳)', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_pushId`(`pushId`) USING BTREE, + INDEX `idx_company`(`companyId`) USING BTREE, + INDEX `idx_user`(`userId`) USING BTREE, + INDEX `idx_createTime`(`createTime`) USING BTREE, + INDEX `idx_isSend`(`isSend`) USING BTREE, + INDEX `idx_wechatAccount`(`wechatAccountId`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'AI智能推送记录表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_kf_auto_greetings +-- ---------------------------- +DROP TABLE IF EXISTS `ck_kf_auto_greetings`; +CREATE TABLE `ck_kf_auto_greetings` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '规则名称', + `trigger` tinyint(4) NOT NULL DEFAULT 0 COMMENT '触发类型:1=好友首次添加,2=首次发消息,3=时间触发,4=关键词触发,5=生日触发,6=自定义', + `condition` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '具体条件(JSON格式)', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '问候内容', + `level` int(11) NOT NULL DEFAULT 0 COMMENT '优先级(数字越小优先级越高)', + `status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '启用状态:0=禁用,1=启用', + `usageCount` int(11) NOT NULL DEFAULT 0 COMMENT '使用次数', + `userId` int(11) NOT NULL DEFAULT 0 COMMENT '用户ID', + `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司ID', + `is_template` tinyint(4) NOT NULL DEFAULT 0 COMMENT '是否模板:0=否,1=是', + `isDel` tinyint(4) NOT NULL DEFAULT 0 COMMENT '删除标记:0=未删除,1=已删除', + `createTime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间(时间戳)', + `updateTime` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间(时间戳)', + `delTime` int(11) NOT NULL DEFAULT 0 COMMENT '删除时间(时间戳)', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_company_user`(`companyId`, `userId`) USING BTREE, + INDEX `idx_trigger`(`trigger`) USING BTREE, + INDEX `idx_status`(`status`) USING BTREE, + INDEX `idx_isDel`(`isDel`) USING BTREE, + INDEX `idx_level`(`level`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '问候规则表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_kf_auto_greetings_record +-- ---------------------------- +DROP TABLE IF EXISTS `ck_kf_auto_greetings_record`; +CREATE TABLE `ck_kf_auto_greetings_record` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `autoId` int(11) NOT NULL DEFAULT 0 COMMENT '规则ID', + `userId` int(11) NOT NULL DEFAULT 0 COMMENT '用户ID', + `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司ID', + `wechatAccountId` int(11) NOT NULL DEFAULT 0 COMMENT '微信账号ID', + `friendIdOrGroupId` int(11) NOT NULL DEFAULT 0 COMMENT '好友ID或群ID', + `isSend` tinyint(4) NOT NULL DEFAULT 0 COMMENT '是否发送:0=未发送,1=已发送', + `sendTime` int(11) NOT NULL DEFAULT 0 COMMENT '发送时间(时间戳)', + `receiveTime` int(11) NOT NULL DEFAULT 0 COMMENT '接收时间(时间戳)', + `createTime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间(时间戳)', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_autoId`(`autoId`) USING BTREE, + INDEX `idx_company`(`companyId`) USING BTREE, + INDEX `idx_user`(`userId`) USING BTREE, + INDEX `idx_createTime`(`createTime`) USING BTREE, + INDEX `idx_isSend`(`isSend`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '问候规则使用记录表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_kf_follow_up +-- ---------------------------- +DROP TABLE IF EXISTS `ck_kf_follow_up`; +CREATE TABLE `ck_kf_follow_up` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id', + `userId` int(11) NOT NULL DEFAULT 0 COMMENT '创建用户ID', + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标题', + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述', + `friendId` int(12) NULL DEFAULT NULL COMMENT '好友id', + `type` tinyint(2) NULL DEFAULT 0 COMMENT '类型 0其他 1电话回访 2发送消息 3安排会议 4发送邮件', + `reminderTime` int(12) NULL DEFAULT NULL COMMENT '提醒时间', + `isRemind` tinyint(2) NULL DEFAULT 0 COMMENT '是否提醒', + `isProcess` tinyint(2) NULL DEFAULT 0 COMMENT '是否处理', + `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_companyId`(`companyId`) USING BTREE, + INDEX `idx_userId`(`userId`) USING BTREE, + INDEX `idx_level`(`type`) USING BTREE, + INDEX `idx_isRemind`(`isRemind`) USING BTREE, + INDEX `idx_isProcess`(`isProcess`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 14 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '跟进提醒' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_kf_friend_settings +-- ---------------------------- +DROP TABLE IF EXISTS `ck_kf_friend_settings`; +CREATE TABLE `ck_kf_friend_settings` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id', + `userId` int(11) NOT NULL DEFAULT 0 COMMENT '创建用户ID', + `type` tinyint(2) NULL DEFAULT 0 COMMENT '匹配类型 0人工接待 1AI辅助 2AI接管', + `wechatAccountId` int(11) NULL DEFAULT NULL COMMENT '客服id', + `friendId` int(11) NULL DEFAULT NULL COMMENT '好友id', + `conversationId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '会话id', + `conversationTime` int(11) NULL DEFAULT NULL COMMENT '会话创建时间', + `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_companyId`(`companyId`) USING BTREE, + INDEX `idx_userId`(`userId`) USING BTREE, + INDEX `idx_wechatAccountId`(`wechatAccountId`) USING BTREE, + INDEX `idx_friendId`(`friendId`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 37 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '好友AI配置' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_kf_keywords +-- ---------------------------- +DROP TABLE IF EXISTS `ck_kf_keywords`; +CREATE TABLE `ck_kf_keywords` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id', + `userId` int(11) NOT NULL DEFAULT 0 COMMENT '创建用户ID', + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标题', + `keywords` json NULL COMMENT '关键词', + `type` tinyint(2) NULL DEFAULT NULL COMMENT '匹配类型 0模糊 1精确', + `replyType` tinyint(2) NULL DEFAULT NULL COMMENT '回复类型 0素材回复 1自定义', + `content` json NULL COMMENT '自定义内容', + `metailGroups` json NULL COMMENT '素材id', + `status` tinyint(2) NULL DEFAULT NULL COMMENT '状态 0停用 1启用', + `level` tinyint(2) NULL DEFAULT 0 COMMENT '等级 0低优先级 1中优先级 2高优先级', + `isDel` tinyint(2) NULL DEFAULT 0 COMMENT '是否删除', + `delTime` int(12) NULL DEFAULT NULL COMMENT '删除时间', + `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 16 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '关键词管理' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_kf_material +-- ---------------------------- +DROP TABLE IF EXISTS `ck_kf_material`; +CREATE TABLE `ck_kf_material` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id', + `userId` int(11) NOT NULL DEFAULT 0 COMMENT '创建用户ID', + `title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标题', + `content` json NULL COMMENT '内容', + `cover` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '封面', + `status` tinyint(2) NULL DEFAULT NULL COMMENT '状态 0停用 1启用', + `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间', + `isDel` tinyint(2) NULL DEFAULT 0 COMMENT '是否删除', + `delTime` int(12) NULL DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 19 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '素材库管理' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_kf_moments +-- ---------------------------- +DROP TABLE IF EXISTS `ck_kf_moments`; +CREATE TABLE `ck_kf_moments` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id', + `userId` int(11) NOT NULL DEFAULT 0 COMMENT '创建用户ID', + `sendData` json NULL COMMENT '发送的具体信息', + `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间', + `isSend` tinyint(2) NULL DEFAULT 0 COMMENT '是否发送 0否 1是', + `sendTime` int(11) NULL DEFAULT NULL COMMENT '发送时间', + `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间', + `isDel` tinyint(2) NULL DEFAULT 0 COMMENT '是否删除', + `delTime` int(12) NULL DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 14 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '客服端发布朋友圈记录' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_kf_moments_settings +-- ---------------------------- +DROP TABLE IF EXISTS `ck_kf_moments_settings`; +CREATE TABLE `ck_kf_moments_settings` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id', + `userId` int(11) NOT NULL DEFAULT 0 COMMENT '创建用户ID', + `wechatId` int(12) NULL DEFAULT NULL COMMENT '微信客服id', + `max` int(11) NULL DEFAULT 5 COMMENT '每日上限', + `sendNum` int(11) NULL DEFAULT 0 COMMENT '今日发送次数', + `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '客服朋友圈配置信息' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_kf_notice +-- ---------------------------- +DROP TABLE IF EXISTS `ck_kf_notice`; +CREATE TABLE `ck_kf_notice` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `type` tinyint(2) NULL DEFAULT NULL COMMENT '通知类型 1代办事项 2跟进提醒 ', + `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id', + `userId` int(11) NOT NULL DEFAULT 0 COMMENT '创建用户ID', + `bindId` int(11) NULL DEFAULT NULL COMMENT '绑定的id', + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标题', + `message` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '通知消息', + `isRead` tinyint(2) NULL DEFAULT 0 COMMENT '是否读取', + `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间', + `readTime` int(12) NULL DEFAULT NULL COMMENT '读取时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 246 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '通知消息' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_kf_questions +-- ---------------------------- +DROP TABLE IF EXISTS `ck_kf_questions`; +CREATE TABLE `ck_kf_questions` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id', + `userId` int(11) NOT NULL DEFAULT 0 COMMENT '创建用户ID', + `type` tinyint(2) NULL DEFAULT 0 COMMENT '匹配类型 0模糊 1精确', + `questions` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '问题', + `answers` json NULL COMMENT '答案', + `status` tinyint(2) NULL DEFAULT 1 COMMENT '状态 0禁用 1启用', + `isDel` tinyint(1) NULL DEFAULT 0 COMMENT '是否删除', + `deleteTime` int(12) NULL DEFAULT NULL COMMENT '删除时间', + `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_companyId`(`companyId`) USING BTREE, + INDEX `idx_userId`(`userId`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'AI问答' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_kf_reply +-- ---------------------------- +DROP TABLE IF EXISTS `ck_kf_reply`; +CREATE TABLE `ck_kf_reply` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `groupId` int(11) NULL DEFAULT NULL, + `userId` int(11) NULL DEFAULT NULL, + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `msgType` tinyint(2) NULL DEFAULT NULL, + `content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `createTime` int(11) NULL DEFAULT NULL, + `lastUpdateTime` int(11) NULL DEFAULT NULL, + `sortIndex` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间', + `isDel` tinyint(2) NULL DEFAULT 0 COMMENT '是否删除', + `delTime` int(12) NULL DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 130746 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '快捷回复' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_kf_reply_group +-- ---------------------------- +DROP TABLE IF EXISTS `ck_kf_reply_group`; +CREATE TABLE `ck_kf_reply_group` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `userId` int(11) NULL DEFAULT 0, + `companyId` int(11) NULL DEFAULT 0, + `groupName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `sortIndex` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `parentId` int(11) NULL DEFAULT NULL, + `replyType` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `replys` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间', + `isDel` tinyint(2) NULL DEFAULT 0 COMMENT '是否删除', + `delTime` int(12) NULL DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 21898 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '快捷回复分组' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_kf_sensitive_word +-- ---------------------------- +DROP TABLE IF EXISTS `ck_kf_sensitive_word`; +CREATE TABLE `ck_kf_sensitive_word` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id', + `userId` int(11) NOT NULL DEFAULT 0 COMMENT '创建用户ID', + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标题', + `keywords` json NULL COMMENT '关键词', + `content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '替换内容/警告内容', + `operation` tinyint(2) NULL DEFAULT NULL COMMENT '操作 0不操作 1替换 2删除 3警告 4禁止发送', + `status` tinyint(2) NULL DEFAULT NULL COMMENT '状态 0停用 1启用', + `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间', + `isDel` tinyint(2) NULL DEFAULT 0 COMMENT '是否删除', + `delTime` int(12) NULL DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '敏感词管理' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_kf_to_do +-- ---------------------------- +DROP TABLE IF EXISTS `ck_kf_to_do`; +CREATE TABLE `ck_kf_to_do` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id', + `userId` int(11) NOT NULL DEFAULT 0 COMMENT '创建用户ID', + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标题', + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述', + `friendId` int(12) NULL DEFAULT NULL COMMENT '好友id', + `level` tinyint(2) NULL DEFAULT 0 COMMENT '提示等级 0低优先级 1中优先级 2高优先级 3紧急', + `reminderTime` int(12) NULL DEFAULT NULL COMMENT '提醒时间', + `isRemind` tinyint(2) NULL DEFAULT 0 COMMENT '是否提醒', + `isProcess` tinyint(2) NULL DEFAULT 0 COMMENT '是否处理', + `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_companyId`(`companyId`) USING BTREE, + INDEX `idx_userId`(`userId`) USING BTREE, + INDEX `idx_level`(`level`) USING BTREE, + INDEX `idx_isRemind`(`isRemind`) USING BTREE, + INDEX `idx_isProcess`(`isProcess`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '待办事项' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_menus +-- ---------------------------- +DROP TABLE IF EXISTS `ck_menus`; +CREATE TABLE `ck_menus` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '菜单ID', + `title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单名称', + `path` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '路由路径', + `icon` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '图标名称', + `parentId` int(11) NOT NULL DEFAULT 0 COMMENT '父菜单ID,0表示顶级菜单', + `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态:1启用,0禁用', + `sort` int(11) NOT NULL DEFAULT 0 COMMENT '排序,数值越小越靠前', + `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_parent_id`(`parentId`) USING BTREE, + INDEX `idx_status`(`status`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 15 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统菜单表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_order +-- ---------------------------- +DROP TABLE IF EXISTS `ck_order`; +CREATE TABLE `ck_order` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `mchId` int(11) NULL DEFAULT NULL COMMENT '门店号', + `companyId` int(11) UNSIGNED NOT NULL, + `userId` int(11) NULL DEFAULT NULL, + `orderType` tinyint(2) NULL DEFAULT NULL COMMENT '订单类型 1购买算力', + `status` tinyint(1) UNSIGNED NULL DEFAULT 0 COMMENT '支付状态 0待支付 1已付款 2已退款 3付款失败', + `goodsId` int(11) NULL DEFAULT 0 COMMENT '商品id', + `goodsName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '商品名称', + `goodsSpecs` json NULL COMMENT '商品规格', + `money` int(11) NULL DEFAULT 0 COMMENT '金额 单位分', + `orderNo` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '订单号', + `ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `nonceStr` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '随机字符串', + `createTime` int(11) NULL DEFAULT NULL, + `payType` tinyint(2) NULL DEFAULT NULL COMMENT '支付类型 1微信 2支付宝', + `payTime` int(11) NULL DEFAULT NULL, + `payInfo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '错误信息', + `deleteTime` int(11) UNSIGNED NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 79 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_plan_scene +-- ---------------------------- +DROP TABLE IF EXISTS `ck_plan_scene`; +CREATE TABLE `ck_plan_scene` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID', + `name` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '场景名称', + `description` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述', + `image` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '图片icon', + `status` tinyint(3) NULL DEFAULT NULL COMMENT '状态', + `sort` tinyint(3) NULL DEFAULT NULL, + `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(11) NULL DEFAULT NULL COMMENT '修改时间', + `deleteTime` int(11) NULL DEFAULT 0 COMMENT '删除时间', + `scenarioTags` json NULL COMMENT '标签', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '获客场景' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_plan_tags +-- ---------------------------- +DROP TABLE IF EXISTS `ck_plan_tags`; +CREATE TABLE `ck_plan_tags` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID', + `tagName` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标签名', + `companyId` int(10) UNSIGNED NULL DEFAULT NULL COMMENT '部门ID', + `createTime` int(11) UNSIGNED NULL DEFAULT NULL COMMENT '创建时间', + `deleteTime` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量标签表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_task_customer +-- ---------------------------- +DROP TABLE IF EXISTS `ck_task_customer`; +CREATE TABLE `ck_task_customer` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `task_id` int(11) NOT NULL, + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '客户姓名', + `source` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '来源', + `phone` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', + `remark` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + `tags` json NULL, + `siteTags` json NULL COMMENT '站内标签', + `processed_wechat_ids` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', + `status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '0-未处理,1-已处理/添加中,2-~~添加成功~~ ~~已添加~~添加任务成功 3-添加失败 4-已通过-已发消息', + `fail_reason` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '', + `addTime` int(11) NOT NULL DEFAULT 0 COMMENT '添加时间', + `passTime` int(11) NOT NULL DEFAULT 0 COMMENT '通过时间', + `createTime` int(11) NOT NULL DEFAULT 0, + `updateTime` int(11) NOT NULL DEFAULT 0, + PRIMARY KEY (`id`) USING BTREE, + INDEX `task_id`(`task_id`) USING BTREE, + INDEX `addTime`(`addTime`) USING BTREE, + INDEX `passTime`(`passTime`) USING BTREE, + INDEX `updateTime`(`updateTime`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 24192 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_tokens_company +-- ---------------------------- +DROP TABLE IF EXISTS `ck_tokens_company`; +CREATE TABLE `ck_tokens_company` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id', + `tokens` bigint(100) NULL DEFAULT NULL, + `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '公司算力账户' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_tokens_package +-- ---------------------------- +DROP TABLE IF EXISTS `ck_tokens_package`; +CREATE TABLE `ck_tokens_package` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称', + `tokens` int(12) NULL DEFAULT NULL, + `price` int(12) NULL DEFAULT NULL COMMENT '售价 单位分', + `originalPrice` int(12) NULL DEFAULT NULL COMMENT '原价 单位分', + `description` json NULL COMMENT '描述', + `sort` int(12) NULL DEFAULT 50 COMMENT '排序', + `isTrial` tinyint(2) NULL DEFAULT 0 COMMENT '是否试用', + `isRecommend` tinyint(2) NULL DEFAULT 0 COMMENT '是否推荐', + `isHot` tinyint(2) NULL DEFAULT 0 COMMENT '是否热门', + `isVip` tinyint(2) NULL DEFAULT 0 COMMENT '是否VIP', + `status` tinyint(2) NULL DEFAULT 0 COMMENT '状态 0停用 1启用', + `isDel` tinyint(2) NULL DEFAULT 0 COMMENT '是否删除', + `delTime` int(12) NULL DEFAULT NULL COMMENT '删除时间', + `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(12) NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'token套餐' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_tokens_record +-- ---------------------------- +DROP TABLE IF EXISTS `ck_tokens_record`; +CREATE TABLE `ck_tokens_record` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `companyId` int(11) NOT NULL DEFAULT 0 COMMENT '公司id', + `userId` int(11) NOT NULL DEFAULT 0 COMMENT '创建用户ID', + `wechatAccountId` int(11) NULL DEFAULT NULL COMMENT '客服id', + `friendIdOrGroupId` int(11) NULL DEFAULT NULL COMMENT '好友id或者群id', + `form` tinyint(2) NULL DEFAULT 0 COMMENT '来源 0未知 1好友聊天 2群聊天 3群公告 4商家 5充值', + `type` tinyint(2) NULL DEFAULT 0 COMMENT '类型 0减少 1增加', + `tokens` int(11) NULL DEFAULT NULL COMMENT '消耗tokens', + `balanceTokens` int(11) NULL DEFAULT NULL COMMENT '剩余tokens', + `remarks` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + `createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 236 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '算力明细记录' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_traffic_order +-- ---------------------------- +DROP TABLE IF EXISTS `ck_traffic_order`; +CREATE TABLE `ck_traffic_order` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `companyId` int(10) UNSIGNED NULL DEFAULT NULL, + `identifier` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '流量池用户', + `createTime` int(11) UNSIGNED NULL DEFAULT NULL COMMENT '创建时间', + `isDel` tinyint(2) NULL DEFAULT 0, + `deleteTime` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '删除时间', + `orderno` varchar(0) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '订单编号', + `userId` int(11) NULL DEFAULT NULL, + `storeId` int(11) NULL DEFAULT NULL COMMENT '门店id', + `goddsName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '商品价格', + `price` int(10) NULL DEFAULT NULL COMMENT '商品价格', + `actualPay` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '实际支付', + `ownerWechatId` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_traffic_pool +-- ---------------------------- +DROP TABLE IF EXISTS `ck_traffic_pool`; +CREATE TABLE `ck_traffic_pool` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID', + `identifier` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '流量标识,可以是手机号、微信号', + `mobile` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号', + `wechatId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信ID', + `createTime` int(10) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(10) NULL DEFAULT NULL COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uni_identifier`(`identifier`) USING BTREE, + INDEX `idx_wechatId`(`wechatId`) USING BTREE, + INDEX `idx_mobile`(`mobile`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 959687 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量池' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_traffic_profile +-- ---------------------------- +DROP TABLE IF EXISTS `ck_traffic_profile`; +CREATE TABLE `ck_traffic_profile` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID', + `identifier` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '流量标识,可以是手机号、微信号', + `nickname` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '平台昵称', + `avatar` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '平台头像', + `gender` tinyint(3) NULL DEFAULT 0 COMMENT '平台性别', + `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '平台手机号', + `platformId` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '平台Id', + `createTime` int(10) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(10) NULL DEFAULT NULL COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uni_identifier`(`identifier`) USING BTREE, + INDEX `idx_mobile`(`phone`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 196606 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量池用户个人信息' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_traffic_source +-- ---------------------------- +DROP TABLE IF EXISTS `ck_traffic_source`; +CREATE TABLE `ck_traffic_source` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID', + `type` tinyint(2) NULL DEFAULT 1 COMMENT '流量来源 0其他 1好友 2群 3场景', + `identifier` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '流量标识', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称', + `status` tinyint(3) NULL DEFAULT 1 COMMENT '1待处理,2处理中,3已通过,4已拒绝,5已过期,6已取消 -3已删除(同步 tk_friend_task 表的 status)', + `sourceId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '来源id(微信id或群id)', + `fromd` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '流量来源(群聊名称)', + `sceneId` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '场景ID', + `companyId` int(11) NULL DEFAULT 0 COMMENT '账号所属项目id', + `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(11) NULL DEFAULT NULL COMMENT '修改时间', + `R` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0', + `F` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0', + `M` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_identifier_sourceId_sceneId`(`identifier`, `sourceId`, `sceneId`) USING BTREE, + INDEX `idx_identifier`(`identifier`) USING BTREE, + INDEX `idx_companyId`(`companyId`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 564508 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量来源' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_traffic_source_package +-- ---------------------------- +DROP TABLE IF EXISTS `ck_traffic_source_package`; +CREATE TABLE `ck_traffic_source_package` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `userId` int(10) NULL DEFAULT NULL COMMENT '用户id', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称', + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述', + `pic` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '图标', + `companyId` int(11) NULL DEFAULT NULL COMMENT '账号所属项目id', + `matchingRules` json NULL COMMENT '匹配规则', + `isSys` tinyint(2) NULL DEFAULT 0 COMMENT '是否系统只有', + `isDel` tinyint(2) NULL DEFAULT 0 COMMENT '是否删除', + `updateTime` int(11) NULL DEFAULT NULL, + `createTime` int(11) NULL DEFAULT 0 COMMENT '创建时间', + `deleteTime` int(11) NULL DEFAULT 0 COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `companyId`(`companyId`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量池包' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_traffic_source_package_item +-- ---------------------------- +DROP TABLE IF EXISTS `ck_traffic_source_package_item`; +CREATE TABLE `ck_traffic_source_package_item` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `packageId` int(10) NULL DEFAULT NULL COMMENT '流量包id', + `companyId` int(11) NULL DEFAULT NULL COMMENT '账号所属项目id', + `identifier` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '流量标识,可以是手机号、微信号', + `isDel` tinyint(2) NULL DEFAULT 0 COMMENT '是否删除', + `createTime` int(11) NULL DEFAULT 0 COMMENT '创建时间', + `deleteTime` int(10) NULL DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_packageId_companyId_identifier_isDel`(`packageId`, `companyId`, `identifier`, `isDel`) USING BTREE, + INDEX `packageId`(`packageId`) USING BTREE, + INDEX `companyId`(`companyId`) USING BTREE, + INDEX `identifier`(`identifier`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 34 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_traffic_tag +-- ---------------------------- +DROP TABLE IF EXISTS `ck_traffic_tag`; +CREATE TABLE `ck_traffic_tag` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID', + `tagId` int(11) NULL DEFAULT NULL, + `tagName` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标签名', + `tagType` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标签值', + `tagValue` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标签值', + `companyId` int(10) UNSIGNED NULL DEFAULT NULL COMMENT '部门ID', + `trafficPoolId` int(10) NULL DEFAULT NULL COMMENT '流量池用户id traffic_pool的主键', + `createTime` int(11) UNSIGNED NULL DEFAULT NULL COMMENT '创建时间', + `isDel` tinyint(2) NULL DEFAULT NULL, + `deleteTime` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量标签表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_user_flow_package +-- ---------------------------- +DROP TABLE IF EXISTS `ck_user_flow_package`; +CREATE TABLE `ck_user_flow_package` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `userId` int(11) NOT NULL DEFAULT 0 COMMENT '用户ID', + `packageId` int(11) NOT NULL DEFAULT 0 COMMENT '套餐ID', + `orderId` int(11) NOT NULL DEFAULT 0 COMMENT '关联订单ID', + `duration` int(11) NOT NULL DEFAULT 1 COMMENT '套餐时长(月)', + `totalFlow` int(11) NOT NULL DEFAULT 0 COMMENT '总流量(人)', + `usedFlow` int(11) NOT NULL DEFAULT 0 COMMENT '已使用流量(人)', + `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态: 0=无效, 1=有效', + `startTime` int(11) NOT NULL DEFAULT 0 COMMENT '开始时间', + `expireTime` int(11) NOT NULL DEFAULT 0 COMMENT '到期时间', + `createTime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间', + `updateTime` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_user_id`(`userId`) USING BTREE, + INDEX `idx_package_id`(`packageId`) USING BTREE, + INDEX `idx_order_id`(`orderId`) USING BTREE, + INDEX `idx_expire_time`(`expireTime`) USING BTREE, + INDEX `idx_status`(`status`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户流量套餐表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_user_log +-- ---------------------------- +DROP TABLE IF EXISTS `ck_user_log`; +CREATE TABLE `ck_user_log` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `userId` int(11) NOT NULL DEFAULT 0 COMMENT '用户ID', + `userName` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名', + `action` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '操作类型', + `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '操作描述', + `ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT 'IP地址', + `userAgent` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户设备信息', + `requestMethod` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求方法', + `requestUrl` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求URL', + `requestData` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '请求数据', + `responseCode` int(11) NULL DEFAULT 0 COMMENT '响应状态码', + `responseMsg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '响应消息', + `createTime` int(11) NULL DEFAULT 0 COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_user_id`(`userId`) USING BTREE, + INDEX `idx_user_name`(`userName`) USING BTREE, + INDEX `idx_action`(`action`) USING BTREE, + INDEX `idx_create_time`(`createTime`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 45 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户操作日志表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_user_portrait +-- ---------------------------- +DROP TABLE IF EXISTS `ck_user_portrait`; +CREATE TABLE `ck_user_portrait` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `type` tinyint(2) NULL DEFAULT 0 COMMENT '类型 0浏览 1点击 2下单/购买 3注册 4互动', + `companyId` int(11) NULL DEFAULT 0, + `trafficPoolId` int(10) NULL DEFAULT NULL COMMENT '流量池用户id traffic_pool的主键', + `source` tinyint(2) NULL DEFAULT 0 COMMENT '来源 0本站 1老油条 2老坑爹', + `uniqueId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '来源网站唯一id', + `sourceData` json NULL COMMENT '来源网站数据', + `remark` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + `count` int(10) NULL DEFAULT 1 COMMENT '统计次数(半小时内)', + `createTime` int(11) UNSIGNED NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(11) NULL DEFAULT NULL COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 17718 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户画像' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_users +-- ---------------------------- +DROP TABLE IF EXISTS `ck_users`; +CREATE TABLE `ck_users` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID', + `account` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号', + `username` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '昵称', + `phone` char(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '登录手机号', + `passwordMd5` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码', + `passwordLocal` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '本地密码', + `avatar` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'https://img.icons8.com/color/512/circled-user-male-skin-type-7.png' COMMENT '头像', + `isAdmin` tinyint(3) NULL DEFAULT 0 COMMENT '是否管理身份 1->是 0->否', + `companyId` int(10) UNSIGNED NOT NULL COMMENT '账号所属项目id', + `typeId` tinyint(3) NOT NULL DEFAULT -1 COMMENT '类型:运营后台/操盘手 传1 、 门店传2', + `status` tinyint(3) NULL DEFAULT 0 COMMENT '1->可用,0->禁用', + `s2_accountId` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'S2的用户账号id', + `balance` int(11) NULL DEFAULT 0 COMMENT '余额', + `tokens` int(11) NULL DEFAULT 0 COMMENT '算力余额', + `createTime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间', + `updateTime` int(11) NULL DEFAULT NULL COMMENT '修改时间', + `deleteTime` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1652 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_vendor_order +-- ---------------------------- +DROP TABLE IF EXISTS `ck_vendor_order`; +CREATE TABLE `ck_vendor_order` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '订单ID', + `orderNo` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '订单编号', + `userId` int(10) UNSIGNED NOT NULL COMMENT '用户ID', + `packageId` int(10) UNSIGNED NOT NULL COMMENT '套餐ID', + `packageName` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '套餐名称', + `totalAmount` decimal(10, 2) NOT NULL COMMENT '订单总额', + `payAmount` decimal(10, 2) NOT NULL COMMENT '支付金额', + `advancePayment` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '预付款', + `status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '状态:0=待支付,1=已支付,2=已完成,3=已取消', + `payTime` int(11) NULL DEFAULT 0 COMMENT '支付时间', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + `createTime` int(11) NOT NULL COMMENT '创建时间', + `updateTime` int(11) NOT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `orderNo`(`orderNo`) USING BTREE, + INDEX `userId`(`userId`) USING BTREE, + INDEX `packageId`(`packageId`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '供应商订单表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_vendor_package +-- ---------------------------- +DROP TABLE IF EXISTS `ck_vendor_package`; +CREATE TABLE `ck_vendor_package` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '套餐ID', + `userId` int(11) NULL DEFAULT NULL COMMENT '用户id', + `companyId` int(11) NULL DEFAULT NULL COMMENT '公司id', + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '套餐名称', + `originalPrice` decimal(10, 2) NOT NULL COMMENT '原价', + `price` decimal(10, 2) NOT NULL COMMENT '售价', + `discount` decimal(4, 2) NULL DEFAULT 0.00 COMMENT '折扣', + `advancePayment` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '预付款', + `tags` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标签', + `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '套餐描述', + `cover` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '封面图片', + `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态:0=下架,1=上架', + `createTime` int(11) NOT NULL COMMENT '创建时间', + `updateTime` int(11) NOT NULL COMMENT '更新时间', + `isDel` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '供应商套餐表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_vendor_project +-- ---------------------------- +DROP TABLE IF EXISTS `ck_vendor_project`; +CREATE TABLE `ck_vendor_project` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '项目ID', + `packageId` int(10) UNSIGNED NOT NULL COMMENT '套餐ID', + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '项目名称', + `originalPrice` decimal(10, 2) NOT NULL COMMENT '原价', + `price` decimal(10, 2) NOT NULL COMMENT '售价', + `duration` int(11) NULL DEFAULT 0 COMMENT '项目时长(分钟)', + `image` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '项目图片', + `detail` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '项目详情', + `createTime` int(11) NOT NULL COMMENT '创建时间', + `updateTime` int(11) NOT NULL COMMENT '更新时间', + `isDel` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE, + INDEX `packageId`(`packageId`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '供应商套餐项目表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_wechat_account +-- ---------------------------- +DROP TABLE IF EXISTS `ck_wechat_account`; +CREATE TABLE `ck_wechat_account` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID', + `s2_wechatAccountId` int(11) NULL DEFAULT NULL COMMENT '微信账号id', + `wechatId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信ID', + `alias` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信号', + `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '昵称', + `pyInitial` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '拼音首字母', + `quanPin` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '全拼', + `avatar` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像URL', + `gender` tinyint(1) NULL DEFAULT 0 COMMENT '性别 0->保密;1->男;2->女', + `region` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '地区', + `signature` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '个性签名', + `phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '电话', + `country` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '国家', + `privince` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '省份', + `city` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '城市', + `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uni_wechatId`(`wechatId`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 3097959 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信账号表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_wechat_customer +-- ---------------------------- +DROP TABLE IF EXISTS `ck_wechat_customer`; +CREATE TABLE `ck_wechat_customer` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增id', + `wechatId` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信id', + `basic` json NULL COMMENT '保存基础信息', + `weight` json NULL COMMENT '保存权重信息', + `activity` json NULL COMMENT '保存账号活跃信息', + `friendShip` json NULL COMMENT '保存朋友关系信息', + `companyId` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '公司id', + `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uni_wechatId`(`wechatId`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 153 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信客服信息' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_wechat_friendship +-- ---------------------------- +DROP TABLE IF EXISTS `ck_wechat_friendship`; +CREATE TABLE `ck_wechat_friendship` ( + `id` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '好友id', + `wechatId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '微信ID', + `tags` json NULL COMMENT '好友标签', + `memo` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '好友备注', + `ownerWechatId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '所有者微信ID', + `companyId` int(11) NULL DEFAULT NULL COMMENT '公司ID', + `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间', + `deleteTime` int(11) NULL DEFAULT NULL COMMENT '删除时间', + UNIQUE INDEX `uk_owner_wechat_account`(`ownerWechatId`, `wechatId`) USING BTREE, + INDEX `idx_wechat_id`(`wechatId`) USING BTREE, + INDEX `idx_owner_wechat_id`(`ownerWechatId`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信好友表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_wechat_group +-- ---------------------------- +DROP TABLE IF EXISTS `ck_wechat_group`; +CREATE TABLE `ck_wechat_group` ( + `id` int(11) UNSIGNED NOT NULL COMMENT 'S2微信群id', + `wechatAccountId` int(11) NULL DEFAULT NULL COMMENT '微信账号ID', + `chatroomId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信群聊id', + `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '群名称', + `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '群头像', + `companyId` int(11) NULL DEFAULT NULL COMMENT '项目id', + `ownerWechatId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '所有者微信ID', + `identifier` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '群主(流量标识,可以是手机号、微信号)', + `createTime` int(11) UNSIGNED NULL DEFAULT NULL, + `updateTime` int(11) UNSIGNED NULL DEFAULT NULL, + `deleteTime` int(11) UNSIGNED NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_owner_chatroomId`(`chatroomId`, `ownerWechatId`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信群' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_wechat_group_member +-- ---------------------------- +DROP TABLE IF EXISTS `ck_wechat_group_member`; +CREATE TABLE `ck_wechat_group_member` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID', + `identifier` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '群成员(流量标识,可以是手机号、微信号)', + `chatroomId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '群真实id', + `customerIs` tinyint(3) NULL DEFAULT 0 COMMENT '是否客服', + `companyId` int(11) NULL DEFAULT NULL COMMENT '项目id', + `groupId` int(10) UNSIGNED NULL DEFAULT NULL COMMENT '所属群ID', + `createTime` int(11) UNSIGNED NULL DEFAULT 0, + `deleteTime` int(11) UNSIGNED NULL DEFAULT 0, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_identifier_chatroomId_groupId`(`identifier`, `chatroomId`, `groupId`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 549847 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信群成员' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_wechat_restricts +-- ---------------------------- +DROP TABLE IF EXISTS `ck_wechat_restricts`; +CREATE TABLE `ck_wechat_restricts` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `taskId` int(11) NULL DEFAULT NULL COMMENT '任务id', + `level` tinyint(3) UNSIGNED NULL DEFAULT 1 COMMENT '风险类型 1 普通 2 警告 3 错误', + `reason` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '风险原因', + `memo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '记录更详细的风险信息', + `wechatId` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信id', + `companyId` int(11) UNSIGNED NULL DEFAULT NULL COMMENT '项目id', + `restrictTime` int(11) NULL DEFAULT NULL COMMENT '限制日期', + `recoveryTime` int(11) NULL DEFAULT NULL COMMENT '恢复日期', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1302 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信风险受限记录' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_wechat_tag +-- ---------------------------- +DROP TABLE IF EXISTS `ck_wechat_tag`; +CREATE TABLE `ck_wechat_tag` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID', + `tags` json NULL COMMENT '标签JSON', + `wechatId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '微信ID', + `companyId` int(11) NULL DEFAULT NULL COMMENT '公司ID', + `createTime` int(10) UNSIGNED NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_wechatId`(`wechatId`) USING BTREE, + INDEX `idx_companyId`(`companyId`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 123366 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信账号表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_workbench +-- ---------------------------- +DROP TABLE IF EXISTS `ck_workbench`; +CREATE TABLE `ck_workbench` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `userId` int(11) NOT NULL COMMENT '创建用户ID', + `companyId` int(11) NULL DEFAULT 0 COMMENT '公司id', + `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '工作台名称', + `type` tinyint(1) NOT NULL DEFAULT 1 COMMENT '工作台类型:1=自动点赞,2=朋友圈同步,3=群消息推送,4=自动建群,5=流量分发,6=通讯录导入', + `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态:0=禁用,1=启用', + `autoStart` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否自动启动:0=否,1=是', + `createTime` int(11) NOT NULL COMMENT '创建时间', + `updateTime` int(11) NOT NULL COMMENT '更新时间', + `isDel` tinyint(1) NULL DEFAULT 0, + `deleteTime` int(11) NULL DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_user_id`(`userId`) USING BTREE, + INDEX `idx_type`(`type`) USING BTREE, + INDEX `idx_status`(`status`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 275 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '工作台主表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_workbench_auto_like +-- ---------------------------- +DROP TABLE IF EXISTS `ck_workbench_auto_like`; +CREATE TABLE `ck_workbench_auto_like` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `workbenchId` int(11) NOT NULL COMMENT '工作台ID', + `interval` int(11) NOT NULL DEFAULT 60 COMMENT '点赞间隔(秒)', + `maxLikes` int(11) NOT NULL DEFAULT 100 COMMENT '最大点赞数', + `startTime` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '00:00:00' COMMENT '开始时间', + `endTime` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '23:59:59' COMMENT '结束时间', + `contentTypes` json NULL COMMENT '内容类型', + `devices` json NULL COMMENT '设备列表,JSON格式:[{\"id\":1,\"name\":\"设备1\"},{\"id\":2,\"name\":\"设备2\"}]', + `friends` json NULL COMMENT '用户列表', + `createTime` int(11) NOT NULL COMMENT '创建时间', + `updateTime` int(11) NOT NULL COMMENT '更新时间', + `targetGroups` json NULL COMMENT '目标用户组列表,JSON格式:[{\"id\":1,\"name\":\"用户组1\"},{\"id\":2,\"name\":\"用户组2\"}] 废除', + `tagOperator` tinyint(1) NULL DEFAULT 2 COMMENT '标签匹配规则 1:and 2:or 废除', + `friendMaxLikes` int(10) NULL DEFAULT NULL COMMENT '好友最大点赞数', + `friendTags` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '好友标签', + `enableFriendTags` tinyint(1) NULL DEFAULT 0 COMMENT '启用好友标签', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_workbench_id`(`workbenchId`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 54 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '自动点赞配置表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_workbench_auto_like_item +-- ---------------------------- +DROP TABLE IF EXISTS `ck_workbench_auto_like_item`; +CREATE TABLE `ck_workbench_auto_like_item` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `workbenchId` int(11) NOT NULL COMMENT '工作台ID', + `deviceId` int(11) NULL DEFAULT 0 COMMENT '设备id', + `snsId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '自动点赞id', + `wechatFriendId` int(11) NULL DEFAULT NULL COMMENT '好友id', + `wechatAccountId` int(11) NULL DEFAULT NULL COMMENT '客服id', + `momentsId` int(11) NULL DEFAULT NULL COMMENT '朋友圈id', + `createTime` int(11) NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `workbenchId`(`workbenchId`) USING BTREE, + INDEX `wechatFriendId`(`wechatFriendId`) USING BTREE, + INDEX `wechatAccountId`(`wechatAccountId`) USING BTREE, + INDEX `momentsId`(`momentsId`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 4639 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '工作台-自动点赞记录' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_workbench_group_create +-- ---------------------------- +DROP TABLE IF EXISTS `ck_workbench_group_create`; +CREATE TABLE `ck_workbench_group_create` ( + `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `workbenchId` int(11) NOT NULL COMMENT '计划ID', + `devices` json NULL COMMENT '目标设备/客服(JSON数组)', + `poolGroups` json NULL COMMENT '流量池JSON', + `wechatGroups` json NULL COMMENT '微信客服JSON', + `startTime` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '开始时间', + `endTime` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '结束时间', + `groupSizeMin` int(10) NULL DEFAULT NULL COMMENT '群好友最小人数', + `groupSizeMax` int(10) NULL DEFAULT NULL COMMENT '群好友最大人数', + `maxGroupsPerDay` int(10) NULL DEFAULT NULL COMMENT '每日建群最大数量', + `groupNameTemplate` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '群模板信息', + `groupDescription` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '群描述', + `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 26 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_workbench_group_create_item +-- ---------------------------- +DROP TABLE IF EXISTS `ck_workbench_group_create_item`; +CREATE TABLE `ck_workbench_group_create_item` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `workbenchId` int(11) NOT NULL COMMENT '工作台ID', + `friendId` int(11) NULL DEFAULT NULL, + `wechatId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '微信id', + `groupId` int(10) NULL DEFAULT NULL COMMENT '群id', + `wechatAccountId` int(11) NULL DEFAULT NULL COMMENT '客服id', + `createTime` int(11) NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 46 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_workbench_group_push +-- ---------------------------- +DROP TABLE IF EXISTS `ck_workbench_group_push`; +CREATE TABLE `ck_workbench_group_push` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `workbenchId` int(11) NOT NULL COMMENT '工作台ID', + `pushType` tinyint(1) NOT NULL DEFAULT 1 COMMENT '推送方式 0定时 1立即', + `targetType` tinyint(1) NOT NULL DEFAULT 1 COMMENT '推送目标类型:1=群推送,2=好友推送', + `startTime` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '推送开始时间', + `endTime` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '推送结束时间', + `maxPerDay` int(11) NULL DEFAULT 0 COMMENT '每日推送条数', + `pushOrder` tinyint(1) NULL DEFAULT 1 COMMENT '推送顺序 1最早 2最新', + `isLoop` tinyint(1) NULL DEFAULT 0 COMMENT '是否循环推送 0否 1是', + `status` tinyint(1) NULL DEFAULT 1 COMMENT '是否启用 0否 1是', + `groups` json NULL COMMENT '推送微信群组(JSON)', + `friends` json NULL COMMENT '推送好友列表(JSON)', + `ownerWechatIds` json NULL COMMENT '所属微信id', + `contentLibraries` json NULL COMMENT '内容库(JSON)', + `friendIntervalMin` int(11) NOT NULL DEFAULT 10 COMMENT '好友间最小间隔时间(秒)', + `friendIntervalMax` int(11) NOT NULL DEFAULT 20 COMMENT '好友间最大间隔时间(秒)', + `messageIntervalMin` int(11) NOT NULL DEFAULT 1 COMMENT '消息间最小间隔时间(秒)', + `messageIntervalMax` int(11) NOT NULL DEFAULT 12 COMMENT '消息间最大间隔时间(秒)', + `isRandomTemplate` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否随机选择话术组(0=否,1=是)', + `postPushTags` json NOT NULL COMMENT '推送完成后打标签', + `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间', + `socialMediaId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '京东导购媒体', + `promotionSiteId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '京东广告位', + `trafficPools` json NULL COMMENT '流量池', + `devices` json NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_workbench_id`(`workbenchId`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 32 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群消息推送扩展表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_workbench_group_push_item +-- ---------------------------- +DROP TABLE IF EXISTS `ck_workbench_group_push_item`; +CREATE TABLE `ck_workbench_group_push_item` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `workbenchId` int(11) NOT NULL COMMENT '工作台ID', + `targetType` tinyint(1) NOT NULL DEFAULT 1 COMMENT '推送目标类型:1=群,2=好友', + `contentId` int(11) NULL DEFAULT 0 COMMENT '内容库is', + `groupId` int(10) NULL DEFAULT NULL COMMENT '群id', + `friendId` int(11) NULL DEFAULT NULL COMMENT '好友ID(当targetType=2时使用)', + `wechatAccountId` int(11) NULL DEFAULT NULL COMMENT '客服id', + `isLoop` tinyint(2) NULL DEFAULT 0 COMMENT '是否循环完成', + `createTime` int(11) NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 302 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_workbench_import_contact +-- ---------------------------- +DROP TABLE IF EXISTS `ck_workbench_import_contact`; +CREATE TABLE `ck_workbench_import_contact` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `workbenchId` int(11) NOT NULL COMMENT '工作台ID', + `devices` json NULL COMMENT '设备id', + `pools` json NULL COMMENT '流量池', + `num` int(11) NULL DEFAULT NULL COMMENT '分配数量', + `clearContact` tinyint(2) NULL DEFAULT 0 COMMENT '是否清除现有联系人', + `remarkType` tinyint(2) NOT NULL DEFAULT 0 COMMENT '备注类型 0不备注 1年月日 2月日 3自定义', + `remark` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + `startTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '开始时间', + `endTime` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '结束时间', + `createTime` int(11) NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_workbench_import_contact_item +-- ---------------------------- +DROP TABLE IF EXISTS `ck_workbench_import_contact_item`; +CREATE TABLE `ck_workbench_import_contact_item` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `workbenchId` int(11) NOT NULL COMMENT '工作台ID', + `deviceId` int(11) NULL DEFAULT NULL COMMENT '设备id', + `packageId` int(11) NULL DEFAULT 0 COMMENT '流量包id', + `poolId` int(11) NULL DEFAULT NULL COMMENT '流量id', + `createTime` int(11) NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 140 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_workbench_moments_sync +-- ---------------------------- +DROP TABLE IF EXISTS `ck_workbench_moments_sync`; +CREATE TABLE `ck_workbench_moments_sync` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `workbenchId` int(11) NOT NULL COMMENT '工作台ID', + `syncInterval` int(11) NOT NULL DEFAULT 1 COMMENT '同步间隔(小时)', + `syncCount` int(11) NOT NULL DEFAULT 5 COMMENT '每日同步数量', + `syncType` tinyint(1) NOT NULL DEFAULT 1 COMMENT '同步类型:1=文本,2=图片,3=视频,4=链接', + `startTime` time(0) NULL DEFAULT '06:00:00' COMMENT '发布开始时间', + `endTime` time(0) NULL DEFAULT '23:59:00' COMMENT '发布结束时间', + `accountType` tinyint(1) NOT NULL DEFAULT 1 COMMENT '账号类型:1=业务号,2=个人号', + `devices` json NOT NULL COMMENT '设备列表,JSON格式', + `contentLibraries` json NULL COMMENT '内容库ID列表,JSON格式', + `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_workbench_id`(`workbenchId`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 47 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '朋友圈同步配置' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_workbench_moments_sync_item +-- ---------------------------- +DROP TABLE IF EXISTS `ck_workbench_moments_sync_item`; +CREATE TABLE `ck_workbench_moments_sync_item` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `workbenchId` int(11) NOT NULL COMMENT '工作台ID', + `deviceId` int(11) NULL DEFAULT 0 COMMENT '设备id', + `contentId` int(10) NULL DEFAULT NULL COMMENT '内容库id', + `wechatAccountId` int(11) NULL DEFAULT NULL COMMENT '客服id', + `createTime` int(11) NOT NULL COMMENT '创建时间', + `isLoop` tinyint(2) NULL DEFAULT 0, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1650 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '朋友圈同步配置' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_workbench_traffic_config +-- ---------------------------- +DROP TABLE IF EXISTS `ck_workbench_traffic_config`; +CREATE TABLE `ck_workbench_traffic_config` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `workbenchId` int(11) NOT NULL COMMENT '流量分发计划ID', + `distributeType` tinyint(1) NOT NULL DEFAULT 1 COMMENT '分配方式 1均分 2优先级 3比例', + `maxPerDay` int(11) NOT NULL DEFAULT 0 COMMENT '每日最大分配量', + `timeType` tinyint(1) NOT NULL DEFAULT 1 COMMENT '时间限制 1全天 2自定义', + `startTime` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '开始时间', + `endTime` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '结束时间', + `account` json NULL COMMENT '分发的账号', + `devices` json NULL COMMENT '目标设备/客服(JSON数组)', + `pools` json NULL COMMENT '流量池(JSON数组)', + `exp` int(10) NULL DEFAULT 30 COMMENT '有效期 单位天', + `createTime` int(11) NOT NULL, + `updateTime` int(11) NOT NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uniq_workbench`(`workbenchId`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 31 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量分发计划扩展表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for ck_workbench_traffic_config_item +-- ---------------------------- +DROP TABLE IF EXISTS `ck_workbench_traffic_config_item`; +CREATE TABLE `ck_workbench_traffic_config_item` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `workbenchId` int(11) NOT NULL DEFAULT 0 COMMENT '工作台ID', + `deviceId` int(11) NULL DEFAULT 0 COMMENT '设备id', + `wechatFriendId` int(10) NULL DEFAULT NULL COMMENT '好友id', + `wechatAccountId` int(11) NULL DEFAULT 0 COMMENT '客服id', + `expTime` int(11) NULL DEFAULT 0 COMMENT '有效时间', + `exp` int(11) NULL DEFAULT 0 COMMENT '有效时间 天', + `isRecycle` tinyint(2) NULL DEFAULT 0 COMMENT '是否回收', + `recycleTime` int(11) NULL DEFAULT 0 COMMENT '回收时间', + `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `workbenchId`(`workbenchId`) USING BTREE, + INDEX `deviceId`(`deviceId`) USING BTREE, + INDEX `wechatFriendId`(`wechatFriendId`) USING BTREE, + INDEX `wechatAccountId`(`wechatAccountId`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 49898 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量分发计划扩展表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for s2_allot_rule +-- ---------------------------- +DROP TABLE IF EXISTS `s2_allot_rule`; +CREATE TABLE `s2_allot_rule` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '规则ID', + `departmentId` int(11) NULL DEFAULT 0 COMMENT '部门id', + `tenantId` int(11) NOT NULL DEFAULT 0 COMMENT '租户ID', + `allotType` tinyint(4) NOT NULL DEFAULT 0 COMMENT '分配类型', + `allotOnline` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否在线分配', + `kefuRange` tinyint(4) NOT NULL DEFAULT 0 COMMENT '客服范围', + `wechatRange` tinyint(4) NOT NULL DEFAULT 0 COMMENT '微信范围', + `kefuData` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '客服数据JSON', + `wechatData` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '微信ID列表JSON', + `labels` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '标签JSON', + `priorityStrategy` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '优先级策略JSON', + `sortIndex` int(11) NOT NULL DEFAULT 0 COMMENT '排序索引', + `creatorAccountId` int(11) NOT NULL DEFAULT 0 COMMENT '创建者账号ID', + `createTime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间', + `ruleName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '规则名称', + `isDel` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_tenant`(`tenantId`) USING BTREE, + INDEX `idx_sort`(`sortIndex`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2176 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '分配规则表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for s2_call_recording +-- ---------------------------- +DROP TABLE IF EXISTS `s2_call_recording`; +CREATE TABLE `s2_call_recording` ( + `id` bigint(20) NOT NULL COMMENT '主键ID', + `tenantId` bigint(20) NOT NULL DEFAULT 0 COMMENT '租户ID', + `deviceOwnerId` bigint(20) NOT NULL DEFAULT 0 COMMENT '设备所有者ID', + `userName` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名', + `nickname` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '昵称', + `realName` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '真实姓名', + `deviceMemo` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '设备备注', + `fileName` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '文件名', + `imei` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '设备IMEI', + `phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '电话号码', + `isCallOut` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否为呼出电话(0:呼入,1:呼出)', + `beginTime` int(11) NOT NULL DEFAULT 0 COMMENT '通话开始时间戳', + `endTime` int(11) NOT NULL DEFAULT 0 COMMENT '通话结束时间戳', + `audioUrl` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '录音文件URL', + `mp3AudioUrl` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'MP3录音文件URL', + `callBeginTime` int(11) NOT NULL DEFAULT 0 COMMENT '呼叫开始时间戳', + `callLogId` bigint(20) NOT NULL DEFAULT 0 COMMENT '通话日志ID', + `callType` int(11) NOT NULL DEFAULT 0 COMMENT '通话类型', + `duration` int(11) NOT NULL DEFAULT 0 COMMENT '通话时长(秒)', + `skipReason` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '跳过原因', + `skipUpload` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否跳过上传', + `isDeleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否已删除', + `createTime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间戳', + `lastUpdateTime` int(11) NOT NULL DEFAULT 0 COMMENT '最后更新时间戳', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_tenant_id`(`tenantId`) USING BTREE, + INDEX `idx_device_owner_id`(`deviceOwnerId`) USING BTREE, + INDEX `idx_user_name`(`userName`) USING BTREE, + INDEX `idx_phone`(`phone`) USING BTREE, + INDEX `idx_begin_time`(`beginTime`) USING BTREE, + INDEX `idx_end_time`(`endTime`) USING BTREE, + INDEX `idx_call_begin_time`(`callBeginTime`) USING BTREE, + INDEX `idx_imei`(`imei`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '通话记录表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for s2_company_account +-- ---------------------------- +DROP TABLE IF EXISTS `s2_company_account`; +CREATE TABLE `s2_company_account` ( + `id` int(11) NULL DEFAULT NULL COMMENT 'id', + `tenantId` int(11) NULL DEFAULT NULL, + `userName` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户名', + `realName` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '真实姓名', + `nickname` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '昵称', + `memo` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '备注', + `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '头像', + `secret` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '密钥', + `accountType` int(11) NULL DEFAULT 0 COMMENT '账户类型', + `departmentId` int(11) NULL DEFAULT 0 COMMENT '部门ID', + `departmentName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '部门名称', + `useGoogleSecretKey` tinyint(1) NULL DEFAULT 0 COMMENT '是否使用谷歌密钥', + `hasVerifyGoogleSecret` tinyint(1) NULL DEFAULT 0 COMMENT '是否验证谷歌密钥', + `passwordMd5` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT 'MD5加密密码', + `passwordLocal` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '本地加密密码', + `lastLoginIp` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '最后登录IP', + `lastLoginTime` int(11) NULL DEFAULT 0 COMMENT '最后登录时间', + `createTime` int(11) NULL DEFAULT 0 COMMENT '创建时间', + `updateTime` int(11) NULL DEFAULT 0 COMMENT '更新时间', + `privilegeIds` json NULL COMMENT '权限', + `alive` tinyint(1) NULL DEFAULT NULL, + `creator` int(10) NULL DEFAULT NULL COMMENT '创建者', + `creatorRealName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建者真实姓名', + `creatorUserName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建者用户名', + `status` tinyint(1) NULL DEFAULT 0 COMMENT '状态 0正常 1禁用', + UNIQUE INDEX `idx_username`(`userName`) USING BTREE, + INDEX `idx_create_time`(`createTime`) USING BTREE, + INDEX `idx_update_time`(`updateTime`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '公司账户表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for s2_department +-- ---------------------------- +DROP TABLE IF EXISTS `s2_department`; +CREATE TABLE `s2_department` ( + `id` int(11) NOT NULL, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称', + `memo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + `tenantId` int(11) NULL DEFAULT NULL, + `isTop` tinyint(1) NULL DEFAULT 0, + `level` int(10) NULL DEFAULT 0, + `parentId` int(10) NULL DEFAULT 0, + `privileges` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL, + `createTime` int(11) NULL DEFAULT NULL, + `lastUpdateTime` int(11) NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '部门表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for s2_device +-- ---------------------------- +DROP TABLE IF EXISTS `s2_device`; +CREATE TABLE `s2_device` ( + `id` int(11) NULL DEFAULT NULL COMMENT '设备真实ID', + `userName` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户名', + `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '昵称', + `realName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '真实姓名', + `groupName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '分组名称', + `wechatAccounts` json NULL COMMENT '微信账号列表JSON', + `alive` tinyint(1) NULL DEFAULT 0 COMMENT '是否在线', + `lastAliveTime` int(11) NULL DEFAULT NULL COMMENT '最后在线时间', + `tenantId` int(11) NULL DEFAULT NULL COMMENT '租户ID', + `groupId` int(11) NULL DEFAULT NULL COMMENT '分组ID', + `currentAccountId` int(11) NULL DEFAULT NULL COMMENT '当前账号ID', + `imei` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '设备IMEI', + `deviceImei` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '设备本地IMEI', + `memo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间', + `isDeleted` tinyint(1) NULL DEFAULT 0 COMMENT '是否删除', + `deletedAndStop` tinyint(1) NULL DEFAULT 0 COMMENT '是否删除并停止', + `deleteTime` int(11) NULL DEFAULT NULL COMMENT '删除时间', + `rooted` tinyint(1) NULL DEFAULT 0 COMMENT '是否root', + `xPosed` tinyint(1) NULL DEFAULT 0 COMMENT '是否安装xposed', + `brand` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '品牌', + `model` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '型号', + `operatingSystem` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '操作系统版本', + `softwareVersion` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '软件版本', + `extra` json NULL COMMENT '额外信息JSON', + `phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号', + `lastUpdateTime` int(11) NULL DEFAULT NULL COMMENT '最后更新时间', + `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间', + `taskConfig` json NULL COMMENT '自动化任务开关 \r\nautoLike:自动点赞\r\nmomentsSync:朋友圈同步\r\nautoCustomerDev:自动开发客户\r\ngroupMessageDeliver:群消息推送\r\nautoGroup:自动建群', + UNIQUE INDEX `uk_imei`(`imei`) USING BTREE, + INDEX `idx_tenant`(`tenantId`) USING BTREE, + INDEX `idx_group`(`groupId`) USING BTREE, + INDEX `idx_current_account`(`currentAccountId`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '设备表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for s2_device_group +-- ---------------------------- +DROP TABLE IF EXISTS `s2_device_group`; +CREATE TABLE `s2_device_group` ( + `id` int(11) NOT NULL, + `tenantId` int(11) NOT NULL COMMENT '租户ID', + `groupName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '分组名称', + `groupMemo` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '分组备注', + `count` int(11) NULL DEFAULT 0 COMMENT '设备数量', + `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间', + INDEX `idx_tenant`(`tenantId`) USING BTREE, + INDEX `idx_group_name`(`groupName`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '设备分组表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for s2_friend_task +-- ---------------------------- +DROP TABLE IF EXISTS `s2_friend_task`; +CREATE TABLE `s2_friend_task` ( + `id` int(11) NOT NULL COMMENT '任务ID', + `tenantId` int(11) NULL DEFAULT 0 COMMENT '租户ID', + `operatorAccountId` int(11) NULL DEFAULT 0 COMMENT '操作账号ID', + `status` int(11) NULL DEFAULT 1 COMMENT '状态:0执行中,1执行成功,2执行失败', + `phone` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号/微信号', + `msgContent` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '验证消息', + `wechatAccountId` int(11) NULL DEFAULT 0 COMMENT '微信账号ID', + `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间戳', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + `extra` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '额外数据JSON', + `labels` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标签,逗号分隔', + `from` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '来源', + `alias` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信账号别名', + `wechatId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信ID', + `wechatAvatar` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信头像', + `wechatNickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信昵称', + `accountNickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号昵称', + `accountRealName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号真实姓名', + `accountUsername` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号用户名', + `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间戳', + UNIQUE INDEX `uk_task_id`(`id`) USING BTREE, + INDEX `idx_tenant_id`(`tenantId`) USING BTREE, + INDEX `idx_operator_account_id`(`operatorAccountId`) USING BTREE, + INDEX `idx_wechat_account_id`(`wechatAccountId`) USING BTREE, + INDEX `idx_status`(`status`) USING BTREE, + INDEX `idx_phone`(`phone`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '添加好友任务记录表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for s2_moments_item +-- ---------------------------- +DROP TABLE IF EXISTS `s2_moments_item`; +CREATE TABLE `s2_moments_item` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `task_id` int(11) NOT NULL COMMENT '朋友圈任务ID', + `temp_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '临时ID', + `wechat_account_id` int(11) NULL DEFAULT NULL COMMENT '微信账号ID', + `execute_count` int(11) NULL DEFAULT 0 COMMENT '执行次数', + `executed` tinyint(1) NULL DEFAULT 0 COMMENT '是否已执行', + `status` tinyint(1) NULL DEFAULT 0 COMMENT '状态', + `extra` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '额外信息', + `execute_time` int(11) NULL DEFAULT NULL COMMENT '执行时间', + `finished_time` int(11) NULL DEFAULT NULL COMMENT '完成时间', + `labels` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '标签', + `alt_list` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '替代列表', + `comments` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '评论', + `moment_content_type` tinyint(1) NULL DEFAULT 0 COMMENT '朋友圈内容类型', + `text` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '文本内容', + `pic_url_list` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '图片URL列表', + `video_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '视频URL', + `link` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '链接信息', + `is_use_location` tinyint(1) NULL DEFAULT 0 COMMENT '是否使用位置', + `lat` decimal(10, 6) NULL DEFAULT 0.000000 COMMENT '纬度', + `lng` decimal(10, 6) NULL DEFAULT 0.000000 COMMENT '经度', + `poi_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '位置名称', + `poi_address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '位置地址', + `video_no` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '视频编号', + `created_at` int(11) NULL DEFAULT NULL COMMENT '记录创建时间', + `updated_at` int(11) NULL DEFAULT NULL COMMENT '记录更新时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `idx_task_temp`(`task_id`, `temp_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 184 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '朋友圈任务项表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for s2_moments_task +-- ---------------------------- +DROP TABLE IF EXISTS `s2_moments_task`; +CREATE TABLE `s2_moments_task` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `task_id` int(11) NOT NULL COMMENT '朋友圈任务ID', + `tenant_id` int(11) NULL DEFAULT NULL COMMENT '租户ID', + `operator_account_id` int(11) NULL DEFAULT NULL COMMENT '操作人账号ID', + `account_username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号用户名', + `account_nickname` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号昵称', + `account_real_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号真实姓名', + `public_mode` tinyint(1) NULL DEFAULT 0 COMMENT '发布模式', + `moment_content_type` tinyint(1) NULL DEFAULT 1 COMMENT '朋友圈内容类型:1纯文本,2图片,3视频,4链接', + `text` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '文本内容', + `pic_url_list` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '图片URL列表', + `video_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '视频URL', + `link` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '链接信息', + `job_status` tinyint(1) NULL DEFAULT 0 COMMENT '任务状态', + `job_origin_status` tinyint(1) NULL DEFAULT 0 COMMENT '任务原始状态', + `job_group` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '任务组', + `job_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '任务名称', + `begin_time` int(11) NULL DEFAULT NULL COMMENT '开始时间', + `end_time` int(11) NULL DEFAULT NULL COMMENT '结束时间', + `timing_time` int(11) NULL DEFAULT NULL COMMENT '定时发布时间', + `create_time` int(11) NULL DEFAULT NULL COMMENT '创建时间', + `immediately` tinyint(1) NULL DEFAULT 1 COMMENT '是否立即发布', + `created_at` int(11) NULL DEFAULT NULL COMMENT '记录创建时间', + `updated_at` int(11) NULL DEFAULT NULL COMMENT '记录更新时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `idx_task_id`(`task_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 88 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '朋友圈任务表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for s2_reply +-- ---------------------------- +DROP TABLE IF EXISTS `s2_reply`; +CREATE TABLE `s2_reply` ( + `id` int(11) NOT NULL, + `tenantId` int(255) NULL DEFAULT NULL, + `groupId` int(11) NULL DEFAULT NULL, + `accountId` int(11) NULL DEFAULT NULL, + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `msgType` tinyint(2) NULL DEFAULT NULL, + `content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `createTime` int(11) NULL DEFAULT NULL, + `lastUpdateTime` int(11) NULL DEFAULT NULL, + `sortIndex` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '快捷回复' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for s2_reply_group +-- ---------------------------- +DROP TABLE IF EXISTS `s2_reply_group`; +CREATE TABLE `s2_reply_group` ( + `id` int(11) NOT NULL, + `groupName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `sortIndex` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `parentId` int(11) NULL DEFAULT NULL, + `replyType` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `replys` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `departmentId` int(11) NULL DEFAULT 2130, + `accountId` int(11) NULL DEFAULT 5150, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '快捷回复分组' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for s2_wechat_account +-- ---------------------------- +DROP TABLE IF EXISTS `s2_wechat_account`; +CREATE TABLE `s2_wechat_account` ( + `id` int(11) NOT NULL COMMENT '微信账号ID', + `wechatId` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '微信ID', + `deviceAccountId` int(11) NULL DEFAULT 0 COMMENT '设备账号ID', + `imei` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'IMEI', + `deviceMemo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '设备备注', + `accountUserName` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号用户名', + `accountRealName` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号真实姓名', + `accountNickname` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号昵称', + `keFuAlive` tinyint(1) NULL DEFAULT 0 COMMENT '客服是否在线', + `deviceAlive` tinyint(1) NULL DEFAULT 0 COMMENT '设备是否在线', + `wechatAlive` tinyint(1) NULL DEFAULT 0 COMMENT '微信是否在线', + `yesterdayMsgCount` int(11) NULL DEFAULT 0 COMMENT '昨日消息数', + `sevenDayMsgCount` int(11) NULL DEFAULT 0 COMMENT '7天消息数', + `thirtyDayMsgCount` int(11) NULL DEFAULT 0 COMMENT '30天消息数', + `totalFriend` int(11) NULL DEFAULT 0 COMMENT '总好友数', + `maleFriend` int(11) NULL DEFAULT 0 COMMENT '男性好友数', + `unknowFriend` int(11) NULL DEFAULT NULL COMMENT '未知好友数', + `femaleFriend` int(11) NULL DEFAULT 0 COMMENT '女性好友数', + `wechatGroupName` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信群组名称', + `tenantId` int(11) NULL DEFAULT NULL COMMENT '租户ID', + `nickname` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '昵称', + `alias` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '别名', + `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像', + `gender` tinyint(1) NULL DEFAULT 0 COMMENT '性别', + `region` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '地区', + `signature` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '签名', + `bindQQ` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '绑定QQ', + `bindEmail` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '绑定邮箱', + `bindMobile` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '绑定手机', + `currentDeviceId` int(11) NULL DEFAULT 0 COMMENT '当前设备ID', + `isDeleted` tinyint(1) NULL DEFAULT 0 COMMENT '是否删除', + `deleteTime` int(11) NULL DEFAULT NULL COMMENT '删除时间', + `groupId` int(11) NULL DEFAULT 0 COMMENT '分组ID', + `memo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + `wechatVersion` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信版本', + `labels` json NULL COMMENT '标签', + `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间', + `status` tinyint(3) NULL DEFAULT 1 COMMENT '状态值', + INDEX `idx_wechat_id`(`wechatId`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信账号表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for s2_wechat_chatroom +-- ---------------------------- +DROP TABLE IF EXISTS `s2_wechat_chatroom`; +CREATE TABLE `s2_wechat_chatroom` ( + `id` int(11) NOT NULL, + `wechatAccountId` int(11) NOT NULL COMMENT '微信账号ID', + `wechatAccountAlias` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信账号别名', + `wechatAccountWechatId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信账号微信ID', + `wechatAccountAvatar` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信账号头像', + `wechatAccountNickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '微信账号昵称', + `chatroomId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '群聊ID', + `hasMe` tinyint(1) NULL DEFAULT 0 COMMENT '是否包含自己', + `chatroomOwnerNickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '群主昵称', + `chatroomOwnerAvatar` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '群主头像', + `conRemark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '群聊名称', + `pyInitial` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '拼音首字母', + `quanPin` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '全拼', + `chatroomAvatar` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '群头像', + `isDeleted` tinyint(1) NULL DEFAULT 0 COMMENT '是否删除', + `deleteTime` int(11) NULL DEFAULT NULL COMMENT '删除时间', + `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间', + `accountId` int(11) NULL DEFAULT 0 COMMENT '账号ID', + `accountUserName` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号用户名', + `accountRealName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号真实姓名', + `accountNickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号昵称', + `groupId` int(11) NULL DEFAULT 0 COMMENT '分组ID', + `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间', + UNIQUE INDEX `uk_chatroom_account`(`chatroomId`, `wechatAccountId`) USING BTREE, + INDEX `wechatAccountId`(`wechatAccountId`) USING BTREE, + INDEX `chatroomId`(`chatroomId`) USING BTREE, + INDEX `wechatAccountWechatId`(`wechatAccountWechatId`) USING BTREE, + INDEX `idx_account_deleted`(`accountId`, `isDeleted`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信群表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for s2_wechat_chatroom_member +-- ---------------------------- +DROP TABLE IF EXISTS `s2_wechat_chatroom_member`; +CREATE TABLE `s2_wechat_chatroom_member` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `chatroomId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '群聊ID', + `wechatId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '微信ID', + `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '昵称', + `avatar` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像', + `conRemark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + `alias` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '别名', + `friendType` tinyint(11) NULL DEFAULT 0 COMMENT '好友类型', + `createTime` int(10) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(10) NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_chatroom_wechat`(`chatroomId`, `wechatId`) USING BTREE, + INDEX `chatroomId`(`chatroomId`) USING BTREE, + INDEX `wechatId`(`wechatId`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 495043 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信群成员表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for s2_wechat_friend +-- ---------------------------- +DROP TABLE IF EXISTS `s2_wechat_friend`; +CREATE TABLE `s2_wechat_friend` ( + `id` int(11) NULL DEFAULT NULL COMMENT '好友id', + `wechatAccountId` int(11) NOT NULL COMMENT '所有者微信账号ID', + `alias` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '好友微信号', + `wechatId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '好友微信ID', + `conRemark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注名', + `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '昵称', + `pyInitial` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '拼音首字母', + `quanPin` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '全拼', + `avatar` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像URL', + `gender` tinyint(1) NULL DEFAULT 0 COMMENT '性别', + `region` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '地区', + `addFrom` int(11) NULL DEFAULT NULL COMMENT '添加来源', + `labels` json NULL COMMENT '标签JSON', + `siteLabels` json NULL COMMENT '站内标签JSON', + `signature` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '个性签名', + `isDeleted` tinyint(1) NULL DEFAULT 0 COMMENT '是否删除', + `isPassed` tinyint(1) NULL DEFAULT 1 COMMENT '是否通过', + `deleteTime` int(11) NULL DEFAULT NULL COMMENT '删除时间', + `accountId` int(11) NULL DEFAULT 0 COMMENT '账号ID', + `extendFields` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '扩展字段JSON', + `accountUserName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号用户名', + `accountRealName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号真实姓名', + `accountNickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号昵称', + `ownerAlias` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '所有者别名', + `ownerWechatId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '所有者微信ID', + `ownerNickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '所有者昵称', + `ownerAvatar` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '所有者头像', + `phone` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '电话', + `thirdParty` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '第三方数据JSON', + `groupId` int(11) NULL DEFAULT 0 COMMENT '分组ID', + `passTime` int(11) NULL DEFAULT NULL COMMENT '通过时间', + `additionalPicture` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '附加图片', + `desc` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '描述', + `country` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '国家', + `privince` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '省份', + `city` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '城市', + `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间', + `updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间', + `R` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0', + `F` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0', + `M` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0', + UNIQUE INDEX `uk_owner_wechat_account`(`ownerWechatId`, `wechatId`, `wechatAccountId`) USING BTREE, + INDEX `idx_wechat_account_id`(`wechatAccountId`) USING BTREE, + INDEX `idx_wechat_id`(`wechatId`) USING BTREE, + INDEX `idx_owner_wechat_id`(`ownerWechatId`) USING BTREE, + INDEX `idx_id`(`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信好友表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for s2_wechat_group +-- ---------------------------- +DROP TABLE IF EXISTS `s2_wechat_group`; +CREATE TABLE `s2_wechat_group` ( + `id` int(11) NOT NULL, + `tenantId` int(11) NULL DEFAULT NULL, + `groupName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `groupMemo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `groupType` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `sortIndex` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `groupOwnerType` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `departmentId` int(11) NULL DEFAULT NULL, + `accountId` int(11) NULL DEFAULT NULL, + `createTime` int(11) NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for s2_wechat_message +-- ---------------------------- +DROP TABLE IF EXISTS `s2_wechat_message`; +CREATE TABLE `s2_wechat_message` ( + `id` bigint(20) NOT NULL COMMENT '消息ID', + `type` tinyint(1) NOT NULL DEFAULT 1 COMMENT '消息类型 1好友 2群', + `wechatFriendId` bigint(20) NULL DEFAULT NULL COMMENT '微信好友ID', + `wechatChatroomId` bigint(20) NOT NULL COMMENT '微信群聊ID', + `senderNickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '发送者昵称', + `senderWechatId` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '发送者微信ID', + `senderIsAdmin` tinyint(1) NULL DEFAULT 0 COMMENT '发送者是否管理员', + `senderIsDeleted` tinyint(1) NULL DEFAULT 0 COMMENT '发送者是否已删除', + `senderChatroomNickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '发送者群昵称', + `senderWechatAccountId` bigint(20) NULL DEFAULT NULL COMMENT '发送者微信账号ID', + `wechatAccountId` bigint(20) NULL DEFAULT NULL COMMENT '微信账号ID', + `tenantId` bigint(20) NULL DEFAULT NULL COMMENT '租户ID', + `accountId` bigint(20) NULL DEFAULT NULL COMMENT '账号ID', + `synergyAccountId` bigint(20) NULL DEFAULT 0 COMMENT '协同账号ID', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容', + `originalContent` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '消息内容(原版)', + `msgType` int(11) NULL DEFAULT NULL COMMENT '消息类型 1 文字 3图片 47动态图片 34语言 43视频 42名片 40/20链接 49文件 419430449转账 436207665红包', + `msgSubType` int(11) NULL DEFAULT 0 COMMENT '消息子类型', + `msgSvrId` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '消息服务器ID', + `isSend` tinyint(1) NULL DEFAULT 1 COMMENT '是否发送', + `createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间', + `isDeleted` tinyint(1) NULL DEFAULT 0 COMMENT '是否已删除', + `deleteTime` int(11) NULL DEFAULT NULL COMMENT '删除时间', + `sendStatus` int(11) NULL DEFAULT 0 COMMENT '发送状态', + `wechatTime` int(11) NULL DEFAULT NULL COMMENT '微信时间', + `origin` int(11) NULL DEFAULT 0 COMMENT '来源', + `msgId` bigint(20) NULL DEFAULT NULL COMMENT '消息ID', + `recallId` tinyint(1) NULL DEFAULT 0 COMMENT '撤回ID', + `isRead` tinyint(1) NULL DEFAULT 0 COMMENT '是否读取', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_wechatChatroomId`(`wechatChatroomId`) USING BTREE, + INDEX `idx_wechatAccountId`(`wechatAccountId`) USING BTREE, + INDEX `idx_msgSvrId`(`msgSvrId`) USING BTREE, + INDEX `idx_type`(`type`) USING BTREE, + INDEX `idx_type_wechatTime`(`type`, `wechatTime`, `id`) USING BTREE, + INDEX `idx_friend_time`(`wechatFriendId`, `wechatTime`, `id`) USING BTREE, + INDEX `idx_chatroom_time`(`wechatChatroomId`, `wechatTime`, `id`) USING BTREE, + INDEX `idx_account_type`(`accountId`, `type`, `wechatTime`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信群聊消息记录表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for s2_wechat_moments +-- ---------------------------- +DROP TABLE IF EXISTS `s2_wechat_moments`; +CREATE TABLE `s2_wechat_moments` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `wechatAccountId` int(11) NOT NULL COMMENT '微信账号ID', + `wechatFriendId` int(11) NULL DEFAULT NULL COMMENT '微信好友ID', + `snsId` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '朋友圈消息ID', + `commentList` json NULL COMMENT '评论列表JSON', + `createTime` bigint(20) NULL DEFAULT 0 COMMENT '创建时间戳', + `likeList` json NULL COMMENT '点赞列表JSON', + `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '朋友圈内容', + `lat` decimal(10, 6) NULL DEFAULT 0.000000 COMMENT '纬度', + `lng` decimal(10, 6) NULL DEFAULT 0.000000 COMMENT '经度', + `location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '位置信息', + `picSize` int(11) NULL DEFAULT 0 COMMENT '图片大小', + `resUrls` json NULL COMMENT '资源URL列表', + `userName` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户名', + `type` int(11) NULL DEFAULT 0 COMMENT '朋友圈类型', + `create_time` int(11) NULL DEFAULT NULL COMMENT '数据创建时间', + `update_time` int(11) NULL DEFAULT NULL COMMENT '数据更新时间', + `coverImage` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `urls` json NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `idx_sns_account`(`snsId`, `wechatAccountId`) USING BTREE, + INDEX `idx_account_friend`(`wechatAccountId`, `wechatFriendId`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 39669 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信朋友圈数据表' ROW_FORMAT = Dynamic; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/Server/微信健康分规则v2.md b/Server/微信健康分规则v2.md new file mode 100644 index 00000000..90c63f07 --- /dev/null +++ b/Server/微信健康分规则v2.md @@ -0,0 +1,58 @@ +# 微信健康分规则 v2 + +## 一、定义 + +当客户收到手机设备后,登录了微信号,我们将对其微信号进行健康分的评估。 + +**健康分 = 基础分 + 动态分** + +健康分只与系统中的"每日自动添加好友次数"这个功能相关联。\ +通过健康分体系来定义一个微信号每日**最佳、最稳定的添加次数**。\ +后期还可将健康分作为标签属性,用于快速筛选微信号。 + +**公式:每日最大加人次数 = 健康分 × 0.2** + +## 二、基础分 + +基础分为 **60--100 分**。 + +由 `60 + 40(基础加成分)` 四个维度参数组成,每个参数具有不同权重。 + +### 基础分组成 + + 类型 权重 分数 + ------------ ------ ------ + 基础信息 0.2 10 + 好友数量 0.3 30 + 默认基础分 --- 60 + +### 1. 基础信息(权重 0.2,满分 10) + + 类型 权重 分数 + -------------- ------ ------ + 已修改微信号 1 10 + +### 2. 好友数量(权重 0.3,满分 30) + + 好友数量范围 权重 分数 + -------------- ------ ------ + 0--50 0.1 3 + 51--500 0.2 6 + 501--3000 0.3 8 + 3001 以上 0.4 12 + +## 三、动态分规则 + +### 扣分规则 + + 场景 扣分 处罚 + ---------- ------ -------------- + 首次频繁 15 暂停 24 小时 + 再次频繁 25 暂停 24 小时 + 封号 60 暂停 72 小时 + +### 加分规则 + + 场景 加分 + --------------------- ------ + 连续 3 天不触发频繁 5/日 diff --git a/Touchkebao/index.html b/Touchkebao/index.html index 92656ef9..f1c04fe6 100644 --- a/Touchkebao/index.html +++ b/Touchkebao/index.html @@ -11,6 +11,10 @@ +
diff --git a/Touchkebao/src/pages/pc/ckbox/components/NavCommon/Notice.tsx b/Touchkebao/src/pages/pc/ckbox/components/NavCommon/Notice.tsx new file mode 100644 index 00000000..1249a6d7 --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/components/NavCommon/Notice.tsx @@ -0,0 +1,431 @@ +import React, { useEffect, useState } from "react"; +import { Drawer, Avatar, Space, Button, Badge, Empty, Tabs, Tag } from "antd"; +import { BellOutlined } from "@ant-design/icons"; +import { + noticeList, + readMessage, + readAll, + friendRequestList as fetchFriendRequestListApi, +} from "./api"; +import styles from "./index.module.scss"; + +interface MessageItem { + id: number; + type: number; + companyId: number; + userId: number; + bindId: number; + title: string; + message: string; + isRead: number; + createTime: string; + readTime: string; + friendData: { + nickname: string; + avatar: string; + }; +} + +interface FriendRequestItem { + taskId: number; + phone: string; + wechatId: string; + adder?: { + avatar?: string; + nickname?: string; + username?: string; + accountNickname?: string; + accountRealName?: string; + }; + status?: { + code?: number; + text?: string; + }; + time?: { + addTime?: string; + addTimeStamp?: number; + updateTime?: string; + updateTimeStamp?: number; + passTime?: string; + passTimeStamp?: number; + }; + friend?: { + nickname?: string; + isPassed?: boolean; + }; + other?: { + msgContent?: string; + remark?: string; + from?: string; + labels?: string[]; + }; +} + +const DEFAULT_QUERY = { page: 1, limit: 20 }; + +const Notice: React.FC = () => { + const [messageDrawerVisible, setMessageDrawerVisible] = useState(false); + const [activeTab, setActiveTab] = useState("messages"); + const [messageList, setMessageList] = useState([]); + const [messageCount, setMessageCount] = useState(0); + const [loading, setLoading] = useState(false); + const [friendRequestList, setFriendRequestList] = useState< + FriendRequestItem[] + >([]); + const [friendRequestLoading, setFriendRequestLoading] = useState(false); + + const fetchMessageList = async () => { + try { + setLoading(true); + const response = await noticeList(DEFAULT_QUERY); + if (response?.list) { + setMessageList(response.list); + const unreadCount = response.list.filter( + (item: MessageItem) => item.isRead === 0, + ).length; + setMessageCount(unreadCount); + } + } catch (error) { + console.error("获取消息列表失败:", error); + } finally { + setLoading(false); + } + }; + + const refreshUnreadCount = async () => { + try { + const response = await noticeList(DEFAULT_QUERY); + if (response && typeof response.noRead === "number") { + setMessageCount(response.noRead); + } + } catch (error) { + console.error("获取未读消息数失败:", error); + } + }; + + useEffect(() => { + fetchMessageList(); + const timer = window.setInterval(refreshUnreadCount, 30 * 1000); + return () => { + window.clearInterval(timer); + }; + }, []); + + const handleMessageClick = () => { + setMessageDrawerVisible(true); + fetchMessageList(); + fetchFriendRequestList(); + }; + + const handleTabChange = (key: string) => { + setActiveTab(key); + if (key === "friendRequests") { + fetchFriendRequestList(); + } + }; + + const handleMessageDrawerClose = () => { + setMessageDrawerVisible(false); + }; + + const handleReadMessage = async (messageId: number) => { + try { + await readMessage({ id: messageId }); + setMessageList(prev => { + const updated = prev.map(item => + item.id === messageId ? { ...item, isRead: 1 } : item, + ); + const unreadCount = updated.filter(item => item.isRead === 0).length; + setMessageCount(unreadCount); + return updated; + }); + } catch (error) { + console.error("标记消息已读失败:", error); + } + }; + + const handleReadAll = async () => { + try { + await readAll(); + setMessageList(prev => prev.map(item => ({ ...item, isRead: 1 }))); + setMessageCount(0); + } catch (error) { + console.error("全部已读失败:", error); + } + }; + + const fetchFriendRequestList = async () => { + try { + setFriendRequestLoading(true); + const response = await fetchFriendRequestListApi(DEFAULT_QUERY); + if (response?.list) { + setFriendRequestList(response.list); + } + } catch (error) { + console.error("获取好友添加记录失败:", error); + } finally { + setFriendRequestLoading(false); + } + }; + + const formatTime = (timeStr?: string) => { + if (!timeStr) { + return "-"; + } + const date = new Date(timeStr); + const now = new Date(); + const diff = now.getTime() - date.getTime(); + const days = Math.floor(diff / (1000 * 60 * 60 * 24)); + + if (days === 0) { + return date.toLocaleTimeString("zh-CN", { + hour: "2-digit", + minute: "2-digit", + }); + } else if (days === 1) { + return "昨天"; + } else if (days < 7) { + return `${days}天前`; + } else { + return date.toLocaleDateString("zh-CN", { + month: "2-digit", + day: "2-digit", + }); + } + }; + + const getStatusText = (statusCode?: number, statusText?: string) => { + if (statusText) { + return statusText; + } + switch (statusCode) { + case 0: + return "待处理"; + case 1: + return "已同意"; + case 2: + return "已拒绝"; + default: + return "未知"; + } + }; + + const getStatusColor = (statusCode?: number) => { + switch (statusCode) { + case 0: + return "#1890ff"; + case 1: + return "#52c41a"; + case 2: + return "#ff4d4f"; + default: + return "#999"; + } + }; + + const getFriendRequestKey = (item: FriendRequestItem) => { + return ( + item.taskId?.toString() || + item.wechatId || + item.phone || + `${item.adder?.username || "unknown"}-${item.time?.addTime || "time"}` + ); + }; + + const getAddedUserName = (item: FriendRequestItem) => { + return ( + item.friend?.nickname || + item.phone || + item.wechatId || + item.adder?.nickname || + "未知好友" + ); + }; + + const getAdderName = (item: FriendRequestItem) => { + return ( + item.adder?.nickname || + item.adder?.username || + item.adder?.accountNickname || + item.adder?.accountRealName || + "未知添加人" + ); + }; + + return ( + <> +
+ + + +
+ + + + + ) + } + > +
+ + {loading ? ( +
+ 加载中... +
+ ) : messageList.length === 0 ? ( + + ) : ( + messageList.map(item => ( +
handleReadMessage(item.id)} + > +
+ + {item.friendData?.nickname?.charAt(0) || "U"} + +
+
+
+ + {item.title} + + {item.isRead === 0 && ( +
+ )} +
+
+ {item.message} +
+ {item.isRead === 0 && ( +
+ {formatTime(item.createTime)} + +
+ )} +
+
+ )) + )} +
+ ), + }, + { + key: "friendRequests", + label: "好友添加记录", + children: ( +
+ {friendRequestLoading ? ( +
+ 加载中... +
+ ) : friendRequestList.length === 0 ? ( + + ) : ( + friendRequestList.map(item => ( +
+
+ + {item.adder?.nickname?.charAt(0) || "U"} + +
+
+
+ + 添加好友: + {getAddedUserName(item)} + + + {getStatusText( + item.status?.code, + item.status?.text, + )} + +
+
+ 申请人:{getAdderName(item)} +
+
+ 验证信息:{item.other?.msgContent || "无"} +
+ +
+ {item.other?.remark && ( + + 备注:{item.other.remark} + + )} +
+
+ {formatTime(item.time?.addTime)} +
+
+
+ )) + )} +
+ ), + }, + ]} + /> + +
+ + ); +}; + +export default Notice; diff --git a/Touchkebao/src/pages/pc/ckbox/components/NavCommon/api.ts b/Touchkebao/src/pages/pc/ckbox/components/NavCommon/api.ts index f814b6b9..dd665a02 100644 --- a/Touchkebao/src/pages/pc/ckbox/components/NavCommon/api.ts +++ b/Touchkebao/src/pages/pc/ckbox/components/NavCommon/api.ts @@ -14,3 +14,8 @@ export const readMessage = (params: { id: number }) => { export const readAll = () => { return request(`/v1/kefu/notice/readAll`, undefined, "PUT"); }; + +// 好友添加任务列表 +export const friendRequestList = (params: { page: number; limit: number }) => { + return request(`/v1/kefu/wechatFriend/addTaskList`, params, "GET"); +}; diff --git a/Touchkebao/src/pages/pc/ckbox/components/NavCommon/index.tsx b/Touchkebao/src/pages/pc/ckbox/components/NavCommon/index.tsx index 4e3fabcd..dce087d4 100644 --- a/Touchkebao/src/pages/pc/ckbox/components/NavCommon/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/components/NavCommon/index.tsx @@ -1,28 +1,18 @@ -import React, { useState, useEffect } from "react"; -import { - Layout, - Drawer, - Avatar, - Space, - Button, - Badge, - Dropdown, - Empty, -} from "antd"; +import React, { useState } from "react"; +import { Layout, Avatar, Space, Button, Dropdown, message } from "antd"; import { BarChartOutlined, UserOutlined, - BellOutlined, LogoutOutlined, ThunderboltOutlined, SettingOutlined, - CalendarOutlined, - RetweetOutlined, + SendOutlined, + ClearOutlined, } from "@ant-design/icons"; -import { noticeList, readMessage, readAll } from "./api"; import { useUserStore } from "@/store/module/user"; import { useNavigate, useLocation } from "react-router-dom"; import styles from "./index.module.scss"; +import Notice from "./Notice"; const { Header } = Layout; @@ -31,39 +21,12 @@ interface NavCommonProps { onMenuClick?: () => void; } -// 消息数据类型 -interface MessageItem { - id: number; - type: number; - companyId: number; - userId: number; - bindId: number; - title: string; - message: string; - isRead: number; - createTime: string; - readTime: string; - friendData: { - nickname: string; - avatar: string; - }; -} - const NavCommon: React.FC = ({ title = "触客宝" }) => { - const [messageDrawerVisible, setMessageDrawerVisible] = useState(false); - const [messageList, setMessageList] = useState([]); - const [messageCount, setMessageCount] = useState(0); - const [loading, setLoading] = useState(false); + const [clearingCache, setClearingCache] = useState(false); const navigate = useNavigate(); const location = useLocation(); const { user, logout } = useUserStore(); - // 初始化时获取消息列表 - useEffect(() => { - fetchMessageList(); - setInterval(IntervalMessageCount, 30 * 1000); - }, []); - // 处理菜单图标点击:在两个路由之间切换 const handleMenuClick = () => { if (!location.pathname.startsWith("/pc/powerCenter")) { @@ -72,111 +35,102 @@ const NavCommon: React.FC = ({ title = "触客宝" }) => { navigate("/pc/weChat"); } }; - - const isWeChat = () => { - return location.pathname.startsWith("/pc/weChat"); - }; - - // 定时器获取消息条数 - const IntervalMessageCount = async () => { - try { - const response = await noticeList({ page: 1, limit: 20 }); - if (response && response.noRead) { - setMessageCount(response.noRead); - } - } catch (error) { - console.error("获取消息列表失败:", error); - } - }; - // 获取消息列表 - const fetchMessageList = async () => { - try { - setLoading(true); - const response = await noticeList({ page: 1, limit: 20 }); - if (response && response.list) { - setMessageList(response.list); - // 计算未读消息数量 - const unreadCount = response.list.filter( - (item: MessageItem) => item.isRead === 0, - ).length; - setMessageCount(unreadCount); - } - } catch (error) { - console.error("获取消息列表失败:", error); - } finally { - setLoading(false); - } - }; - - // 处理消息中心点击 - const handleMessageClick = () => { - setMessageDrawerVisible(true); - fetchMessageList(); - }; - - // 处理消息抽屉关闭 - const handleMessageDrawerClose = () => { - setMessageDrawerVisible(false); - }; - // 处理退出登录 const handleLogout = () => { logout(); // 清除localStorage中的token和用户状态 navigate("/login"); // 跳转到登录页面 }; - // 处理消息已读 - const handleReadMessage = async (messageId: number) => { - try { - await readMessage({ id: messageId }); // 这里需要根据实际API调整参数 - // 更新本地状态 - setMessageList(prev => - prev.map(item => - item.id === messageId ? { ...item, isRead: 1 } : item, - ), - ); - // 重新计算未读数量 - const unreadCount = - messageList.filter(item => item.isRead === 0).length - 1; - setMessageCount(Math.max(0, unreadCount)); - } catch (error) { - console.error("标记消息已读失败:", error); - } + // 清除所有 IndexedDB 数据库 + const clearAllIndexedDB = async (): Promise => { + return new Promise((resolve, reject) => { + if (!window.indexedDB) { + resolve(); + return; + } + + // 获取所有数据库名称 + const databases: string[] = []; + const request = indexedDB.databases(); + + request + .then(dbs => { + dbs.forEach(db => { + if (db.name) { + databases.push(db.name); + } + }); + + // 删除所有数据库 + const deletePromises = databases.map(dbName => { + return new Promise((resolveDelete, rejectDelete) => { + const deleteRequest = indexedDB.deleteDatabase(dbName); + deleteRequest.onsuccess = () => { + resolveDelete(); + }; + deleteRequest.onerror = () => { + rejectDelete(new Error(`删除数据库 ${dbName} 失败`)); + }; + deleteRequest.onblocked = () => { + // 如果数据库被阻塞,等待一下再重试 + setTimeout(() => { + const retryRequest = indexedDB.deleteDatabase(dbName); + retryRequest.onsuccess = () => resolveDelete(); + retryRequest.onerror = () => + rejectDelete(new Error(`删除数据库 ${dbName} 失败`)); + }, 100); + }; + }); + }); + + Promise.all(deletePromises) + .then(() => resolve()) + .catch(reject); + }) + .catch(reject); + }); }; - // 处理全部已读 - const handleReadAll = async () => { + // 处理清除缓存 + const handleClearCache = async () => { try { - await readAll(); // 这里需要根据实际API调整参数 - // 更新本地状态 - setMessageList(prev => prev.map(item => ({ ...item, isRead: 1 }))); - setMessageCount(0); + setClearingCache(true); + const hideLoading = message.loading("正在清除缓存...", 0); + + // 1. 清除所有 localStorage + try { + localStorage.clear(); + } catch (error) { + console.warn("清除 localStorage 失败:", error); + } + + // 2. 清除所有 sessionStorage + try { + sessionStorage.clear(); + } catch (error) { + console.warn("清除 sessionStorage 失败:", error); + } + + // 3. 清除所有 IndexedDB 数据库 + try { + await clearAllIndexedDB(); + } catch (error) { + console.warn("清除 IndexedDB 失败:", error); + } + + hideLoading(); + message.success("缓存清除成功"); + + // 清除成功后跳转到登录页面 + setTimeout(() => { + logout(); + navigate("/login"); + }, 500); } catch (error) { - console.error("全部已读失败:", error); - } - }; - - // 格式化时间 - const formatTime = (timeStr: string) => { - const date = new Date(timeStr); - const now = new Date(); - const diff = now.getTime() - date.getTime(); - const days = Math.floor(diff / (1000 * 60 * 60 * 24)); - - if (days === 0) { - return date.toLocaleTimeString("zh-CN", { - hour: "2-digit", - minute: "2-digit", - }); - } else if (days === 1) { - return "昨天"; - } else if (days < 7) { - return `${days}天前`; - } else { - return date.toLocaleDateString("zh-CN", { - month: "2-digit", - day: "2-digit", - }); + console.error("清除缓存失败:", error); + message.error("清除缓存失败,请稍后重试"); + } finally { + setClearingCache(false); } }; @@ -199,6 +153,13 @@ const NavCommon: React.FC = ({ title = "触客宝" }) => { navigate("/pc/commonConfig"); }, }, + { + key: "clearCache", + icon: , + label: clearingCache ? "清除缓存中..." : "清除缓存", + onClick: handleClearCache, + disabled: clearingCache, + }, { key: "logout", icon: , @@ -220,10 +181,10 @@ const NavCommon: React.FC = ({ title = "触客宝" }) => { > {title} @@ -236,11 +197,7 @@ const NavCommon: React.FC = ({ title = "触客宝" }) => { {user?.tokens} -
- - - -
+ = ({ title = "触客宝" }) => { - - - - - } - > -
- {loading ? ( -
- 加载中... -
- ) : messageList.length === 0 ? ( - - ) : ( - messageList.map(item => ( -
handleReadMessage(item.id)} - > -
- - {item.friendData?.nickname?.charAt(0) || "U"} - -
-
-
- {item.title} - {item.isRead === 0 && ( -
- )} -
-
{item.message}
- {item.isRead === 0 && ( -
- {formatTime(item.createTime)} - -
- )} -
-
- )) - )} -
-
); }; diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/index.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/index.tsx index fed9b453..97ea2452 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/content-management/index.tsx @@ -20,7 +20,7 @@ const ContentManagement: React.FC = () => { return (
, - color: "#52c41a", - tag: "内容管理", - features: [ - "多库管理与分类", - "AI调用权限配置", - "内容检索规则设置", - "手动内容上传", - ], - path: "/pc/powerCenter/content-library", - }, + // { + // id: "content-library", + // title: "AI内容库配置", + // description: "管理AI内容库,配置调用权限,优化AI推送效果和内容质量", + // icon: , + // color: "#52c41a", + // tag: "内容管理", + // features: [ + // "多库管理与分类", + // "AI调用权限配置", + // "内容检索规则设置", + // "手动内容上传", + // ], + // path: "/pc/powerCenter/content-library", + // }, { id: "message-push-assistant", title: "消息推送助手", diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/index.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/index.tsx index 51652b1e..87090063 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/index.tsx @@ -39,18 +39,6 @@ const PowerCenter: React.FC = () => { return (
{/* 页面标题区域 */} -
-
-
-
-

功能中心

-
-

- AI智能营销·一站式客户管理·高效业务增长 -

-
-
- {/* KPI统计区域(置顶,按图展示) */}
@@ -157,9 +145,11 @@ const PowerCenter: React.FC = () => { {card.features.map((feature, index) => (
  • {feature}
  • @@ -186,7 +176,7 @@ const PowerCenter: React.FC = () => { className={styles.cardIcon} style={{ backgroundColor: getIconBgColor( - featureCategories[3].color + featureCategories[3].color, ), }} > @@ -212,9 +202,11 @@ const PowerCenter: React.FC = () => { {featureCategories[3].features.map((feature, index) => (
  • {feature}
  • diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/api.ts b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/api.ts index 34fa4bb4..5cc9147a 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/api.ts +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/api.ts @@ -1,6 +1,9 @@ import request from "@/api/request"; +import type { CreatePushTaskPayload } from "./types"; -// 获取客服列表 -export function queryWorkbenchCreate(params) { +// 创建推送任务 +export function queryWorkbenchCreate( + params: CreatePushTaskPayload, +): Promise { return request("/v1/workbench/create", params, "POST"); } diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepPushParams/index.module.scss b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepPushParams/index.module.scss new file mode 100644 index 00000000..5b3802fd --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepPushParams/index.module.scss @@ -0,0 +1,92 @@ +.stepContent { + .step4Content { + display: flex; + flex-direction: column; + gap: 24px; + max-width: 600px; + } + + .settingsPanel { + border: 1px solid #e8e8e8; + border-radius: 8px; + padding: 20px; + background: #fafafa; + + .settingsTitle { + font-size: 14px; + font-weight: 500; + color: #1a1a1a; + margin-bottom: 16px; + } + + .settingItem { + margin-bottom: 20px; + + &:last-child { + margin-bottom: 0; + } + + .settingLabel { + font-size: 14px; + font-weight: 500; + color: #1a1a1a; + margin-bottom: 12px; + } + + .settingControl { + display: flex; + align-items: center; + gap: 8px; + + span { + font-size: 14px; + color: #666; + min-width: 80px; + } + } + } + } + + .tagSection { + .settingLabel { + font-size: 14px; + font-weight: 500; + color: #1a1a1a; + margin-bottom: 12px; + } + } + + .pushPreview { + border: 1px solid #e8e8e8; + border-radius: 8px; + padding: 20px; + background: #f0f7ff; + + .previewTitle { + font-size: 14px; + font-weight: 500; + color: #1a1a1a; + margin-bottom: 12px; + } + + ul { + list-style: none; + padding: 0; + margin: 0; + + li { + font-size: 14px; + color: #666; + line-height: 1.8; + } + } + } +} + +@media (max-width: 768px) { + .stepContent { + .step4Content { + max-width: 100%; + } + } +} diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepPushParams/index.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepPushParams/index.tsx new file mode 100644 index 00000000..f15f6411 --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepPushParams/index.tsx @@ -0,0 +1,108 @@ +"use client"; + +import React from "react"; +import { Select, Slider } from "antd"; +import styles from "./index.module.scss"; + +interface StepPushParamsProps { + selectedAccounts: any[]; + selectedContacts: any[]; + targetLabel: string; + friendInterval: [number, number]; + onFriendIntervalChange: (value: [number, number]) => void; + messageInterval: [number, number]; + onMessageIntervalChange: (value: [number, number]) => void; + selectedTag: string; + onSelectedTagChange: (value: string) => void; + savedScriptGroups: any[]; +} + +const StepPushParams: React.FC = ({ + selectedAccounts, + selectedContacts, + targetLabel, + friendInterval, + onFriendIntervalChange, + messageInterval, + onMessageIntervalChange, + selectedTag, + onSelectedTagChange, + savedScriptGroups, +}) => { + return ( +
    +
    +
    +
    相关设置
    +
    +
    好友间间隔
    +
    + 间隔时间(秒) + + onFriendIntervalChange(value as [number, number]) + } + style={{ flex: 1, margin: "0 16px" }} + /> + + {friendInterval[0]} - {friendInterval[1]} + +
    +
    +
    +
    消息间间隔
    +
    + 间隔时间(秒) + + onMessageIntervalChange(value as [number, number]) + } + style={{ flex: 1, margin: "0 16px" }} + /> + + {messageInterval[0]} - {messageInterval[1]} + +
    +
    +
    + +
    +
    完成打标签
    + +
    + +
    +
    推送预览
    +
      +
    • 推送账号: {selectedAccounts.length}个
    • +
    • + 推送{targetLabel}: {selectedContacts.length}个 +
    • +
    • 话术组数: {savedScriptGroups.length}个
    • +
    • 随机推送: 否
    • +
    • 预计耗时: ~1分钟
    • +
    +
    +
    +
    + ); +}; + +export default StepPushParams; diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/InputMessage/InputMessage.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/InputMessage/InputMessage.tsx index baadd6b4..08b8fa38 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/InputMessage/InputMessage.tsx +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/InputMessage/InputMessage.tsx @@ -6,6 +6,7 @@ import { EmojiPicker } from "@/components/EmojiSeclection"; import { EmojiInfo } from "@/components/EmojiSeclection/wechatEmoji"; import SimpleFileUpload from "@/components/Upload/SimpleFileUpload"; import AudioRecorder from "@/components/Upload/AudioRecorder"; +import type { MessageItem } from "../../../types"; import styles from "./index.module.scss"; @@ -17,6 +18,7 @@ interface InputMessageProps { defaultValue?: string; onContentChange?: (value: string) => void; onSend?: (value: string) => void; + onAddMessage?: (message: MessageItem) => void; // 新增:支持添加非文本消息 clearOnSend?: boolean; placeholder?: string; hint?: React.ReactNode; @@ -68,6 +70,7 @@ const InputMessage: React.FC = ({ defaultValue = "", onContentChange, onSend, + onAddMessage, clearOnSend = false, placeholder = "输入消息...", hint, @@ -169,9 +172,44 @@ const InputMessage: React.FC = ({ msgType, content, }); - antdMessage.success("附件上传成功,可在推送时使用"); + + // 如果提供了 onAddMessage 回调,则添加到消息列表 + if (onAddMessage) { + let messageItem: MessageItem; + if ([FileType.IMAGE].includes(fileType)) { + messageItem = { + type: "image", + content: filePath.url, + fileName: filePath.name, + }; + } else if ([FileType.AUDIO].includes(fileType)) { + messageItem = { + type: "audio", + content: filePath.url, + fileName: filePath.name, + durationMs: filePath.durationMs, + }; + } else if ([FileType.FILE].includes(fileType)) { + messageItem = { + type: "file", + content: filePath.url, + fileName: filePath.name, + }; + } else { + // 默认作为文本处理 + messageItem = { + type: "text", + content: filePath.url, + fileName: filePath.name, + }; + } + onAddMessage(messageItem); + antdMessage.success("已添加消息内容"); + } else { + antdMessage.success("附件上传成功,可在推送时使用"); + } }, - [], + [onAddMessage], ); const handleAudioUploaded = useCallback( diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.module.scss b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.module.scss index 2fe9b07f..0896f840 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.module.scss +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.module.scss @@ -19,28 +19,27 @@ .step3Content { display: flex; + flex-direction: row; gap: 24px; - align-items: flex-start; .leftColumn { flex: 1; display: flex; flex-direction: column; gap: 20px; + min-width: 0; } .rightColumn { - width: 400px; flex: 1; + max-width: 500px; display: flex; flex-direction: column; gap: 20px; + flex-shrink: 0; } .previewHeader { - display: flex; - justify-content: space-between; - .previewHeaderTitle { font-size: 16px; font-weight: 600; @@ -49,78 +48,178 @@ } .messagePreview { - border: 2px dashed #52c41a; + border: 1px solid #e8e8e8; border-radius: 8px; - padding: 15px; + padding: 16px; + background: #f5f5f5; + height: 400px; + overflow-y: auto; - .messageBubble { - min-height: 100px; - background: #fff; - border-radius: 6px; - color: #666; + .messagePlaceholder { + color: #999; font-size: 14px; - line-height: 1.6; - - .currentEditingLabel { - font-size: 14px; - color: #52c41a; - font-weight: bold; - margin-bottom: 12px; - } - - .messageText { - color: #1a1a1a; - white-space: pre-wrap; - word-break: break-word; - } - - .messagePlaceholder { - color: #999; - font-size: 14px; - } - - .messageList { - display: flex; - flex-direction: column; - gap: 0; - } - - .messageItem { - display: flex; - gap: 12px; - align-items: center; - justify-content: space-between; - padding: 10px 0; - border-bottom: 1px solid #f0f0f0; - - &:last-child { - border-bottom: none; - } - - .messageText { - flex: 1; - } - - .messageAction { - color: #ff4d4f; - padding: 0; - } - } + text-align: center; + padding: 40px 20px; } - .scriptNameInput { - margin-top: 12px; + .messageList { + display: flex; + flex-direction: column; + gap: 12px; + } + + .messageBubbleWrapper { + display: flex; + flex-direction: column; + } + + .messageBubble { + display: flex; + gap: 10px; + align-items: flex-start; + + .messageAvatar { + width: 36px; + height: 36px; + border-radius: 50%; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + display: flex; + align-items: center; + justify-content: center; + color: #fff; + font-size: 18px; + flex-shrink: 0; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + + .messageContent { + display: flex; + flex-direction: column; + gap: 6px; + min-width: 0; + + .messageBubbleInner { + background: #fff; + border-radius: 8px; + padding: 10px 14px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); + position: relative; + max-width: 100%; + + &::before { + content: ""; + position: absolute; + left: -6px; + top: 12px; + width: 0; + height: 0; + border-top: 6px solid transparent; + border-bottom: 6px solid transparent; + border-right: 6px solid #fff; + } + + .messageText { + color: #1a1a1a; + white-space: pre-wrap; + word-break: break-word; + line-height: 1.6; + font-size: 14px; + } + + .messageMedia { + display: flex; + flex-direction: column; + gap: 8px; + + .messageMediaIcon { + font-size: 24px; + color: #1890ff; + margin-bottom: 4px; + } + + .messageImage { + max-width: 100%; + max-height: 200px; + border-radius: 4px; + object-fit: contain; + background: #f5f5f5; + } + + .messageFileInfo { + display: flex; + flex-direction: column; + gap: 4px; + } + + .messageFileName { + color: #1a1a1a; + font-size: 14px; + font-weight: 500; + word-break: break-all; + } + + .messageFileSize { + color: #999; + font-size: 12px; + } + } + } + + .messageActions { + display: flex; + gap: 8px; + justify-content: flex-start; + padding-left: 4px; + opacity: 0.7; + transition: opacity 0.2s; + + &:hover { + opacity: 1; + } + + .aiRewriteButton { + color: #1890ff; + padding: 0; + font-size: 12px; + height: auto; + line-height: 1.5; + + &:hover { + color: #40a9ff; + } + } + + .messageAction { + color: #ff4d4f; + padding: 0; + font-size: 12px; + height: auto; + line-height: 1.5; + + &:hover { + color: #ff7875; + } + } + } + } } } - .savedScriptGroups { - .contentLibrarySelector { - margin-bottom: 20px; - padding: 16px; - background: #fff; - border: 1px solid #e8e8e8; - border-radius: 8px; + .pushContentHeader { + .pushContentTitle { + font-size: 16px; + font-weight: 600; + color: #1a1a1a; + margin-bottom: 16px; } + } + + .contentLibrarySelector { + margin-bottom: 20px; + padding: 16px; + background: #fff; + border: 1px solid #e8e8e8; + border-radius: 8px; .contentLibraryHeader { display: flex; @@ -139,7 +238,9 @@ font-size: 12px; color: #999; } + } + .savedScriptGroups { .scriptGroupHeaderRow { display: flex; align-items: center; @@ -160,7 +261,7 @@ } .scriptGroupList { - max-height: 260px; + max-height: calc(100vh - 400px); overflow-y: auto; } @@ -241,132 +342,358 @@ } } - .messageInputArea { - .messageInput { - margin-bottom: 12px; - } + .scriptNameInput { + margin-top: 0; + } - .attachmentButtons { - display: flex; - gap: 8px; - margin-bottom: 12px; - } + .createScriptGroupButton { + margin-top: 0; + } - .aiRewriteSection { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 8px; - gap: 12px; - - .aiRewriteToggle { - display: flex; - align-items: center; - gap: 8px; + // AI改写弹窗样式 + .aiRewriteModalWrap { + :global { + .ant-modal { + border-radius: 12px; + overflow: hidden; } - .aiRewriteLabel { - font-size: 14px; - color: #1a1a1a; - } - - .aiRewriteInput { - max-width: 240px; - } - - .aiRewriteActions { - display: flex; - align-items: center; - gap: 8px; - } - - .aiRewriteButton { - min-width: 96px; + .ant-modal-content { + border-radius: 12px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12); } } } - .settingsPanel { - border: 1px solid #e8e8e8; - border-radius: 8px; - padding: 20px; - background: #fafafa; - - .settingsTitle { - font-size: 14px; - font-weight: 500; - color: #1a1a1a; - margin-bottom: 16px; - } - - .settingItem { - margin-bottom: 20px; - - &:last-child { - margin-bottom: 0; + .aiRewriteModal { + :global { + .ant-modal-header { + border-bottom: 1px solid #f0f0f0; + padding: 20px 24px; + background: linear-gradient(135deg, #fff 0%, #fafafa 100%); + border-radius: 12px 12px 0 0; } - .settingLabel { - font-size: 14px; - font-weight: 500; - color: #1a1a1a; - margin-bottom: 12px; + .ant-modal-body { + padding: 24px; + background: #ffffff; + + // 确保内容区域的样式能够正确应用 + // 原文消息内容区域 + [class*="aiRewriteModalOriginalText"] { + padding: 20px !important; + background: #f5f5f5 !important; + min-height: 80px !important; + } + + // 改写提示词输入框 + [class*="aiRewriteModalTextArea"] { + textarea.ant-input { + background: #f5f5f5 !important; + min-height: 80px !important; + } + } } - .settingControl { + .ant-modal-footer { + border-top: 1px solid #f0f0f0; + padding: 16px 24px; + background: #fafafa; display: flex; - align-items: center; - gap: 8px; + justify-content: flex-end; + gap: 12px; + border-radius: 0 0 12px 12px; + } - span { - font-size: 14px; - color: #666; - min-width: 80px; + .ant-modal-close { + color: #8c8c8c; + transition: color 0.3s; + top: 20px; + right: 24px; + + &:hover { + color: #1a1a1a; } } } } - .tagSection { - .settingLabel { - font-size: 14px; - font-weight: 500; - color: #1a1a1a; - margin-bottom: 12px; + .aiRewriteModalTitle { + display: flex; + align-items: center; + gap: 8px; + font-size: 18px; + font-weight: 600; + color: #1a1a1a; + + .aiRewriteModalTitleIcon { + font-size: 20px; } } - .pushPreview { - border: 1px solid #e8e8e8; - border-radius: 8px; - padding: 20px; - background: #f0f7ff; + .aiRewriteModalContent { + display: flex; + flex-direction: column; + gap: 24px; + } - .previewTitle { - font-size: 14px; - font-weight: 500; - color: #1a1a1a; - margin-bottom: 12px; + .aiRewriteModalCompareSection { + display: flex; + flex-direction: column; + gap: 24px; + padding: 24px; + background: linear-gradient(135deg, #fafafa 0%, #f5f5f5 100%); + border-radius: 12px; + border: 1px solid #e8e8e8; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); + } + + .aiRewriteModalDivider { + height: 1px; + background: linear-gradient( + 90deg, + transparent 0%, + #d9d9d9 20%, + #d9d9d9 80%, + transparent 100% + ); + margin: 12px 0; + position: relative; + + &::before { + content: ""; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + width: 40px; + height: 1px; + background: #1890ff; } - ul { - list-style: none; - padding: 0; - margin: 0; + &::after { + content: "→"; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + background: #fafafa; + padding: 0 8px; + color: #1890ff; + font-size: 12px; + } + } - li { + .aiRewriteModalSection { + display: flex; + flex-direction: column; + gap: 12px; + margin-bottom: 0; + } + + .aiRewriteModalSectionHeader { + display: flex; + align-items: center; + gap: 8px; + font-size: 15px; + font-weight: 600; + color: #1a1a1a; + margin-bottom: 4px; + + .aiRewriteModalSectionIcon { + font-size: 18px; + line-height: 1; + } + + .aiRewriteModalLabel { + font-size: 15px; + font-weight: 600; + color: #1a1a1a; + letter-spacing: 0.3px; + } + } + + .aiRewriteModalTextArea { + :global { + textarea.ant-input { + border-radius: 8px; + border: 1px solid #d9d9d9; + transition: all 0.3s; font-size: 14px; - color: #666; - line-height: 1.8; + padding: 12px; + background: #f5f5f5 !important; + min-height: 80px !important; + + &:hover { + border-color: #40a9ff; + background: #fafafa !important; + } + + &:focus { + border-color: #1890ff; + box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.1); + background: #ffffff !important; + } } } } -} -@media (max-width: 1200px) { - .step3Content { - .rightColumn { - width: 350px; + .aiRewriteModalOriginalText { + padding: 20px !important; + background: #f5f5f5 !important; + border: 1px solid #d9d9d9; + border-left: 4px solid #8c8c8c; + border-radius: 8px; + font-size: 14px; + color: #333; + line-height: 1.8; + white-space: pre-wrap; + word-break: break-word; + position: relative; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08); + margin-top: 4px; + transition: all 0.3s ease; + min-height: 80px !important; + + &:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12); + border-color: #bfbfbf; + background: #fafafa !important; + } + } + + .aiRewriteModalLoading { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 32px 20px; + background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%); + border: 1px solid #bae6fd; + border-radius: 8px; + gap: 12px; + + .aiRewriteModalLoadingIcon { + font-size: 32px; + animation: pulse 1.5s ease-in-out infinite; + } + + .aiRewriteModalLoadingText { + color: #1890ff; + font-size: 14px; + font-weight: 500; + } + } + + @keyframes pulse { + 0%, + 100% { + opacity: 1; + transform: scale(1); + } + 50% { + opacity: 0.7; + transform: scale(1.05); + } + } + + .aiRewriteModalResultText { + padding: 20px; + background: #f0f9ff; + border: 1px solid #91d5ff; + border-left: 4px solid #1890ff; + border-radius: 8px; + font-size: 14px; + color: #1a1a1a; + line-height: 1.8; + white-space: pre-wrap; + word-break: break-word; + max-height: 300px; + overflow-y: auto; + box-shadow: 0 2px 8px rgba(24, 144, 255, 0.15); + position: relative; + margin-top: 4px; + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-track { + background: #f0f0f0; + border-radius: 3px; + } + + &::-webkit-scrollbar-thumb { + background: #bfbfbf; + border-radius: 3px; + + &:hover { + background: #999; + } + } + } + + .aiRewriteExecuteButton { + background-color: #ff6b35 !important; + border-color: #ff6b35 !important; + font-weight: 500; + height: 36px; + padding: 0 20px; + border-radius: 6px; + box-shadow: 0 2px 4px rgba(255, 107, 53, 0.2); + transition: all 0.3s ease; + + &:hover:not(:disabled) { + background-color: #ff5722 !important; + border-color: #ff5722 !important; + box-shadow: 0 4px 8px rgba(255, 107, 53, 0.3); + transform: translateY(-1px); + } + + &:active:not(:disabled) { + background-color: #e64a19 !important; + border-color: #e64a19 !important; + transform: translateY(0); + box-shadow: 0 2px 4px rgba(255, 107, 53, 0.2); + } + + &:disabled { + background-color: #d9d9d9 !important; + border-color: #d9d9d9 !important; + color: #bfbfbf !important; + box-shadow: none; + } + } + + .confirmButton { + background-color: #07c160 !important; + border-color: #07c160 !important; + font-weight: 500; + height: 36px; + padding: 0 20px; + border-radius: 6px; + box-shadow: 0 2px 4px rgba(7, 193, 96, 0.2); + transition: all 0.3s ease; + + &:hover:not(:disabled) { + background-color: #06ad56 !important; + border-color: #06ad56 !important; + box-shadow: 0 4px 8px rgba(7, 193, 96, 0.3); + transform: translateY(-1px); + } + + &:active:not(:disabled) { + background-color: #059c4d !important; + border-color: #059c4d !important; + transform: translateY(0); + box-shadow: 0 2px 4px rgba(7, 193, 96, 0.2); + } + + &:disabled { + background-color: #d9d9d9 !important; + border-color: #d9d9d9 !important; + color: #bfbfbf !important; + box-shadow: none; } } } diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.tsx index 991f3a2a..82f109e7 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.tsx @@ -5,8 +5,7 @@ import { Button, Checkbox, Input, - Select, - Slider, + Modal, Switch, message as antdMessage, } from "antd"; @@ -15,11 +14,15 @@ import { DeleteOutlined, PlusOutlined, ReloadOutlined, + UserOutlined, + PictureOutlined, + FileOutlined, + SoundOutlined, } from "@ant-design/icons"; import type { CheckboxChangeEvent } from "antd/es/checkbox"; import styles from "./index.module.scss"; -import { ContactItem, ScriptGroup } from "../../types"; +import { ContactItem, ScriptGroup, MessageItem } from "../../types"; import InputMessage from "./InputMessage/InputMessage"; import ContentLibrarySelector from "./ContentLibrarySelector"; import type { ContentItem } from "@/components/ContentSelection/data"; @@ -36,12 +39,6 @@ interface StepSendMessageProps { targetLabel: string; messageContent: string; onMessageContentChange: (value: string) => void; - friendInterval: [number, number]; - onFriendIntervalChange: (value: [number, number]) => void; - messageInterval: [number, number]; - onMessageIntervalChange: (value: [number, number]) => void; - selectedTag: string; - onSelectedTagChange: (value: string) => void; aiRewriteEnabled: boolean; onAiRewriteToggle: (value: boolean) => void; aiPrompt: string; @@ -64,12 +61,6 @@ const StepSendMessage: React.FC = ({ targetLabel, messageContent, onMessageContentChange, - friendInterval, - onFriendIntervalChange, - messageInterval, - onMessageIntervalChange, - selectedTag, - onSelectedTagChange, aiRewriteEnabled, onAiRewriteToggle, aiPrompt, @@ -88,47 +79,111 @@ const StepSendMessage: React.FC = ({ const [savingScriptGroup, setSavingScriptGroup] = useState(false); const [aiRewriting, setAiRewriting] = useState(false); const [deletingGroupIds, setDeletingGroupIds] = useState([]); + const [aiRewriteModalVisible, setAiRewriteModalVisible] = useState(false); + const [aiRewriteModalIndex, setAiRewriteModalIndex] = useState( + null, + ); + const [aiRewriteModalPrompt, setAiRewriteModalPrompt] = useState(""); + const [aiRewritingMessage, setAiRewritingMessage] = useState(false); + const [aiRewriteResult, setAiRewriteResult] = useState(null); + + // 将 string[] 转换为 MessageItem[] + const messagesToItems = useCallback((messages: string[]): MessageItem[] => { + return messages.map(msg => { + try { + // 尝试解析为 JSON(新格式) + const parsed = JSON.parse(msg); + if (parsed && typeof parsed === "object" && "type" in parsed) { + return parsed as MessageItem; + } + } catch { + // 解析失败,作为文本消息处理 + } + // 旧格式:纯文本 + return { type: "text", content: msg }; + }); + }, []); + + // 将 MessageItem[] 转换为 string[] + const itemsToMessages = useCallback((items: MessageItem[]): string[] => { + return items.map(item => { + // 如果是纯文本消息,直接返回内容(保持向后兼容) + if (item.type === "text" && !item.fileName) { + return item.content; + } + // 其他类型序列化为 JSON + return JSON.stringify(item); + }); + }, []); + + // 内部维护的 MessageItem[] 状态 + const [messageItems, setMessageItems] = useState(() => + messagesToItems(currentScriptMessages), + ); + + // 当 currentScriptMessages 变化时,同步更新 messageItems + React.useEffect(() => { + setMessageItems(messagesToItems(currentScriptMessages)); + }, [currentScriptMessages, messagesToItems]); const handleAddMessage = useCallback( - (content?: string, showSuccess?: boolean) => { - const finalContent = (content ?? messageContent).trim(); - if (!finalContent) { - antdMessage.warning("请输入消息内容"); - return; + (content?: string | MessageItem, showSuccess?: boolean) => { + let newItem: MessageItem; + if (typeof content === "string") { + const finalContent = (content || messageContent).trim(); + if (!finalContent) { + antdMessage.warning("请输入消息内容"); + return; + } + newItem = { type: "text", content: finalContent }; + } else if (content && typeof content === "object") { + newItem = content; + } else { + const finalContent = messageContent.trim(); + if (!finalContent) { + antdMessage.warning("请输入消息内容"); + return; + } + newItem = { type: "text", content: finalContent }; } - onCurrentScriptMessagesChange([...currentScriptMessages, finalContent]); + + const newItems = [...messageItems, newItem]; + setMessageItems(newItems); + onCurrentScriptMessagesChange(itemsToMessages(newItems)); onMessageContentChange(""); if (showSuccess) { antdMessage.success("已添加消息内容"); } }, [ - currentScriptMessages, messageContent, + messageItems, onCurrentScriptMessagesChange, onMessageContentChange, + itemsToMessages, ], ); const handleRemoveMessage = useCallback( (index: number) => { - const next = currentScriptMessages.filter((_, idx) => idx !== index); - onCurrentScriptMessagesChange(next); + const next = messageItems.filter((_, idx) => idx !== index); + setMessageItems(next); + onCurrentScriptMessagesChange(itemsToMessages(next)); }, - [currentScriptMessages, onCurrentScriptMessagesChange], + [messageItems, onCurrentScriptMessagesChange, itemsToMessages], ); const handleSaveScriptGroup = useCallback(async () => { if (savingScriptGroup) { return; } - if (currentScriptMessages.length === 0) { + if (messageItems.length === 0) { antdMessage.warning("请先添加消息内容"); return; } const groupName = currentScriptName.trim() || `话术组${savedScriptGroups.length + 1}`; - const messages = [...currentScriptMessages]; + const messages = itemsToMessages(messageItems); const params: CreateContentLibraryParams = { name: groupName, sourceType: 1, @@ -155,6 +210,7 @@ const StepSendMessage: React.FC = ({ messages, }; onSavedScriptGroupsChange([...savedScriptGroups, newGroup]); + setMessageItems([]); onCurrentScriptMessagesChange([]); onCurrentScriptNameChange(""); onMessageContentChange(""); @@ -169,7 +225,7 @@ const StepSendMessage: React.FC = ({ }, [ aiPrompt, aiRewriteEnabled, - currentScriptMessages, + messageItems, currentScriptName, onCurrentScriptMessagesChange, onCurrentScriptNameChange, @@ -177,6 +233,7 @@ const StepSendMessage: React.FC = ({ onSavedScriptGroupsChange, savedScriptGroups, savingScriptGroup, + itemsToMessages, ]); const handleAiRewrite = useCallback(async () => { @@ -272,6 +329,138 @@ const StepSendMessage: React.FC = ({ onMessageContentChange, ]); + const handleOpenAiRewriteModal = useCallback((index: number) => { + setAiRewriteModalIndex(index); + setAiRewriteModalPrompt(""); + setAiRewriteModalVisible(true); + }, []); + + const handleCloseAiRewriteModal = useCallback(() => { + setAiRewriteModalVisible(false); + setAiRewriteModalIndex(null); + setAiRewriteModalPrompt(""); + setAiRewriteResult(null); + }, []); + + // 执行 AI 改写,获取结果但不立即应用 + const handleAiRewriteExecute = useCallback(async () => { + if (aiRewriteModalIndex === null) { + return; + } + const trimmedPrompt = aiRewriteModalPrompt.trim(); + if (!trimmedPrompt) { + antdMessage.warning("请输入改写提示词"); + return; + } + const messageToRewrite = messageItems[aiRewriteModalIndex]; + if (!messageToRewrite) { + antdMessage.error("消息不存在"); + return; + } + // AI改写只支持文本消息 + if (messageToRewrite.type !== "text") { + antdMessage.warning("AI改写仅支持文本消息"); + return; + } + if (aiRewritingMessage) { + return; + } + try { + setAiRewritingMessage(true); + const response = await aiEditContent({ + aiPrompt: trimmedPrompt, + content: messageToRewrite.content, + }); + const normalizedResponse = response as { + content?: string; + contentAfter?: string; + contentFront?: string; + data?: + | string + | { + content?: string; + contentAfter?: string; + contentFront?: string; + }; + result?: string; + }; + const dataField = normalizedResponse?.data; + const dataContent = + typeof dataField === "string" + ? dataField + : (dataField?.content ?? undefined); + const dataContentAfter = + typeof dataField === "string" ? undefined : dataField?.contentAfter; + const dataContentFront = + typeof dataField === "string" ? undefined : dataField?.contentFront; + + const primaryAfter = + normalizedResponse?.contentAfter ?? dataContentAfter ?? undefined; + const primaryFront = + normalizedResponse?.contentFront ?? dataContentFront ?? undefined; + + let rewrittenContent = ""; + if (typeof response === "string") { + rewrittenContent = response; + } else if (primaryAfter) { + rewrittenContent = primaryFront + ? `${primaryFront}\n${primaryAfter}` + : primaryAfter; + } else if (typeof normalizedResponse?.content === "string") { + rewrittenContent = normalizedResponse.content; + } else if (typeof dataContent === "string") { + rewrittenContent = dataContent; + } else if (typeof normalizedResponse?.result === "string") { + rewrittenContent = normalizedResponse.result; + } else if (primaryFront) { + rewrittenContent = primaryFront; + } + if (!rewrittenContent || typeof rewrittenContent !== "string") { + antdMessage.error("AI改写失败,请稍后重试"); + return; + } + setAiRewriteResult(rewrittenContent.trim()); + } catch (error) { + console.error("AI改写失败:", error); + antdMessage.error("AI改写失败,请稍后重试"); + } finally { + setAiRewritingMessage(false); + } + }, [ + aiRewriteModalIndex, + aiRewriteModalPrompt, + messageItems, + aiRewritingMessage, + ]); + + // 确认并应用 AI 改写结果 + const handleConfirmAiRewrite = useCallback(() => { + if (aiRewriteModalIndex === null || !aiRewriteResult) { + return; + } + const messageToRewrite = messageItems[aiRewriteModalIndex]; + if (!messageToRewrite) { + antdMessage.error("消息不存在"); + return; + } + const newItems = [...messageItems]; + newItems[aiRewriteModalIndex] = { + ...messageToRewrite, + content: aiRewriteResult, + }; + setMessageItems(newItems); + onCurrentScriptMessagesChange(itemsToMessages(newItems)); + handleCloseAiRewriteModal(); + antdMessage.success("AI改写完成"); + }, [ + aiRewriteModalIndex, + aiRewriteResult, + messageItems, + onCurrentScriptMessagesChange, + itemsToMessages, + handleCloseAiRewriteModal, + ]); + const handleApplyGroup = useCallback( (group: ScriptGroup) => { onCurrentScriptMessagesChange(group.messages); @@ -352,61 +541,167 @@ const StepSendMessage: React.FC = ({
    + {/* 1. 模拟推送内容 */}
    模拟推送内容
    +
    + + {/* 2. 消息列表 */} +
    + {messageItems.length === 0 ? ( +
    + 开始添加消息内容... +
    + ) : ( +
    + {messageItems.map((msgItem, index) => ( +
    +
    +
    + +
    +
    +
    + {msgItem.type === "text" && ( +
    + {msgItem.content} +
    + )} + {msgItem.type === "image" && ( +
    +
    + +
    + {msgItem.fileName { + const target = e.target as HTMLImageElement; + target.style.display = "none"; + }} + /> + {msgItem.fileName && ( +
    + {msgItem.fileName} +
    + )} +
    + )} + {msgItem.type === "file" && ( +
    +
    + +
    +
    +
    + {msgItem.fileName || "文件"} +
    + {msgItem.fileSize && ( +
    + {msgItem.fileSize >= 1024 * 1024 + ? `${(msgItem.fileSize / 1024 / 1024).toFixed(2)} MB` + : `${(msgItem.fileSize / 1024).toFixed(2)} KB`} +
    + )} +
    +
    + )} + {msgItem.type === "audio" && ( +
    +
    + +
    +
    +
    + {msgItem.fileName || "语音消息"} +
    + {msgItem.durationMs && ( +
    + {Math.floor(msgItem.durationMs / 1000)}秒 +
    + )} +
    +
    + )} +
    +
    + {msgItem.type === "text" && ( + + )} +
    +
    +
    +
    + ))} +
    + )} +
    + + {/* 3. 消息输入组件 */} +
    + handleAddMessage(value)} + onAddMessage={message => handleAddMessage(message)} + clearOnSend + placeholder="请输入内容" + hint={`按ENTER发送,按住CTRL+ENTER换行,已配置${savedScriptGroups.length}个话术组,已选择${selectedScriptGroupIds.length}个进行推送,已选${selectedContentLibraries.length}个内容库`} + /> +
    + + {/* 4. 话术组标题 */} +
    + onCurrentScriptNameChange(event.target.value)} + /> +
    + + {/* 5. 创建话术组按钮 */} +
    -
    -
    -
    当前编辑话术
    - {currentScriptMessages.length === 0 ? ( -
    - 开始添加消息内容... -
    - ) : ( -
    - {currentScriptMessages.map((msg, index) => ( -
    -
    {msg}
    -
    - ))} -
    - )} -
    -
    - - onCurrentScriptNameChange(event.target.value) - } - /> -
    -
    +
    -
    - {/* 内容库选择组件 */} +
    +
    +
    推送内容
    +
    +
    已保存话术组 ({savedScriptGroups.length}) @@ -451,137 +746,119 @@ const StepSendMessage: React.FC = ({ />
    -
    - {group.messages[0]} - {group.messages.length > 1 && " ..."} -
    )) )}
    - -
    - handleAddMessage(value)} - clearOnSend - placeholder="请输入内容" - hint={`按住CTRL+ENTER换行,已配置${savedScriptGroups.length}个话术组,已选择${selectedScriptGroupIds.length}个进行推送,已选${selectedContentLibraries.length}个内容库`} - /> -
    -
    - -
    AI智能话术改写
    -
    - {aiRewriteEnabled && ( - onAiPromptChange(event.target.value)} - className={styles.aiRewriteInput} - /> - )} -
    -
    - -
    - - -
    -
    -
    -
    - -
    -
    -
    相关设置
    -
    -
    好友间间隔
    -
    - 间隔时间(秒) - - onFriendIntervalChange(value as [number, number]) - } - style={{ flex: 1, margin: "0 16px" }} - /> - - {friendInterval[0]} - {friendInterval[1]} - -
    -
    -
    -
    消息间间隔
    -
    - 间隔时间(秒) - - onMessageIntervalChange(value as [number, number]) - } - style={{ flex: 1, margin: "0 16px" }} - /> - - {messageInterval[0]} - {messageInterval[1]} - -
    -
    -
    - -
    -
    完成打标签
    - -
    - -
    -
    推送预览
    -
      -
    • 推送账号: {selectedAccounts.length}个
    • -
    • - 推送{targetLabel}: {selectedContacts.length}个 -
    • -
    • 话术组数: {savedScriptGroups.length}个
    • -
    • 随机推送: 否
    • -
    • 预计耗时: ~1分钟
    • -
    -
    + + {/* AI改写弹窗 */} + + + AI智能改写 +
    + } + open={aiRewriteModalVisible} + onCancel={handleCloseAiRewriteModal} + width={680} + footer={[ + , + , + , + ]} + className={styles.aiRewriteModal} + wrapClassName={styles.aiRewriteModalWrap} + > +
    + {/* 原文和结果对比区域 */} +
    + {/* 原消息内容区域 */} + {aiRewriteModalIndex !== null && ( +
    +
    + 📝 + + 1原消息内容 + +
    +
    + {messageItems[aiRewriteModalIndex]?.type === "text" + ? messageItems[aiRewriteModalIndex].content + : "非文本消息不支持AI改写"} +
    +
    + )} + + {/* Loading 状态 */} + {aiRewritingMessage && ( +
    +
    +
    + AI正在改写中,请稍候... +
    +
    + )} + + {/* 分隔线 */} + {aiRewriteModalIndex !== null && aiRewriteResult && ( +
    + )} + + {/* 改写结果区域 */} + {aiRewriteResult && ( +
    +
    + + 改写结果 +
    +
    + {aiRewriteResult} +
    +
    + )} +
    + + {/* 提示词输入区域 - 放在最下面 */} +
    +
    + 💡 + 改写提示词 +
    + setAiRewriteModalPrompt(event.target.value)} + rows={3} + autoFocus + disabled={aiRewritingMessage} + className={styles.aiRewriteModalTextArea} + /> +
    +
    +
    ); }; diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/index.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/index.tsx index 5ee5b4bd..6789c6a6 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/index.tsx @@ -14,7 +14,13 @@ import { getCustomerList } from "@/pages/pc/ckbox/weChat/api"; import StepSelectAccount from "./components/StepSelectAccount"; import StepSelectContacts from "./components/StepSelectContacts"; import StepSendMessage from "./components/StepSendMessage"; -import { ContactItem, PushType, ScriptGroup } from "./types"; +import StepPushParams from "./components/StepPushParams"; +import { + ContactItem, + PushType, + ScriptGroup, + CreatePushTaskPayload, +} from "./types"; import StepIndicator from "@/components/StepIndicator"; import type { ContentItem } from "@/components/ContentSelection/data"; import type { PoolSelectionItem } from "@/components/PoolSelection/data"; @@ -163,6 +169,20 @@ const CreatePushTask: React.FC = () => { return; } setCurrentStep(3); + return; + } + + if (currentStep === 3) { + // 验证推送内容 + if ( + currentScriptMessages.length === 0 && + selectedScriptGroupIds.length === 0 && + selectedContentLibraries.length === 0 + ) { + message.warning("请至少添加一条消息、选择一个话术组或内容库"); + return; + } + setCurrentStep(4); } }; @@ -184,9 +204,12 @@ const CreatePushTask: React.FC = () => { if (creatingTask) { return; } + + // ========== 1. 数据验证和准备 ========== const selectedGroups = savedScriptGroups.filter(group => selectedScriptGroupIds.includes(group.id), ); + if ( currentScriptMessages.length === 0 && selectedGroups.length === 0 && @@ -195,19 +218,27 @@ const CreatePushTask: React.FC = () => { message.warning("请添加话术内容、选择话术组或内容库"); return; } + + // 手动消息处理 const manualMessages = currentScriptMessages .map(item => item.trim()) .filter(Boolean); + if (validPushType === "group-announcement" && manualMessages.length === 0) { message.warning("请先填写公告内容"); return; } - const toNumberId = (value: unknown) => { + + // ID 转换工具函数 + const toNumberId = (value: unknown): number | null => { + if (value === null || value === undefined) return null; const numeric = Number(value); return Number.isFinite(numeric) && !Number.isNaN(numeric) ? numeric : null; }; + + // ========== 2. 内容库ID处理 ========== const contentGroupIds = Array.from( new Set( [ @@ -220,6 +251,7 @@ const CreatePushTask: React.FC = () => { ].filter((id): id is number => id !== null), ), ); + if ( manualMessages.length === 0 && selectedGroups.length === 0 && @@ -228,6 +260,8 @@ const CreatePushTask: React.FC = () => { message.warning("缺少有效的话术内容,请重新检查"); return; } + + // ========== 3. 账号ID处理 ========== const ownerWechatIds = Array.from( new Set( selectedAccounts @@ -235,47 +269,25 @@ const CreatePushTask: React.FC = () => { .filter((id): id is number => id !== null), ), ); + if (ownerWechatIds.length === 0) { message.error("缺少有效的推送账号信息"); return; } + + // ========== 4. 联系人ID处理 ========== const selectedContactIds = Array.from( new Set( selectedContacts.map(contact => contact?.id).filter(isValidNumber), ), ); + if (selectedContactIds.length === 0) { message.error("缺少有效的推送对象"); return; } - const friendIntervalMin = friendInterval[0]; - const friendIntervalMax = friendInterval[1]; - const messageIntervalMin = messageInterval[0]; - const messageIntervalMax = messageInterval[1]; - const trafficPoolIds = selectedTrafficPools - .map(pool => pool.id) - .filter( - id => id !== undefined && id !== null && String(id).trim() !== "", - ); - const { startTime, endTime } = DEFAULT_TIME_RANGE[validPushType]; - const maxPerDay = - selectedContacts.length > 0 - ? selectedContacts.length - : DEFAULT_MAX_PER_DAY[validPushType]; - const pushOrder = DEFAULT_PUSH_ORDER[validPushType]; - const normalizedPostPushTags = - selectedTag.trim().length > 0 - ? [ - toNumberId(selectedTag) !== null - ? (toNumberId(selectedTag) as number) - : selectedTag, - ] - : []; - const taskName = - currentScriptName.trim() || - selectedGroups[0]?.name || - (manualMessages[0] ? manualMessages[0].slice(0, 20) : "") || - `推送任务-${Date.now()}`; + + // ========== 5. 设备分组ID处理(好友推送必填) ========== const deviceGroupIds = Array.from( new Set( selectedAccounts @@ -283,84 +295,191 @@ const CreatePushTask: React.FC = () => { .filter((id): id is number => id !== null), ), ); + if (validPushType === "friend-message" && deviceGroupIds.length === 0) { message.error("缺少有效的推送设备分组"); return; } - const basePayload: Record = { - name: taskName, - type: 3, - autoStart: DEFAULT_AUTO_START[validPushType], - status: 1, - pushType: DEFAULT_PUSH_TYPE[validPushType], + // ========== 6. 流量池ID处理 ========== + const trafficPoolIds = selectedTrafficPools + .map(pool => { + const id = pool.id; + if (id === undefined || id === null) return null; + const strId = String(id).trim(); + return strId !== "" ? strId : null; + }) + .filter((id): id is string => id !== null); + + // ========== 7. 时间范围 ========== + const { startTime, endTime } = DEFAULT_TIME_RANGE[validPushType]; + + // ========== 8. 每日最大推送数 ========== + const maxPerDay = + selectedContacts.length > 0 + ? selectedContacts.length + : DEFAULT_MAX_PER_DAY[validPushType]; + + // ========== 9. 推送顺序 ========== + const pushOrder = DEFAULT_PUSH_ORDER[validPushType]; + + // ========== 10. 推送后标签处理 ========== + const postPushTags = + selectedTag.trim().length > 0 + ? (() => { + const tagId = toNumberId(selectedTag); + return tagId !== null ? [tagId] : []; + })() + : []; + + // ========== 11. 任务名称 ========== + const taskName = + currentScriptName.trim() || + selectedGroups[0]?.name || + (manualMessages[0] ? manualMessages[0].slice(0, 20) : "") || + `推送任务-${Date.now()}`; + + // ========== 12. 构建基础载荷 ========== + const basePayload: CreatePushTaskPayload = { + name: String(taskName).trim(), + type: 3, // 固定值:工作台类型 + autoStart: DEFAULT_AUTO_START[validPushType] ? 1 : 0, + status: 1, // 固定值:启用 + pushType: DEFAULT_PUSH_TYPE[validPushType] ? 1 : 0, targetType: validPushType === "friend-message" ? 2 : 1, groupPushSubType: validPushType === "group-announcement" ? 2 : 1, - startTime, - endTime, - maxPerDay, - pushOrder, - friendIntervalMin, - friendIntervalMax, - messageIntervalMin, - messageIntervalMax, + startTime: String(startTime), + endTime: String(endTime), + maxPerDay: Number(maxPerDay), + pushOrder: Number(pushOrder), + friendIntervalMin: Number(friendInterval[0]), + friendIntervalMax: Number(friendInterval[1]), + messageIntervalMin: Number(messageInterval[0]), + messageIntervalMax: Number(messageInterval[1]), isRandomTemplate: selectedScriptGroupIds.length > 1 ? 1 : 0, - contentGroups: contentGroupIds, - postPushTags: normalizedPostPushTags, - ownerWechatIds, - enableAiRewrite: aiRewriteEnabled ? 1 : 0, + contentGroups: contentGroupIds.length > 0 ? contentGroupIds : [], + postPushTags: postPushTags, + ownerWechatIds: ownerWechatIds, }; - if (trafficPoolIds.length > 0) { - basePayload.trafficPools = trafficPoolIds; - } - if (validPushType === "friend-message") { - basePayload.isLoop = 0; - basePayload.deviceGroups = deviceGroupIds; - } - if (manualMessages.length > 0) { - basePayload.manualMessages = manualMessages; - if (currentScriptName.trim()) { - basePayload.manualScriptName = currentScriptName.trim(); - } - } - if (selectedScriptGroupIds.length > 0) { - basePayload.selectedScriptGroupIds = selectedScriptGroupIds; - } - if (aiRewriteEnabled && aiPrompt.trim()) { - basePayload.aiRewritePrompt = aiPrompt.trim(); - } - if (selectedGroups.length > 0) { - basePayload.scriptGroups = selectedGroups.map(group => ({ - id: group.id, - name: group.name, - messages: group.messages, - })); - } + + // ========== 13. 根据推送类型添加特定字段 ========== if (validPushType === "friend-message") { + // 好友推送特有字段 + // 注意:wechatFriends 必须是字符串数组,不是数字数组 basePayload.wechatFriends = Array.from( new Set( selectedContacts - .map(contact => toNumberId(contact?.id)) - .filter((id): id is number => id !== null), + .map(contact => { + const id = toNumberId(contact?.id); + return id !== null ? String(id) : null; + }) + .filter((id): id is string => id !== null), ), ); - basePayload.targetType = 2; + basePayload.deviceGroups = deviceGroupIds; // 必填,数字数组 + basePayload.isLoop = 0; // 固定值 + basePayload.targetType = 2; // 确保是好友类型 + basePayload.groupPushSubType = 1; // 固定为群群发 } else { + // 群推送特有字段 const groupIds = Array.from( new Set( selectedContacts - .map(contact => toNumberId(contact.groupId ?? contact.id)) + .map(contact => { + // 优先使用 groupId,如果没有则使用 id + const id = contact.groupId ?? contact.id; + return toNumberId(id); + }) .filter((id): id is number => id !== null), ), ); - basePayload.wechatGroups = groupIds; + + basePayload.wechatGroups = groupIds; // 数字数组 + basePayload.targetType = 1; // 群类型 basePayload.groupPushSubType = validPushType === "group-announcement" ? 2 : 1; - basePayload.targetType = 1; + + // 群公告特有字段 if (validPushType === "group-announcement") { basePayload.announcementContent = manualMessages.join("\n"); } } + + // ========== 14. 可选字段处理 ========== + // 流量池(如果存在) + if (trafficPoolIds.length > 0) { + basePayload.trafficPools = trafficPoolIds; // 字符串数组 + } + + // 手动消息(如果存在) + if (manualMessages.length > 0) { + basePayload.manualMessages = manualMessages; + if (currentScriptName.trim()) { + basePayload.manualScriptName = String(currentScriptName.trim()); + } + } + + // 选中的话术组ID(如果存在) + if (selectedScriptGroupIds.length > 0) { + basePayload.selectedScriptGroupIds = selectedScriptGroupIds.map(id => + String(id), + ); + } + + // AI改写相关(如果启用) + if (aiRewriteEnabled) { + basePayload.enableAiRewrite = 1; + if (aiPrompt.trim()) { + basePayload.aiRewritePrompt = String(aiPrompt.trim()); + } + } else { + basePayload.enableAiRewrite = 0; + } + + // 话术组对象(如果存在) + if (selectedGroups.length > 0) { + basePayload.scriptGroups = selectedGroups.map(group => ({ + id: String(group.id), + name: String(group.name || ""), + messages: Array.isArray(group.messages) + ? group.messages.map(msg => String(msg)) + : [], + })); + } + + // ========== 15. 数据验证和提交 ========== + // 最终验证:确保必填字段存在 + if (validPushType === "friend-message") { + if ( + !Array.isArray(basePayload.deviceGroups) || + basePayload.deviceGroups.length === 0 + ) { + message.error("好友推送必须选择设备分组"); + return; + } + if ( + !Array.isArray(basePayload.wechatFriends) || + basePayload.wechatFriends.length === 0 + ) { + message.error("好友推送必须选择好友"); + return; + } + } else { + if ( + !Array.isArray(basePayload.wechatGroups) || + basePayload.wechatGroups.length === 0 + ) { + message.error("群推送必须选择群"); + return; + } + } + + // 提交前打印日志(开发环境) + if (process.env.NODE_ENV === "development") { + console.log("提交数据:", JSON.stringify(basePayload, null, 2)); + } + + // ========== 16. 提交请求 ========== let hideLoading: ReturnType | undefined; try { setCreatingTask(true); @@ -386,7 +505,7 @@ const CreatePushTask: React.FC = () => { -
    +
    { onBackClick={handleClose} />
    - +
    + +
    } footer={ @@ -434,6 +560,12 @@ const CreatePushTask: React.FC = () => { {selectedContacts.length}个 )} + {currentStep === 4 && ( + + 推送账号: {selectedAccounts.length}个, 推送{step2Title}:{" "} + {selectedContacts.length}个 + + )}
    {currentStep === 1 && ( @@ -458,6 +590,14 @@ const CreatePushTask: React.FC = () => { )} {currentStep === 3 && ( + <> + + + + )} + {currentStep === 4 && ( <>
    diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/types.ts b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/types.ts index bd5eb0f1..f51d5ed4 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/types.ts +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/types.ts @@ -20,8 +20,67 @@ export interface ContactItem { extendFields?: Record; } +// 消息类型定义 +export type MessageType = "text" | "image" | "file" | "audio"; + +export interface MessageItem { + type: MessageType; + content: string; // 文本内容或文件URL + // 文件相关字段 + fileName?: string; // 文件名 + fileSize?: number; // 文件大小(字节) + // 语音相关字段 + durationMs?: number; // 语音时长(毫秒) +} + export interface ScriptGroup { id: string; name: string; - messages: string[]; + messages: string[]; // 保持向后兼容,但实际应该使用 MessageItem[] +} + +// 接口请求载荷类型定义 +export interface CreatePushTaskPayload { + // 基础字段 + name: string; + type: 3; // 固定值:工作台类型 + autoStart: 0 | 1; + status: 1; // 固定值:启用 + pushType: 0 | 1; // 0=定时,1=立即 + targetType: 1 | 2; // 1=群推送,2=好友推送 + groupPushSubType: 1 | 2; // 1=群群发,2=群公告 + startTime: string; // "HH:mm" 格式 + endTime: string; // "HH:mm" 格式 + maxPerDay: number; + pushOrder: 1 | 2; // 1=最早优先,2=最新优先 + friendIntervalMin: number; + friendIntervalMax: number; + messageIntervalMin: number; + messageIntervalMax: number; + isRandomTemplate: 0 | 1; + contentGroups: number[]; // 内容库ID数组 + postPushTags: number[]; // 推送后标签ID数组 + ownerWechatIds: number[]; // 客服ID数组 + + // 好友推送特有字段 + wechatFriends?: string[]; // 好友ID列表(字符串数组) + deviceGroups?: number[]; // 设备分组ID数组(好友推送时必填) + isLoop?: 0; // 固定值(好友推送时) + + // 群推送特有字段 + wechatGroups?: number[]; // 微信群ID数组 + announcementContent?: string; // 群公告内容(群公告时必填) + + // 可选字段 + trafficPools?: string[]; // 流量池ID数组(字符串数组) + manualMessages?: string[]; // 手动消息数组 + manualScriptName?: string; // 手动话术名称 + selectedScriptGroupIds?: string[]; // 选中的话术组ID数组 + enableAiRewrite?: 0 | 1; // 是否启用AI改写 + aiRewritePrompt?: string; // AI改写提示词 + scriptGroups?: Array<{ + id: string; + name: string; + messages: string[]; + }>; // 话术组对象数组 } diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/提示词.txt b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/提示词.txt deleted file mode 100644 index df1db7a1..00000000 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/提示词.txt +++ /dev/null @@ -1,79 +0,0 @@ -帮我对接数据,以下是传参实例,三种模式都是同一界面的。 - -群发助手传参实例 -{ - "name": "群群发-新品宣传", // 任务名称 - "type": 3, // 工作台类型:3=群消息推送 - "autoStart": 1, // 保存后自动启动 - "status": 1, // 是否启用 - "pushType": 0, // 推送方式:0=定时,1=立即 - "targetType": 1, // 目标类型:1=群推送 - "groupPushSubType": 1, // 群推送子类型:1=群群发,2=群公告 - "startTime": "09:00", // 推送起始时间 - "endTime": "20:00", // 推送结束时间 - "maxPerDay": 200, // 每日最大推送群数 - "pushOrder": 1, // 推送顺序:1=最早优先,2=最新优先 - "wechatGroups": [102, 205, 318], // 选择的微信群 ID 列表 - "contentGroups": [11, 12], // 关联内容库 ID 列表 - "friendIntervalMin": 10, // 群间最小间隔(秒) - "friendIntervalMax": 25, // 群间最大间隔(秒) - "messageIntervalMin": 2, // 同一群消息间最小间隔(秒) - "messageIntervalMax": 6, // 同一群消息间最大间隔(秒) - "isRandomTemplate": 1, // 是否随机选择话术模板 - "postPushTags": [301, 302], // 推送完成后打的标签 - ownerWechatIds:[123123,1231231] //客服id -} - -//群公告传参实例 -{ - "name": "群公告-双11活动", // 任务名称 - "type": 3, // 群消息推送 - "autoStart": 0, // 不自动启动 - "status": 1, // 启用 - "pushType": 1, // 立即推送 - "targetType": 1, // 群推送 - "groupPushSubType": 2, // 群公告 - "startTime": "08:30", // 开始时间 - "endTime": "18:30", // 结束时间 - "maxPerDay": 80, // 每日最大公告数 - "pushOrder": 2, // 最新优先 - "wechatGroups": [5021, 5026], // 公告目标群 - "announcementContent": "…", // 公告正文 - "enableAiRewrite": 1, // 启用 AI 改写 - "aiRewritePrompt": "保持活泼口吻…", // AI 改写提示词 - "contentGroups": [21], // 关联内容库 - "friendIntervalMin": 15, // 群间最小间隔 - "friendIntervalMax": 30, // 群间最大间隔 - "messageIntervalMin": 3, // 消息间最小间隔 - "messageIntervalMax": 9, // 消息间最大间隔 - "isRandomTemplate": 0, // 不随机模板 - "postPushTags": [], // 推送后标签 - ownerWechatIds:[123123,1231231] //客服id -} - -//好友传参实例 -{ - "name": "好友私聊-新客转化", // 任务名称 - "type": 3, // 群消息推送 - "autoStart": 1, // 自动启动 - "status": 1, // 启用 - "pushType": 0, // 定时推送 - "targetType": 2, // 目标类型:2=好友推送 - "groupPushSubType": 1, // 固定为群群发(好友推送不支持公告) - "startTime": "10:00", // 开始时间 - "endTime": "22:00", // 结束时间 - "maxPerDay": 150, // 每日最大推送好友数 - "pushOrder": 1, // 最早优先 - "wechatFriends": ["12312"], // 指定好友列表(可为空数组) - "deviceGroups": [9001, 9002], // 必选:推送设备分组 ID - "contentGroups": [41, 42], // 话术内容库 - "friendIntervalMin": 12, // 好友间最小间隔 - "friendIntervalMax": 28, // 好友间最大间隔 - "messageIntervalMin": 4, // 消息间最小间隔 - "messageIntervalMax": 10, // 消息间最大间隔 - "isRandomTemplate": 1, // 随机话术 - "postPushTags": [501], // 推送后标签 - ownerWechatIds:[123123,1231231] //客服id -} - -请求接口是 queryWorkbenchCreate diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/push-history/api.ts b/Touchkebao/src/pages/pc/ckbox/powerCenter/push-history/api.ts index a3627033..9b4a4abe 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/push-history/api.ts +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/push-history/api.ts @@ -6,8 +6,10 @@ export interface GetPushHistoryParams { page?: number; pageSize?: number; keyword?: string; - pushType?: string; - status?: string; + pushTypeCode?: string; // 推送类型代码:friend, group, announcement + status?: string; // 状态:pending, completed, failed + workbenchId?: string; + [property: string]: any; } // 获取推送历史接口响应 @@ -27,11 +29,30 @@ export interface GetPushHistoryResponse { */ export interface GetGroupPushHistoryParams { keyword?: string; - limit: string; - page: string; + limit?: string | number; + page?: string | number; + pageSize?: string | number; + pushTypeCode?: string; + status?: string; workbenchId?: string; [property: string]: any; } -export const getPushHistory = async (params: GetGroupPushHistoryParams) => { - return request("/v1/workbench/group-push-history", params, "GET"); + +export const getPushHistory = async ( + params: GetGroupPushHistoryParams, +): Promise => { + // 转换参数格式,确保 limit 和 page 是字符串 + const requestParams: Record = { + ...params, + }; + + if (params.page !== undefined) { + requestParams.page = String(params.page); + } + + if (params.pageSize !== undefined) { + requestParams.limit = String(params.pageSize); + } + + return request("/v1/workbench/group-push-history", requestParams, "GET"); }; diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/push-history/index.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/push-history/index.tsx index bd2545a7..8b71fa3c 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/push-history/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/push-history/index.tsx @@ -15,30 +15,33 @@ import styles from "./index.module.scss"; const { Option } = Select; -// 推送类型枚举 -export enum PushType { - FRIEND_MESSAGE = "friend-message", // 好友消息 - GROUP_MESSAGE = "group-message", // 群消息 - GROUP_ANNOUNCEMENT = "group-announcement", // 群公告 +// 推送类型代码枚举 +export enum PushTypeCode { + FRIEND = "friend", // 好友消息 + GROUP = "group", // 群消息 + ANNOUNCEMENT = "announcement", // 群公告 } // 推送状态枚举 export enum PushStatus { + PENDING = "pending", // 进行中 COMPLETED = "completed", // 已完成 - IN_PROGRESS = "in-progress", // 进行中 FAILED = "failed", // 失败 } // 推送历史记录接口 export interface PushHistoryRecord { - id: string; - pushType: PushType; - pushContent: string; + workbenchId: number; + taskName: string; + pushType: string; // 推送类型中文名称,如 "好友消息" + pushTypeCode: string; // 推送类型代码,如 "friend" targetCount: number; successCount: number; - failureCount: number; - status: PushStatus; + failCount: number; + status: string; // 状态代码,如 "pending" + statusText: string; // 状态中文名称,如 "进行中" createTime: string; + contentLibraryName: string; // 内容库名称 } const PushHistory: React.FC = () => { @@ -59,8 +62,8 @@ const PushHistory: React.FC = () => { try { setLoading(true); const params: any = { - page, - pageSize: pagination.pageSize, + page: String(page), + limit: String(pagination.pageSize), }; if (searchValue.trim()) { @@ -68,7 +71,7 @@ const PushHistory: React.FC = () => { } if (typeFilter !== "all") { - params.pushType = typeFilter; + params.pushTypeCode = typeFilter; } if (statusFilter !== "all") { @@ -157,13 +160,33 @@ const PushHistory: React.FC = () => { }; // 获取推送类型标签 - const getPushTypeTag = (type: PushType) => { - const typeMap = { - [PushType.FRIEND_MESSAGE]: { text: "好友消息", color: "#666" }, - [PushType.GROUP_MESSAGE]: { text: "群消息", color: "#666" }, - [PushType.GROUP_ANNOUNCEMENT]: { text: "群公告", color: "#666" }, + const getPushTypeTag = (pushType: string, pushTypeCode?: string) => { + // 优先使用中文名称,如果没有则根据代码映射 + if (pushType) { + const colorMap: Record = { + 好友消息: "#1890ff", + 群消息: "#52c41a", + 群公告: "#722ed1", + }; + return ( + + {pushType} + + ); + } + // 如果没有中文名称,根据代码映射 + const codeMap: Record = { + [PushTypeCode.FRIEND]: { text: "好友消息", color: "#1890ff" }, + [PushTypeCode.GROUP]: { text: "群消息", color: "#52c41a" }, + [PushTypeCode.ANNOUNCEMENT]: { text: "群公告", color: "#722ed1" }, }; - const config = typeMap[type] || { text: "未知", color: "#666" }; + const config = + pushTypeCode && codeMap[pushTypeCode] + ? codeMap[pushTypeCode] + : { text: pushType || "未知", color: "#666" }; return ( {config.text} @@ -172,14 +195,31 @@ const PushHistory: React.FC = () => { }; // 获取状态标签 - const getStatusTag = (status: PushStatus) => { - const statusMap = { + const getStatusTag = (status: string, statusText?: string) => { + // 优先使用中文状态文本 + const displayText = statusText || status; + + // 根据状态代码或文本匹配 + const statusMap: Record< + string, + { text: string; color: string; icon: React.ReactNode } + > = { [PushStatus.COMPLETED]: { text: "已完成", color: "#52c41a", icon: , }, - [PushStatus.IN_PROGRESS]: { + completed: { + text: "已完成", + color: "#52c41a", + icon: , + }, + [PushStatus.PENDING]: { + text: "进行中", + color: "#1890ff", + icon: , + }, + pending: { text: "进行中", color: "#1890ff", icon: , @@ -189,12 +229,43 @@ const PushHistory: React.FC = () => { color: "#ff4d4f", icon: , }, + failed: { + text: "失败", + color: "#ff4d4f", + icon: , + }, }; - const config = statusMap[status] || { - text: "未知", - color: "#666", - icon: null, + + // 根据状态文本匹配 + const textMap: Record< + string, + { text: string; color: string; icon: React.ReactNode } + > = { + 已完成: { + text: "已完成", + color: "#52c41a", + icon: , + }, + 进行中: { + text: "进行中", + color: "#1890ff", + icon: , + }, + 失败: { + text: "失败", + color: "#ff4d4f", + icon: , + }, }; + + const config = textMap[displayText] || + statusMap[status] || + statusMap[status.toLowerCase()] || { + text: displayText, + color: "#666", + icon: null, + }; + return ( { dataIndex: "pushType", key: "pushType", width: 120, - render: (type: PushType) => getPushTypeTag(type), + render: (pushType: string, record: PushHistoryRecord) => + getPushTypeTag(pushType, record.pushTypeCode), }, { - title: "推送内容", - dataIndex: "pushContent", - key: "pushContent", + title: "任务名称", + dataIndex: "taskName", + key: "taskName", ellipsis: true, render: (text: string) => {text}, }, + { + title: "内容库", + dataIndex: "contentLibraryName", + key: "contentLibraryName", + width: 150, + ellipsis: true, + render: (text: string) => ( + {text || "-"} + ), + }, { title: "目标数量", dataIndex: "targetCount", @@ -246,8 +328,8 @@ const PushHistory: React.FC = () => { }, { title: "失败数", - dataIndex: "failureCount", - key: "failureCount", + dataIndex: "failCount", + key: "failCount", width: 100, align: "center" as const, render: (count: number) => ( @@ -260,7 +342,8 @@ const PushHistory: React.FC = () => { key: "status", width: 120, align: "center" as const, - render: (status: PushStatus) => getStatusTag(status), + render: (status: string, record: PushHistoryRecord) => + getStatusTag(status, record.statusText), }, { title: "创建时间", @@ -329,9 +412,9 @@ const PushHistory: React.FC = () => { suffixIcon={} > - - - + + +
    @@ -353,7 +436,7 @@ const PushHistory: React.FC = () => { columns={columns} dataSource={dataSource} loading={loading} - rowKey="id" + rowKey="workbenchId" pagination={false} className={styles.dataTable} /> diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/selectMap.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/selectMap.module.scss new file mode 100644 index 00000000..b8f65724 --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/selectMap.module.scss @@ -0,0 +1,115 @@ +.selectMapContainer { + display: flex; + flex-direction: column; + height: 600px; + gap: 16px; +} + +.searchArea { + flex-shrink: 0; + position: relative; + z-index: 10000; +} + +.searchInput { + width: 100%; + position: relative; + z-index: 10000; +} + +.searchResults { + position: absolute; + top: 100%; + left: 0; + right: 0; + z-index: 10001; + background: #fff; + border: 1px solid #e8e8e8; + border-radius: 4px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + margin-top: 4px; + max-height: 300px; + overflow-y: auto; + pointer-events: auto; + + :global(.ant-list-item) { + cursor: pointer; + padding: 12px 16px; + transition: background-color 0.2s; + + &:hover { + background-color: #f5f5f5; + } + } +} + +.mapArea { + flex: 1; + position: relative; + border: 1px solid #e8e8e8; + border-radius: 4px; + overflow: hidden; +} + +.mapContainer { + width: 100%; + height: 100%; + min-height: 400px; +} + +.loadingOverlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(255, 255, 255, 0.8); + display: flex; + align-items: center; + justify-content: center; + z-index: 100; +} + +.locationInfo { + flex-shrink: 0; + padding: 12px 16px; + background: #f5f5f5; + border-radius: 4px; + border: 1px solid #e8e8e8; +} + +.locationLabel { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + font-weight: 500; + color: #1890ff; + margin-bottom: 8px; +} + +.locationText { + font-size: 14px; + color: #333; + margin-bottom: 4px; + word-break: break-all; +} + +.locationCoords { + font-size: 12px; + color: #999; + font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace; +} + +.resultItem { + :global(.ant-list-item-meta-title) { + font-size: 14px; + color: #333; + margin-bottom: 4px; + } + + :global(.ant-list-item-meta-description) { + font-size: 12px; + color: #999; + } +} diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/selectMap.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/selectMap.tsx new file mode 100644 index 00000000..093b3067 --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/selectMap.tsx @@ -0,0 +1,1016 @@ +import React, { useState, useEffect, useRef } from "react"; +import { Modal, Input, Button, List, message, Spin } from "antd"; +import { SearchOutlined, EnvironmentOutlined } from "@ant-design/icons"; +import { useWebSocketStore } from "@/store/module/websocket/websocket"; +import styles from "./selectMap.module.scss"; + +// 声明腾讯地图类型(新版TMap API) +declare global { + interface Window { + TMap: any; + geolocationRef: any; // 全局IP定位服务引用(TMap.service.IPLocation实例) + } +} + +interface SelectMapProps { + visible: boolean; + onClose: () => void; + contract?: any; + addMessage?: (message: any) => void; + onConfirm?: (locationXml: string) => void; +} + +interface SearchResult { + id: string; + title: string; + address: string; + location: { + lat: number; + lng: number; + }; + adcode?: string; + city?: string; + district?: string; +} + +interface LocationData { + x: string; // 纬度 + y: string; // 经度 + scale: string; // 缩放级别 + label: string; // 地址标签 + poiname: string; // POI名称 + maptype: string; // 地图类型 + poiid: string; // POI ID +} + +const SelectMap: React.FC = ({ + visible, + onClose, + contract, + addMessage, + onConfirm, +}) => { + const [searchValue, setSearchValue] = useState(""); + const [searchResults, setSearchResults] = useState([]); + const [isSearching, setIsSearching] = useState(false); + const [selectedLocation, setSelectedLocation] = useState( + null, + ); + const [map, setMap] = useState(null); + const [isReverseGeocoding, setIsReverseGeocoding] = useState(false); + const [isLocating, setIsLocating] = useState(false); + const [tmapLoaded, setTmapLoaded] = useState(false); + const mapContainerRef = useRef(null); + const geocoderRef = useRef(null); + const suggestServiceRef = useRef(null); + const markerRef = useRef(null); + const { sendCommand } = useWebSocketStore.getState(); + + // XML转义函数,防止特殊字符破坏XML格式 + const escapeXml = (str: string | undefined | null): string => { + if (!str) return ""; + return String(str) + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); + }; + + // 加载腾讯地图SDK + useEffect(() => { + // 检查TMap是否已经加载 + if (window.TMap) { + // 等待 API 完全初始化 + const checkAPIReady = () => { + if (window.TMap && window.TMap.Map) { + console.log("腾讯地图SDK已加载,API 可用"); + setTmapLoaded(true); + } else { + // 如果 API 还未完全初始化,等待一段时间后重试 + setTimeout(checkAPIReady, 100); + } + }; + checkAPIReady(); + return; + } + + // 动态加载腾讯地图SDK(使用与index.html相同的密钥) + const script = document.createElement("script"); + script.src = + "https://map.qq.com/api/gljs?v=1.exp&libraries=service&key=7DZBZ-ZSRK3-QJN3W-O5VTV-4E2P6-7GFYX"; + script.async = true; + script.onload = () => { + console.log("腾讯地图SDK脚本加载成功,等待 API 初始化..."); + // 等待 API 完全初始化 + const checkAPIReady = () => { + if (window.TMap && window.TMap.Map) { + console.log("腾讯地图SDK API 初始化完成"); + setTmapLoaded(true); + } else { + // 如果 API 还未完全初始化,等待一段时间后重试(最多等待 5 秒) + setTimeout(checkAPIReady, 100); + } + }; + // 延迟检查,给 API 一些初始化时间 + setTimeout(checkAPIReady, 200); + }; + script.onerror = () => { + console.error("腾讯地图SDK加载失败"); + message.error("地图加载失败,请刷新页面重试"); + }; + document.head.appendChild(script); + + return () => { + // 清理script标签 + if (document.head.contains(script)) { + document.head.removeChild(script); + } + }; + }, []); + + // 检查 TMap API 是否可用(辅助函数) + const checkTMapAPI = () => { + if (!window.TMap) { + console.error("TMap 未加载"); + return false; + } + + // 检查 MultiMarker 是否可用 + if (!window.TMap.MultiMarker) { + console.error("TMap.MultiMarker 不可用", { + TMap: window.TMap, + keys: Object.keys(window.TMap || {}), + }); + return false; + } + + // 检查 Style 是否存在(可能是构造函数、对象或命名空间) + // 注意:Style 可能不是构造函数,而是配置对象或命名空间 + const hasStyle = + window.TMap.MultiMarker.Style !== undefined || + window.TMap.MarkerStyle !== undefined; + + if (!hasStyle) { + console.warn("TMap Style API 不可用,将使用配置对象方式", { + MultiMarker: window.TMap.MultiMarker, + MultiMarkerKeys: Object.keys(window.TMap.MultiMarker || {}), + MarkerStyle: window.TMap.MarkerStyle, + }); + // 不返回 false,因为 MultiMarker 可能接受配置对象 + } + + return true; + }; + + // 创建标记样式(兼容不同的 API 版本) + const createMarkerStyle = (options: any) => { + // 检查 MultiMarker.Style 是否存在 + if (window.TMap.MultiMarker?.Style) { + // 如果 Style 是函数(构造函数),使用 new + if (typeof window.TMap.MultiMarker.Style === "function") { + try { + return new window.TMap.MultiMarker.Style(options); + } catch (error) { + console.warn( + "使用 new MultiMarker.Style 失败,尝试直接返回配置对象:", + error, + ); + // 如果构造函数调用失败,直接返回配置对象 + return options; + } + } else { + // 如果 Style 不是函数,可能是对象或命名空间,直接返回配置对象 + // MultiMarker 可能接受配置对象而不是 Style 实例 + console.log("MultiMarker.Style 不是构造函数,直接使用配置对象"); + return options; + } + } + // 尝试 MarkerStyle + if (window.TMap.MarkerStyle) { + if (typeof window.TMap.MarkerStyle === "function") { + try { + return new window.TMap.MarkerStyle(options); + } catch (error) { + console.warn( + "使用 new MarkerStyle 失败,尝试直接返回配置对象:", + error, + ); + return options; + } + } else { + return options; + } + } + // 如果都不存在,直接返回配置对象(让 MultiMarker 自己处理) + console.warn("未找到 Style API,直接使用配置对象"); + return options; + }; + + // 初始化地图 + useEffect(() => { + if (visible && mapContainerRef.current && tmapLoaded && window.TMap) { + console.log("开始初始化地图"); + console.log("TMap API 检查:", { + TMap: !!window.TMap, + MultiMarker: !!window.TMap.MultiMarker, + MultiMarkerStyle: !!window.TMap.MultiMarker?.Style, + MarkerStyle: !!window.TMap.MarkerStyle, + }); + + // 检查容器尺寸,确保容器有有效的宽高 + const checkContainerSize = () => { + if (!mapContainerRef.current) return false; + const rect = mapContainerRef.current.getBoundingClientRect(); + return rect.width > 0 && rect.height > 0; + }; + + let mapInstance: any = null; + let handleMapClickFn: ((evt: any) => void) | null = null; + let delayTimer: NodeJS.Timeout | null = null; + let isMounted = true; // 标记弹窗是否仍然打开 + + // 初始化地图函数(使用箭头函数避免函数声明位置问题) + const initializeMap = () => { + if (!mapContainerRef.current) return; + + try { + // 再次检查容器尺寸 + const rect = mapContainerRef.current.getBoundingClientRect(); + if (rect.width <= 0 || rect.height <= 0) { + console.error("地图容器尺寸无效:", rect); + message.error("地图容器尺寸无效,请刷新页面重试"); + return; + } + + // 创建地图实例 + const center = new window.TMap.LatLng(39.908823, 116.39747); // 默认北京 + mapInstance = new window.TMap.Map(mapContainerRef.current, { + center: center, + zoom: 13, + rotation: 0, + pitch: 0, + }); + + setMap(mapInstance); + + // 创建地理编码服务(用于反向地理编码) + geocoderRef.current = new window.TMap.service.Geocoder(); + + // 创建IP定位服务 + window.geolocationRef = new window.TMap.service.IPLocation(); + + // 创建搜索建议服务 + suggestServiceRef.current = new window.TMap.service.Suggestion({ + pageSize: 10, + autoExtend: true, + }); + + // 地图点击事件处理函数 + handleMapClickFn = (evt: any) => { + try { + // 检查弹窗是否仍然打开,以及必要的API是否可用 + if (!isMounted || !mapInstance || !mapContainerRef.current) { + return; + } + + // 检查 TMap API 是否可用 + if (!checkTMapAPI()) { + console.error("TMap API 不可用,无法创建标记点"); + message.warning("地图标记功能不可用,请刷新页面重试"); + return; + } + + const lat = evt.latLng.getLat(); + const lng = evt.latLng.getLng(); + + console.log("地图点击:", lat, lng); + + // 更新标记点 + if (markerRef.current) { + markerRef.current.setMap(null); + markerRef.current = null; + } + + // 创建标记样式 + const markerStyle = createMarkerStyle({ + width: 25, + height: 35, + anchor: { x: 12, y: 35 }, + src: "https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/markerDefault.png", + }); + + // 创建新标记 + const newMarker = new window.TMap.MultiMarker({ + id: "marker-layer", + map: mapInstance, + styles: { + marker: markerStyle, + }, + geometries: [ + { + id: "selected-marker", + styleId: "marker", + position: new window.TMap.LatLng(lat, lng), + properties: { + title: "选中位置", + }, + }, + ], + }); + + markerRef.current = newMarker; + + // 设置基本位置信息(防止白屏) + // 经纬度格式化为6位小数(微信位置消息标准格式) + setSelectedLocation({ + x: lat.toString(), + y: lng.toString(), + scale: "16", + label: `${lat}, ${lng}`, + poiname: "选中位置", + maptype: "0", + poiid: "", + }); + + // 反向地理编码获取地址 + if (!isMounted || !geocoderRef.current) { + return; + } + + setIsReverseGeocoding(true); + geocoderRef.current + .getAddress({ location: new window.TMap.LatLng(lat, lng) }) + .then((result: any) => { + // 检查弹窗是否仍然打开 + if (!isMounted) { + return; + } + setIsReverseGeocoding(false); + console.log("反向地理编码结果:", result); + + try { + if (result && result.result) { + const resultData = result.result; + const address = resultData.address || ""; + const addressComponent = + resultData.address_component || {}; + const formattedAddresses = + resultData.formatted_addresses || {}; + + // 构建地址标签 + let addressLabel = + formattedAddresses.recommend || + formattedAddresses.rough || + address; + + if (!addressLabel) { + const parts = []; + if (addressComponent.province) + parts.push(addressComponent.province); + if (addressComponent.city) + parts.push(addressComponent.city); + if (addressComponent.district) + parts.push(addressComponent.district); + if (addressComponent.street) + parts.push(addressComponent.street); + if (addressComponent.street_number) + parts.push(addressComponent.street_number); + addressLabel = parts.join(""); + } + + if (!addressLabel) { + addressLabel = `${lat}, ${lng}`; + } + + // 经纬度格式化为6位小数(微信位置消息标准格式) + setSelectedLocation({ + x: lat.toString(), + y: lng.toString(), + scale: "16", + label: addressLabel, + poiname: addressComponent.street || "未知位置", + maptype: "0", + poiid: resultData.poi_id || "", + }); + } else { + message.warning("获取详细地址信息失败,将使用坐标显示"); + } + } catch (error) { + console.error("解析地址信息错误:", error); + message.warning("解析地址信息失败,将使用坐标显示"); + } + }) + .catch((error: any) => { + // 检查弹窗是否仍然打开 + if (!isMounted) { + return; + } + setIsReverseGeocoding(false); + console.error("反向地理编码错误:", error); + message.warning("获取详细地址信息失败,将使用坐标显示"); + }); + } catch (error) { + console.error("地图点击处理错误:", error); + message.error("处理地图点击时出错,请重试"); + } + }; + + // 绑定地图点击事件 + mapInstance.on("click", handleMapClickFn); + + // 使用腾讯地图API初始化用户位置 + const initializeUserLocation = ( + lat: number, + lng: number, + isDefault: boolean = false, + ) => { + // 检查弹窗是否仍然打开,以及必要的API是否可用 + if (!isMounted || !mapInstance || !mapContainerRef.current) { + console.log("弹窗已关闭或地图实例无效,跳过初始化位置"); + return; + } + + // 检查 TMap API 是否可用 + if (!checkTMapAPI()) { + console.error("TMap API 不可用,无法创建标记点"); + message.warning("地图标记功能不可用,请刷新页面重试"); + return; + } + + // 创建位置对象 + let userLocation: any = null; + try { + console.log(isDefault ? "使用默认位置:" : "用户位置:", lat, lng); + + // 移动地图中心到位置 + userLocation = new window.TMap.LatLng(lat, lng); + mapInstance.setCenter(userLocation); + mapInstance.setZoom(16); + + // 添加标记点 + if (markerRef.current) { + markerRef.current.setMap(null); + markerRef.current = null; + } + + // 创建标记样式 + const markerStyle = createMarkerStyle({ + width: 25, + height: 35, + anchor: { x: 12, y: 35 }, + src: "https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/markerDefault.png", + }); + + const newMarker = new window.TMap.MultiMarker({ + id: "marker-layer", + map: mapInstance, + styles: { + marker: markerStyle, + }, + geometries: [ + { + id: "user-location", + styleId: "marker", + position: userLocation, + properties: { + title: isDefault ? "默认位置" : "当前位置", + }, + }, + ], + }); + + markerRef.current = newMarker; + } catch (error) { + console.error("创建标记点失败:", error); + // 即使创建标记失败,也设置基本的位置信息 + // 经纬度格式化为6位小数(微信位置消息标准格式) + setSelectedLocation({ + x: lat.toString(), + y: lng.toString(), + scale: "16", + label: `${lat}, ${lng}`, + poiname: isDefault ? "默认位置" : "当前位置", + maptype: "0", + poiid: "", + }); + return; + } + + // 使用腾讯地图服务获取该位置的地址信息 + if (!isMounted || !geocoderRef.current || !userLocation) { + return; + } + + setIsReverseGeocoding(true); + geocoderRef.current + .getAddress({ location: userLocation }) + .then((result: any) => { + // 检查弹窗是否仍然打开 + if (!isMounted) { + return; + } + setIsReverseGeocoding(false); + if (result && result.result) { + const resultData = result.result; + const formattedAddresses = + resultData.formatted_addresses || {}; + const addressComponent = resultData.address_component || {}; + + const addressLabel = + formattedAddresses.recommend || + formattedAddresses.rough || + resultData.address || + `${lat}, ${lng}`; + + // 经纬度格式化为6位小数(微信位置消息标准格式) + setSelectedLocation({ + x: lat.toString(), + y: lng.toString(), + scale: "16", + label: addressLabel, + poiname: + addressComponent.street || + (isDefault ? "默认位置" : "当前位置"), + maptype: "0", + poiid: resultData.poi_id || "", + }); + } + }) + .catch((error: any) => { + // 检查弹窗是否仍然打开 + if (!isMounted) { + return; + } + setIsReverseGeocoding(false); + console.error("获取地址信息失败:", error); + // 即使获取地址失败,也设置基本的位置信息 + // 经纬度格式化为6位小数(微信位置消息标准格式) + setSelectedLocation({ + x: lat.toString(), + y: lng.toString(), + scale: "16", + label: `${lat}, ${lng}`, + poiname: isDefault ? "默认位置" : "当前位置", + maptype: "0", + poiid: "", + }); + }); + }; + + // 使用腾讯地图IP定位获取用户位置 + setIsLocating(true); + try { + if (window.geolocationRef) { + window.geolocationRef + .locate() + .then((result: any) => { + // 检查弹窗是否仍然打开 + if (!isMounted) { + return; + } + setIsLocating(false); + console.log("IP定位结果:", result); + if (result && result.result && result.result.location) { + const { lat, lng } = result.result.location; + // message.info("已定位到您的大致位置"); + initializeUserLocation(lat, lng, false); + } else { + // IP定位失败:使用默认位置 + message.info("无法获取您的位置,已定位到北京"); + // 使用默认位置(北京市) + initializeUserLocation(39.908823, 116.39747, true); + } + }) + .catch((error: any) => { + // 检查弹窗是否仍然打开 + if (!isMounted) { + return; + } + setIsLocating(false); + console.error("IP定位失败:", error); + message.info("无法获取您的位置,已定位到北京"); + // 使用默认位置(北京市) + initializeUserLocation(39.908823, 116.39747, true); + }); + } else { + // IP定位服务未初始化:使用默认位置 + setIsLocating(false); + message.info("无法获取您的位置,已定位到北京"); + // 使用默认位置(北京市) + initializeUserLocation(39.908823, 116.39747, true); + } + } catch (error) { + // 捕获任何可能的错误,防止白屏 + console.error("定位过程中发生错误:", error); + if (isMounted) { + setIsLocating(false); + message.error("定位服务出现异常,已定位到北京"); + // 使用默认位置(北京市) + initializeUserLocation(39.908823, 116.39747, true); + } + } + } catch (error) { + console.error("初始化地图时出错:", error); + message.error("地图加载失败,请刷新页面重试"); + setIsLocating(false); + } + }; + + // 使用 requestAnimationFrame 确保容器尺寸正确后再初始化 + const initTimer = requestAnimationFrame(() => { + // 再次检查容器尺寸 + if (!checkContainerSize()) { + console.log("容器尺寸无效,延迟初始化地图"); + delayTimer = setTimeout(() => { + if (checkContainerSize() && mapContainerRef.current) { + initializeMap(); + } else { + console.error("地图容器尺寸仍然无效"); + message.error("地图容器初始化失败,请刷新页面重试"); + } + }, 100); + return; + } + + // 容器尺寸有效,立即初始化 + initializeMap(); + }); + + // 清理函数 + return () => { + // 标记弹窗已关闭 + isMounted = false; + // 取消 requestAnimationFrame + cancelAnimationFrame(initTimer); + // 清理延迟定时器 + if (delayTimer) { + clearTimeout(delayTimer); + } + // 清理地图事件监听 + if (mapInstance && handleMapClickFn) { + try { + mapInstance.off("click", handleMapClickFn); + } catch (error) { + console.error("清理地图事件监听失败:", error); + } + } + // 清理地图实例 + if (mapInstance) { + try { + mapInstance.destroy(); + } catch (error) { + console.error("销毁地图实例失败:", error); + } + mapInstance = null; + } + // 清理标记点 + if (markerRef.current) { + try { + markerRef.current.setMap(null); + } catch (error) { + console.error("清理标记点失败:", error); + } + markerRef.current = null; + } + // 重置地图状态 + setMap(null); + }; + } + }, [visible, tmapLoaded]); + + // 搜索地址(获取搜索建议) + const handleSearch = () => { + try { + if (!searchValue.trim()) { + message.warning("请输入搜索关键词"); + return; + } + + if (!suggestServiceRef.current) { + message.error("搜索服务未初始化,请刷新页面重试"); + return; + } + + setIsSearching(true); + suggestServiceRef.current + .getSuggestions({ + keyword: searchValue, + location: map ? map.getCenter() : undefined, + }) + .then((result: any) => { + setIsSearching(false); + console.log("搜索建议结果:", result); + + if (result && result.data && result.data.length > 0) { + const searchResults = result.data.map((item: any) => ({ + id: item.id, + title: item.title || item.name || "", + address: item.address || "", + location: { + lat: item.location.lat, + lng: item.location.lng, + }, + adcode: item.adcode || "", + city: item.city || "", + district: item.district || "", + })); + setSearchResults(searchResults); + } else { + setSearchResults([]); + message.info("未找到相关地址"); + } + }) + .catch((error: any) => { + setIsSearching(false); + console.error("搜索失败:", error); + message.error("搜索失败,请重试"); + // 确保搜索状态被重置 + setSearchResults([]); + }); + } catch (error) { + setIsSearching(false); + console.error("搜索处理错误:", error); + message.error("搜索过程中出错,请重试"); + setSearchResults([]); + } + }; + + // 选择搜索结果 + const handleSelectResult = (result: SearchResult) => { + try { + if (!map) { + message.error("地图未初始化,请刷新页面重试"); + return; + } + + // 检查 TMap API 是否可用 + if (!checkTMapAPI()) { + console.error("TMap API 不可用,无法创建标记点"); + message.error("地图API不可用,请刷新页面重试"); + return; + } + + const lat = result.location.lat; + const lng = result.location.lng; + + console.log("选择搜索结果:", result); + + // 移动地图中心 + map.setCenter(new window.TMap.LatLng(lat, lng)); + map.setZoom(16); + + // 更新标记点 + if (markerRef.current) { + markerRef.current.setMap(null); + markerRef.current = null; + } + + // 创建标记样式 + const markerStyle = createMarkerStyle({ + width: 25, + height: 35, + anchor: { x: 12, y: 35 }, + src: "https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/markerDefault.png", + }); + + const newMarker = new window.TMap.MultiMarker({ + id: "marker-layer", + map: map, + styles: { + marker: markerStyle, + }, + geometries: [ + { + id: "selected-poi", + styleId: "marker", + position: new window.TMap.LatLng(lat, lng), + properties: { + title: result.title, + }, + }, + ], + }); + + markerRef.current = newMarker; + + // 设置选中的位置信息 + // 经纬度格式化为6位小数(微信位置消息标准格式) + setSelectedLocation({ + x: lat.toString(), + y: lng.toString(), + scale: "16", + label: result.address || result.title, + poiname: result.title || "", + maptype: "0", + poiid: result.id || "", + }); + + // 清空搜索结果 + setSearchResults([]); + setSearchValue(""); + } catch (error) { + console.error("选择搜索结果错误:", error); + message.error("选择位置时出错,请重试"); + } + }; + + // 确认选择 + const handleConfirm = () => { + try { + if (!selectedLocation) { + message.warning("请先选择位置"); + return; + } + + // 转义XML特殊字符,确保格式正确 + // 注意:经纬度在存储时已经格式化为6位小数,直接使用即可 + const escapedLabel = escapeXml(selectedLocation.label); + const escapedPoiname = escapeXml(selectedLocation.poiname); + const scale = selectedLocation.scale || "16"; + const maptype = selectedLocation.maptype || "0"; + const poiid = escapeXml(selectedLocation.poiid || ""); + + // 生成XML格式的位置信息(格式与正确示例保持一致) + const locationXml = + ''; + + // 如果有onConfirm回调,调用它 + if (onConfirm) { + onConfirm(locationXml); + } + + // 如果有addMessage和contract,发送位置消息 + if (addMessage && contract) { + const messageId = +Date.now(); + const localMessage = { + id: messageId, + wechatAccountId: contract.wechatAccountId, + wechatFriendId: contract?.chatroomId ? 0 : contract.id, + wechatChatroomId: contract?.chatroomId ? contract.id : 0, + tenantId: 0, + accountId: 0, + synergyAccountId: 0, + content: locationXml, + msgType: 48, // 位置消息类型 + msgSubType: 0, + msgSvrId: "", + isSend: true, + createTime: new Date().toISOString(), + isDeleted: false, + deleteTime: "", + sendStatus: 1, + wechatTime: Date.now(), + origin: 0, + msgId: 0, + recalled: false, + seq: messageId, + }; + + addMessage(localMessage); + console.log(locationXml); + + // 发送消息到服务器 + sendCommand("CmdSendMessage", { + wechatAccountId: contract.wechatAccountId, + wechatChatroomId: contract?.chatroomId ? contract.id : 0, + wechatFriendId: contract?.chatroomId ? 0 : contract.id, + msgSubType: 0, + msgType: 48, + content: locationXml, + seq: messageId, + }); + } + + // 关闭弹窗并重置状态 + handleClose(); + } catch (error) { + console.error("确认位置时出错:", error); + message.error("发送位置信息时出错,请重试"); + } + }; + + // 关闭弹窗 + const handleClose = () => { + setSearchValue(""); + setSearchResults([]); + setSelectedLocation(null); + if (markerRef.current) { + markerRef.current.setMap(null); + markerRef.current = null; + } + setIsSearching(false); + setIsReverseGeocoding(false); + setIsLocating(false); + onClose(); + }; + + return ( + + 取消 + , + , + ]} + > +
    + {/* 搜索区域 */} +
    + setSearchValue(e.target.value)} + onPressEnter={handleSearch} + prefix={} + suffix={ + + } + className={styles.searchInput} + /> + + {/* 搜索结果列表 */} + {searchResults.length > 0 && ( +
    + ( + handleSelectResult(item)} + > + } + title={item.title} + description={item.address} + /> + + )} + /> +
    + )} +
    + + {/* 地图区域 */} +
    + +
    + +
    + + {/* 选中位置信息 */} + {selectedLocation && ( +
    +
    + 已选择位置 +
    +
    + {selectedLocation.label || selectedLocation.poiname} +
    +
    + 经度: {selectedLocation.y}, 纬度: {selectedLocation.x} +
    +
    + )} +
    + + ); +}; + +export default SelectMap; diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx index 864ccb6e..9135624f 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx @@ -1,5 +1,15 @@ -import React, { useEffect, useState } from "react"; -import { Layout, Input, Button, Modal, message, Tooltip } from "antd"; +import React, { useEffect, useState, useRef } from "react"; +import { + Layout, + Input, + Button, + Modal, + message, + Tooltip, + AutoComplete, + Input as AntInput, + Spin, +} from "antd"; import { SendOutlined, FolderOutlined, @@ -8,6 +18,7 @@ import { CloseOutlined, MessageOutlined, ReloadOutlined, + EnvironmentOutlined, } from "@ant-design/icons"; import { ContractData, weChatGroup, ChatRecord } from "@/pages/pc/ckbox/data"; import { useWebSocketStore } from "@/store/module/websocket/websocket"; @@ -23,6 +34,7 @@ import { manualTriggerAi, } from "@/store/module/weChat/weChat"; import { useContactStore } from "@/store/module/weChat/contacts"; +import SelectMap from "./components/selectMap"; const { Footer } = Layout; const { TextArea } = Input; @@ -326,6 +338,8 @@ const MessageEnter: React.FC = ({ contract }) => { updateShowChatRecordModel(!showChatRecordModel); }; + const [mapVisible, setMapVisible] = useState(false); + return ( <> {/* 聊天输入 */} @@ -423,6 +437,12 @@ const MessageEnter: React.FC = ({ contract }) => { } className={styles.toolbarButton} /> +
    diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx index 4bfe7c51..a3de4b84 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx @@ -347,6 +347,7 @@ const MessageRecord: React.FC = ({ contract }) => { useEffect(() => { const prevMessages = prevMessagesRef.current; + const prevLength = prevMessages.length; const hasVideoStateChange = currentMessages.some((msg, index) => { // 首先检查消息对象本身是否为null或undefined @@ -384,8 +385,9 @@ const MessageRecord: React.FC = ({ contract }) => { } }); - // 只有在没有视频状态变化时才自动滚动到底部 - if (!hasVideoStateChange && isLoadingData) { + if (currentMessages.length > prevLength && !hasVideoStateChange) { + scrollToBottom(); + } else if (isLoadingData && !hasVideoStateChange) { scrollToBottom(); } diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/components/detailValue.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/components/detailValue.tsx index 4966a863..a986f5cb 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/components/detailValue.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/components/detailValue.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState, useEffect } from "react"; +import React, { useCallback, useState, useEffect, useRef } from "react"; import { Input, message } from "antd"; import { Button } from "antd-mobile"; import { EditOutlined } from "@ant-design/icons"; @@ -56,8 +56,32 @@ const DetailValue: React.FC = ({ useState>(value); const [changedKeys, setChangedKeys] = useState([]); + // 使用 useRef 存储上一次的 value,用于深度比较 + const prevValueRef = useRef>(value); + + // 深度比较函数:比较两个对象的值是否真的变化了 + const isValueChanged = useCallback( + (prev: Record, next: Record) => { + const allKeys = new Set([...Object.keys(prev), ...Object.keys(next)]); + for (const key of allKeys) { + if (prev[key] !== next[key]) { + return true; + } + } + return false; + }, + [], + ); + // 当外部value变化时,更新内部状态 + // 优化:只有当值真正变化时才重置编辑状态,避免因对象引用变化导致编辑状态丢失 useEffect(() => { + // 深度比较,只有当值真正变化时才更新 + if (!isValueChanged(prevValueRef.current, value)) { + return; + } + + // 只有在值真正变化时才更新状态 setFieldValues(value); setOriginalValues(value); setChangedKeys([]); @@ -67,7 +91,10 @@ const DetailValue: React.FC = ({ newEditingFields[field.key] = false; }); setEditingFields(newEditingFields); - }, [value, fields]); + + // 更新 ref + prevValueRef.current = value; + }, [value, fields, isValueChanged]); const handleFieldChange = useCallback( (fieldKey: string, nextVal: string) => { diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/index.tsx index 6f40e175..951b5bcc 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/index.tsx @@ -210,14 +210,34 @@ const Person: React.FC = ({ contract }) => { // 构建联系人或群聊详细信息 - const customerList = useCustomerStore(state => state.customerList); - const kfSelectedUser = useMemo(() => { - if (!contract.wechatAccountId) return null; - const matchedCustomer = customerList.find( - customer => customer.id === contract.wechatAccountId, - ); - return matchedCustomer || null; - }, [customerList, contract.wechatAccountId]); + // 优化:使用选择器函数直接订阅匹配的客服对象,避免订阅整个 customerList + // 添加相等性比较,只有当匹配的客服对象或其 labels 真正变化时才触发重新渲染 + const kfSelectedUser = useCustomerStore( + state => { + if (!contract.wechatAccountId) return null; + return ( + state.customerList.find( + customer => customer.id === contract.wechatAccountId, + ) || null + ); + }, + (prev, next) => { + // 如果都是 null,认为相等 + if (!prev && !next) return true; + // 如果一个是 null 另一个不是,认为不相等 + if (!prev || !next) return false; + // 比较关键字段:id 和 labels(因为 useEffect 中使用了 labels) + if (prev.id !== next.id) return false; + // 比较 labels 数组是否真的变化了 + const prevLabels = prev.labels || []; + const nextLabels = next.labels || []; + if (prevLabels.length !== nextLabels.length) return false; + // 深度比较 labels 数组内容(先复制再排序,避免修改原数组) + const prevLabelsStr = JSON.stringify([...prevLabels].sort()); + const nextLabelsStr = JSON.stringify([...nextLabels].sort()); + return prevLabelsStr === nextLabelsStr; + }, + ); // 不再需要从useContactStore获取getContactsByCustomer diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/CustomerList/com.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/CustomerList/com.module.scss index 9cc2941e..29e92bf8 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/CustomerList/com.module.scss +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/CustomerList/com.module.scss @@ -47,6 +47,11 @@ .active & { border-color: #1890ff; } + + &.offline { + filter: grayscale(100%); + opacity: 0.6; + } } } .allUser { diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/CustomerList/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/CustomerList/index.tsx index 9ce88a1f..55ba297f 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/CustomerList/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/CustomerList/index.tsx @@ -89,7 +89,6 @@ const CustomerList: React.FC = () => { >
    全部
    -
    {customerList.map(customer => (
    { { {!customer.avatar && customer.name.charAt(0)} -
    ))} diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx index 2625b581..0e39a94b 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx @@ -383,7 +383,7 @@ const MessageList: React.FC = () => { const requestId = ++loadRequestRef.current; const initializeSessions = async () => { - setLoading(true); + // setLoading(true); try { const cachedSessions = @@ -416,7 +416,7 @@ const MessageList: React.FC = () => { } } finally { if (!isCancelled && loadRequestRef.current === requestId) { - setLoading(false); + // setLoading(false); } } }; diff --git a/Touchkebao/src/utils/dbAction/contact.ts b/Touchkebao/src/utils/dbAction/contact.ts index abbb7fdb..661d827e 100644 --- a/Touchkebao/src/utils/dbAction/contact.ts +++ b/Touchkebao/src/utils/dbAction/contact.ts @@ -353,13 +353,13 @@ export class ContactManager { exclude: boolean = false, ): Promise { try { - console.log("getContactCount 调用参数:", { - userId, - type, - customerId, - groupIds, - exclude, - }); + // console.log("getContactCount 调用参数:", { + // userId, + // type, + // customerId, + // groupIds, + // exclude, + // }); const conditions: any[] = [ { field: "userId", operator: "equals", value: userId }, @@ -394,14 +394,14 @@ export class ContactManager { } } - console.log("查询条件:", conditions); + // console.log("查询条件:", conditions); const contacts = await contactUnifiedService.findWhereMultiple(conditions); - console.log( - `查询结果数量: ${contacts.length}, type: ${type}, groupIds: ${groupIds}`, - ); + // console.log( + // `查询结果数量: ${contacts.length}, type: ${type}, groupIds: ${groupIds}`, + // ); return contacts.length; } catch (error) { diff --git a/Touchkebao/src/utils/filter.ts b/Touchkebao/src/utils/filter.ts index 5e2d3754..22887a8b 100644 --- a/Touchkebao/src/utils/filter.ts +++ b/Touchkebao/src/utils/filter.ts @@ -58,6 +58,11 @@ export const messageFilter = (message: string) => { return "[图片]"; } + // XML 格式的位置消息:包含 ]/i.test(message)) { + return "[位置]"; + } + // 其他情况直接返回原始消息 return message; } diff --git a/Moncter/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/InputMessage/InputMessage.tsx b/Touchkebao/消息功能规划.md similarity index 100% rename from Moncter/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/InputMessage/InputMessage.tsx rename to Touchkebao/消息功能规划.md