From 16a8656b490dae559104dcd8f878a455d4ca89b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Thu, 20 Nov 2025 15:52:05 +0800 Subject: [PATCH 01/49] =?UTF-8?q?=E4=BB=8E=E6=B6=88=E6=81=AF=E6=8E=A8?= =?UTF-8?q?=E9=80=81=E5=8A=A9=E6=89=8B=E4=BB=BB=E5=8A=A1=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E4=B8=AD=E5=88=A0=E9=99=A4=E5=BC=83=E7=94=A8=E7=9A=84=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E7=A4=BA=E4=BE=8B=EF=BC=8C=E6=9B=B4=E6=96=B0=E6=8E=A8?= =?UTF-8?q?=E9=80=81=E5=8E=86=E5=8F=B2API=E4=BB=A5=E4=BD=BF=E7=94=A8pushTy?= =?UTF-8?q?peCode=E5=92=8C=E7=8A=B6=E6=80=81=EF=BC=8C=E5=B9=B6=E9=80=9A?= =?UTF-8?q?=E8=BF=87=E6=94=B9=E8=BF=9B=E7=9A=84=E7=B1=BB=E5=9E=8B=E5=A4=84?= =?UTF-8?q?=E7=90=86=E5=92=8CUI=E8=B0=83=E6=95=B4=E6=9D=A5=E5=A2=9E?= =?UTF-8?q?=E5=BC=BAPushHistory=E7=BB=84=E4=BB=B6=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../StepSendMessage/index.module.scss | 1 + .../components/StepSendMessage/index.tsx | 4 - .../create-push-task/提示词.txt | 79 --------- .../pc/ckbox/powerCenter/push-history/api.ts | 33 +++- .../ckbox/powerCenter/push-history/index.tsx | 159 +++++++++++++----- 5 files changed, 149 insertions(+), 127 deletions(-) delete mode 100644 Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/提示词.txt 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 f7c7d1a47..0896f8402 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 @@ -32,6 +32,7 @@ .rightColumn { flex: 1; + max-width: 500px; display: flex; flex-direction: column; gap: 20px; 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 ab0eefe13..82f109e71 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 @@ -746,10 +746,6 @@ const StepSendMessage: React.FC = ({ /> -
- {group.messages[0]} - {group.messages.length > 1 && " ..."} -
)) )} 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 df1db7a1d..000000000 --- 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 a36270332..9b4a4abe9 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 1236b5a86..8b71fa3c7 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", + 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} /> From b0a60e428c249e02729a0862952351eb8616cb6a Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Thu, 20 Nov 2025 16:07:57 +0800 Subject: [PATCH 02/49] =?UTF-8?q?=E8=B4=A6=E5=8F=B7=E8=AF=84=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/model/WechatAccountModel.php | 24 +- .../CheckAndFixWechatAccountCommand.php | 159 ++ .../WechatAccountHealthScoreService.php | 378 +++ .../controller/WorkbenchController.php | 244 +- Server/sql.sql | 2301 +++++++++++++++++ Server/微信健康分规则v2.md | 58 + 6 files changed, 3116 insertions(+), 48 deletions(-) create mode 100644 Server/application/command/CheckAndFixWechatAccountCommand.php create mode 100644 Server/application/common/service/WechatAccountHealthScoreService.php create mode 100644 Server/sql.sql create mode 100644 Server/微信健康分规则v2.md diff --git a/Server/application/api/model/WechatAccountModel.php b/Server/application/api/model/WechatAccountModel.php index 2f72b9082..919da478e 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/CheckAndFixWechatAccountCommand.php b/Server/application/command/CheckAndFixWechatAccountCommand.php new file mode 100644 index 000000000..e498f77ba --- /dev/null +++ b/Server/application/command/CheckAndFixWechatAccountCommand.php @@ -0,0 +1,159 @@ +setName('wechat:check-and-fix') + ->setDescription('检测和修复s2_wechat_account表中wechatId和alias不一致的问题,并更新健康分'); + } + + protected function execute(Input $input, Output $output) + { + $output->writeln("开始检测和修复s2_wechat_account表..."); + + try { + // 1. 检测wechatId和alias不一致的记录 + $output->writeln("步骤1: 检测wechatId和alias不一致的记录..."); + $inconsistentAccounts = $this->findInconsistentAccounts(); + $output->writeln("发现 " . count($inconsistentAccounts) . " 条不一致记录"); + + if (empty($inconsistentAccounts)) { + $output->writeln("没有发现不一致的记录,任务完成!"); + return; + } + + // 2. 修复不一致的记录 + $output->writeln("步骤2: 修复不一致的记录..."); + $fixedCount = $this->fixInconsistentAccounts($inconsistentAccounts); + $output->writeln("已修复 " . $fixedCount . " 条记录"); + + // 3. 更新isModifiedAlias字段 + $output->writeln("步骤3: 更新isModifiedAlias字段..."); + $updatedCount = $this->updateModifiedAliasFlag(); + $output->writeln("已更新 " . $updatedCount . " 条记录的isModifiedAlias字段"); + + // 4. 重新计算健康分 + $output->writeln("步骤4: 重新计算健康分..."); + $healthScoreService = new WechatAccountHealthScoreService(); + $accountIds = array_column($inconsistentAccounts, 'id'); + $stats = $healthScoreService->batchCalculateAndUpdate($accountIds, 100); + $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()); + } + } + + /** + * 查找wechatId和alias不一致的记录 + * + * @return array + */ + private function findInconsistentAccounts() + { + // 查找wechatId和alias不一致的记录 + // 条件:wechatId不为空,alias不为空,且wechatId != alias + $accounts = Db::table('s2_wechat_account') + ->where('isDeleted', 0) + ->where('wechatId', '<>', '') + ->where('alias', '<>', '') + ->whereRaw('wechatId != alias') + ->field('id, wechatId, alias, nickname, isModifiedAlias') + ->select(); + + return $accounts ?: []; + } + + /** + * 修复不一致的记录 + * 策略:从s2_wechat_friend表中查找最新的alias值来更新 + * + * @param array $accounts 不一致的账号列表 + * @return int 修复数量 + */ + private function fixInconsistentAccounts($accounts) + { + $fixedCount = 0; + + foreach ($accounts as $account) { + $wechatId = $account['wechatId']; + + // 从s2_wechat_friend表中查找最新的alias值 + $latestAlias = Db::table('s2_wechat_friend') + ->where('wechatId', $wechatId) + ->where('alias', '<>', '') + ->order('updateTime', 'desc') + ->value('alias'); + + // 如果找到了最新的alias,则更新 + if (!empty($latestAlias) && $latestAlias !== $account['alias']) { + Db::table('s2_wechat_account') + ->where('id', $account['id']) + ->update([ + 'alias' => $latestAlias, + 'updateTime' => time() + ]); + $fixedCount++; + } + } + + return $fixedCount; + } + + /** + * 更新isModifiedAlias字段 + * 如果wechatId和alias不一致,则标记为已修改 + * + * @return int 更新数量 + */ + private function updateModifiedAliasFlag() + { + // 更新isModifiedAlias字段:wechatId != alias 的记录标记为1 + $updatedCount = Db::table('s2_wechat_account') + ->where('isDeleted', 0) + ->where('wechatId', '<>', '') + ->where('alias', '<>', '') + ->whereRaw('wechatId != alias') + ->update([ + 'isModifiedAlias' => 1, + 'updateTime' => time() + ]); + + // 更新isModifiedAlias字段:wechatId == alias 的记录标记为0 + Db::table('s2_wechat_account') + ->where('isDeleted', 0) + ->where('wechatId', '<>', '') + ->where('alias', '<>', '') + ->whereRaw('wechatId = alias') + ->update([ + 'isModifiedAlias' => 0, + 'updateTime' => time() + ]); + + return $updatedCount; + } +} + diff --git a/Server/application/common/service/WechatAccountHealthScoreService.php b/Server/application/common/service/WechatAccountHealthScoreService.php new file mode 100644 index 000000000..cda09cfd2 --- /dev/null +++ b/Server/application/common/service/WechatAccountHealthScoreService.php @@ -0,0 +1,378 @@ +where('id', $accountId) + ->find(); + } + + if (empty($accountData)) { + throw new Exception("账号不存在:{$accountId}"); + } + + // 计算基础分 + $baseScore = $this->calculateBaseScore($accountData); + + // 计算动态分 + $dynamicScore = $this->calculateDynamicScore($accountData); + + // 计算总分 + $healthScore = $baseScore + $dynamicScore; + + // 确保健康分在合理范围内(0-100) + $healthScore = max(0, min(100, $healthScore)); + + // 更新数据库 + $updateData = [ + 'healthScore' => $healthScore, + 'baseScore' => $baseScore, + 'dynamicScore' => $dynamicScore, + 'scoreUpdateTime' => time() + ]; + + Db::table('s2_wechat_account') + ->where('id', $accountId) + ->update($updateData); + + return [ + 'accountId' => $accountId, + 'wechatId' => $accountData['wechatId'] ?? '', + 'healthScore' => $healthScore, + 'baseScore' => $baseScore, + 'dynamicScore' => $dynamicScore, + 'baseInfoScore' => $this->getBaseInfoScore($accountData), + 'friendCountScore' => $this->getFriendCountScore($accountData['totalFriend'] ?? 0), + 'maxAddFriendPerDay' => $this->getMaxAddFriendPerDay($healthScore) + ]; + + } catch (Exception $e) { + throw new Exception("计算健康分失败:" . $e->getMessage()); + } + } + + /** + * 计算基础分 + * 基础分 = 默认60分 + 基础信息分(10分) + 好友数量分(30分) + * + * @param array $accountData 账号数据 + * @return int 基础分 + */ + private function calculateBaseScore($accountData) + { + $baseScore = self::DEFAULT_BASE_SCORE; + + // 基础信息分(已修改微信号得10分) + $baseScore += $this->getBaseInfoScore($accountData); + + // 好友数量分(最高30分) + $totalFriend = $accountData['totalFriend'] ?? 0; + $baseScore += $this->getFriendCountScore($totalFriend); + + return $baseScore; + } + + /** + * 获取基础信息分 + * 已修改微信号:10分 + * + * @param array $accountData 账号数据 + * @return int 基础信息分 + */ + private function getBaseInfoScore($accountData) + { + // 检查是否已修改微信号 + // 如果isModifiedAlias字段为1,或者wechatId和alias不一致,则认为已修改 + $isModifiedAlias = isset($accountData['isModifiedAlias']) ? (int)$accountData['isModifiedAlias'] : 0; + $wechatId = trim($accountData['wechatId'] ?? ''); + $alias = trim($accountData['alias'] ?? ''); + + // 如果字段标记为已修改,或者wechatId和alias不一致,则得分 + if ($isModifiedAlias == 1 || (!empty($wechatId) && !empty($alias) && $wechatId !== $alias)) { + return self::BASE_INFO_SCORE; + } + + return 0; + } + + /** + * 获取好友数量分 + * 根据好友数量区间得分(最高30分) + * + * @param int $totalFriend 总好友数 + * @return int 好友数量分 + */ + private function getFriendCountScore($totalFriend) + { + if ($totalFriend <= 50) { + return 3; // 0-50: 3分 + } elseif ($totalFriend <= 500) { + return 6; // 51-500: 6分 + } elseif ($totalFriend <= 3000) { + return 8; // 501-3000: 8分 + } else { + return 12; // 3001以上: 12分 + } + } + + /** + * 计算动态分 + * 动态分 = 扣分 + 加分 + * + * @param array $accountData 账号数据 + * @return int 动态分 + */ + private function calculateDynamicScore($accountData) + { + $dynamicScore = 0; + + // 处理扣分 + $dynamicScore += $this->calculatePenalty($accountData); + + // 处理加分 + $dynamicScore += $this->calculateBonus($accountData); + + return $dynamicScore; + } + + /** + * 计算扣分 + * 首次频繁:-15分 + * 再次频繁:-25分 + * 封号:-60分 + * + * @param array $accountData 账号数据 + * @return int 扣分数 + */ + private function calculatePenalty($accountData) + { + $penalty = 0; + + // 检查是否有频繁记录 + $lastFrequentTime = $accountData['lastFrequentTime'] ?? null; + $frequentCount = $accountData['frequentCount'] ?? 0; + + if (!empty($lastFrequentTime)) { + // 判断是首次频繁还是再次频繁 + if ($frequentCount == 1) { + $penalty += self::PENALTY_FIRST_FREQUENT; // 首次频繁-15分 + } elseif ($frequentCount >= 2) { + $penalty += self::PENALTY_SECOND_FREQUENT; // 再次频繁-25分 + } + } + + // 检查是否封号(这里需要根据实际业务逻辑判断,比如status字段或其他标识) + // 假设status=0表示封号 + $status = $accountData['status'] ?? 1; + if ($status == 0) { + $penalty += self::PENALTY_BANNED; // 封号-60分 + } + + return $penalty; + } + + /** + * 计算加分 + * 连续3天不触发频繁:每天+5分 + * + * @param array $accountData 账号数据 + * @return int 加分数 + */ + private function calculateBonus($accountData) + { + $bonus = 0; + + $lastNoFrequentTime = $accountData['lastNoFrequentTime'] ?? null; + $consecutiveNoFrequentDays = $accountData['consecutiveNoFrequentDays'] ?? 0; + + // 如果连续不频繁天数>=3,则每天+5分 + if ($consecutiveNoFrequentDays >= 3) { + $bonus = $consecutiveNoFrequentDays * self::BONUS_NO_FREQUENT_PER_DAY; + } + + return $bonus; + } + + /** + * 根据健康分计算每日最大加人次数 + * 公式:每日最大加人次数 = 健康分 * 0.2 + * + * @param int $healthScore 健康分 + * @return int 每日最大加人次数 + */ + public function getMaxAddFriendPerDay($healthScore) + { + return (int)floor($healthScore * 0.2); + } + + /** + * 批量计算并更新多个账号的健康分 + * + * @param array $accountIds 账号ID数组 + * @param int $batchSize 每批处理数量 + * @return array 处理结果统计 + */ + public function batchCalculateAndUpdate($accountIds = [], $batchSize = 100) + { + $stats = [ + 'total' => 0, + 'success' => 0, + 'failed' => 0, + 'errors' => [] + ]; + + // 如果没有指定账号ID,则处理所有账号 + if (empty($accountIds)) { + $accountIds = Db::table('s2_wechat_account') + ->where('isDeleted', 0) + ->column('id'); + } + + $stats['total'] = count($accountIds); + + // 分批处理 + $batches = array_chunk($accountIds, $batchSize); + + foreach ($batches as $batch) { + foreach ($batch as $accountId) { + try { + $this->calculateAndUpdate($accountId); + $stats['success']++; + } catch (Exception $e) { + $stats['failed']++; + $stats['errors'][] = [ + 'accountId' => $accountId, + 'error' => $e->getMessage() + ]; + } + } + } + + return $stats; + } + + /** + * 记录频繁事件 + * + * @param int $accountId 账号ID + * @return bool + */ + public function recordFrequent($accountId) + { + $accountData = Db::table('s2_wechat_account') + ->where('id', $accountId) + ->find(); + + if (empty($accountData)) { + return false; + } + + $frequentCount = ($accountData['frequentCount'] ?? 0) + 1; + + $updateData = [ + 'lastFrequentTime' => time(), + 'frequentCount' => $frequentCount, + 'consecutiveNoFrequentDays' => 0, // 重置连续不频繁天数 + 'lastNoFrequentTime' => null + ]; + + Db::table('s2_wechat_account') + ->where('id', $accountId) + ->update($updateData); + + // 重新计算健康分 + $this->calculateAndUpdate($accountId); + + return true; + } + + /** + * 记录不频繁事件(用于加分) + * + * @param int $accountId 账号ID + * @return bool + */ + public function recordNoFrequent($accountId) + { + $accountData = Db::table('s2_wechat_account') + ->where('id', $accountId) + ->find(); + + if (empty($accountData)) { + return false; + } + + $lastNoFrequentTime = $accountData['lastNoFrequentTime'] ?? null; + $consecutiveNoFrequentDays = $accountData['consecutiveNoFrequentDays'] ?? 0; + $currentTime = time(); + + // 如果上次不频繁时间是昨天或更早,则增加连续天数 + if (empty($lastNoFrequentTime) || ($currentTime - $lastNoFrequentTime) >= 86400) { + // 如果间隔超过1天,重置为1天 + if (!empty($lastNoFrequentTime) && ($currentTime - $lastNoFrequentTime) > 86400 * 2) { + $consecutiveNoFrequentDays = 1; + } else { + $consecutiveNoFrequentDays++; + } + } + + $updateData = [ + 'lastNoFrequentTime' => $currentTime, + 'consecutiveNoFrequentDays' => $consecutiveNoFrequentDays + ]; + + Db::table('s2_wechat_account') + ->where('id', $accountId) + ->update($updateData); + + // 重新计算健康分 + $this->calculateAndUpdate($accountId); + + return true; + } +} + diff --git a/Server/application/cunkebao/controller/WorkbenchController.php b/Server/application/cunkebao/controller/WorkbenchController.php index 62e171163..11681d9d9 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/sql.sql b/Server/sql.sql new file mode 100644 index 000000000..b49716bc2 --- /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 000000000..90c63f076 --- /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/日 From 84ced0c32a3bfe0a48b00abe202ba9c06b11f13d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Thu, 20 Nov 2025 16:13:57 +0800 Subject: [PATCH 03/49] =?UTF-8?q?=E4=BC=98=E5=8C=96ProfileModules=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E4=B8=AD=E7=9A=84=E7=8A=B6=E6=80=81=E7=AE=A1=E7=90=86?= =?UTF-8?q?=EF=BC=8C=E4=BD=BF=E7=94=A8=E9=80=89=E6=8B=A9=E5=99=A8=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E8=AE=A2=E9=98=85=E5=8C=B9=E9=85=8D=E7=9A=84=E5=AE=A2?= =?UTF-8?q?=E6=9C=8D=E5=AF=B9=E8=B1=A1=EF=BC=8C=E9=81=BF=E5=85=8D=E4=B8=8D?= =?UTF-8?q?=E5=BF=85=E8=A6=81=E7=9A=84=E9=87=8D=E6=96=B0=E6=B8=B2=E6=9F=93?= =?UTF-8?q?=E3=80=82=E5=90=8C=E6=97=B6=E5=9C=A8DetailValue=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E4=B8=AD=E5=BC=95=E5=85=A5useRef=E8=BF=9B=E8=A1=8C?= =?UTF-8?q?=E6=B7=B1=E5=BA=A6=E6=AF=94=E8=BE=83=EF=BC=8C=E7=A1=AE=E4=BF=9D?= =?UTF-8?q?=E5=8F=AA=E6=9C=89=E5=9C=A8=E5=80=BC=E7=9C=9F=E6=AD=A3=E5=8F=98?= =?UTF-8?q?=E5=8C=96=E6=97=B6=E6=89=8D=E6=9B=B4=E6=96=B0=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProfileModules/components/detailValue.tsx | 31 ++++++++++++++-- .../components/ProfileModules/index.tsx | 36 ++++++++++++++----- 2 files changed, 57 insertions(+), 10 deletions(-) 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 4966a8631..a986f5cb4 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 6f40e175e..951b5bcc9 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 From 235e3ed994e5e68201f05aab3169e201d76a6b04 Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Thu, 20 Nov 2025 16:18:16 +0800 Subject: [PATCH 04/49] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Server/composer.json | 137 ++++++++++++++++++++++--------------------- 1 file changed, 69 insertions(+), 68 deletions(-) diff --git a/Server/composer.json b/Server/composer.json index 5bb827dfd..26ba92b7c 100644 --- a/Server/composer.json +++ b/Server/composer.json @@ -1,71 +1,72 @@ { - "name": "topthink/think", - "description": "the new thinkphp framework", - "type": "project", - "keywords": [ - "framework", - "thinkphp", - "ORM" + "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" + }, + { + "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" ], - "homepage": "http://thinkphp.cn/", - "license": "Apache-2.0", - "authors": [ - { - "name": "liu21st", - "email": "liu21st@gmail.com" - }, - { - "name": "yunwuxin", - "email": "448901948@qq.com" - } - ], - "require": { - "php": ">=5.6.0", - "topthink/framework": "5.1.*", - "topthink/think-installer": "~1.0", - "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.2", - "aliyuncs/oss-sdk-php": "^2.3", - "monolog/monolog": "^1.24", - "guzzlehttp/guzzle": "^6.3", - "overtrue/wechat": "~4.0", - "endroid/qr-code": "^3.5", - "phpoffice/phpspreadsheet": "^1.8", - "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", - "topthink/think-migration": "^2.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 + "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 } From a2b0041490a5eb5875de44a730784d8e00df9d21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Thu, 20 Nov 2025 16:50:20 +0800 Subject: [PATCH 05/49] =?UTF-8?q?=E6=9B=B4=E6=96=B0NavCommon=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E4=B8=AD=E7=9A=84=E6=8C=89=E9=92=AE=E5=9B=BE=E6=A0=87?= =?UTF-8?q?=E5=92=8C=E6=96=87=E6=9C=AC=EF=BC=8C=E5=B0=86=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E6=8C=89=E9=92=AE=E7=9A=84=E5=9B=BE=E6=A0=87?= =?UTF-8?q?=E6=9B=B4=E6=94=B9=E4=B8=BASendOutlined=EF=BC=8C=E5=B9=B6?= =?UTF-8?q?=E5=B0=86=E5=85=B6=E6=96=87=E6=9C=AC=E6=9B=B4=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E2=80=9C=E5=8F=91=E6=9C=8B=E5=8F=8B=E5=9C=88=E2=80=9D=E3=80=82?= =?UTF-8?q?=E5=90=8C=E6=97=B6=EF=BC=8C=E8=B0=83=E6=95=B4powerCenter?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E4=B8=AD=E7=9A=84=E6=95=B0=E6=8D=AE=E7=BB=93?= =?UTF-8?q?=E6=9E=84=EF=BC=8C=E6=B3=A8=E9=87=8A=E6=8E=89=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E5=BA=93=E9=85=8D=E7=BD=AE=E9=83=A8=E5=88=86=EF=BC=8C=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=A0=B7=E5=BC=8F=E4=BB=A3=E7=A0=81=E3=80=82?= =?UTF-8?q?=E6=9B=B4=E6=96=B0ContentManagement=E7=BB=84=E4=BB=B6=E7=9A=84?= =?UTF-8?q?=E6=A0=87=E9=A2=98=E4=B8=BA=E2=80=9C=E5=8F=91=E6=9C=8B=E5=8F=8B?= =?UTF-8?q?=E5=9C=88=E2=80=9D=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pc/ckbox/components/NavCommon/index.tsx | 6 +-- .../powerCenter/content-management/index.tsx | 2 +- .../pages/pc/ckbox/powerCenter/index.data.tsx | 37 +++++++++++-------- .../src/pages/pc/ckbox/powerCenter/index.tsx | 30 ++++++--------- 4 files changed, 36 insertions(+), 39 deletions(-) diff --git a/Touchkebao/src/pages/pc/ckbox/components/NavCommon/index.tsx b/Touchkebao/src/pages/pc/ckbox/components/NavCommon/index.tsx index 52d9e5df6..fcd3f7a45 100644 --- a/Touchkebao/src/pages/pc/ckbox/components/NavCommon/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/components/NavCommon/index.tsx @@ -17,7 +17,7 @@ import { LogoutOutlined, ThunderboltOutlined, SettingOutlined, - CalendarOutlined, + SendOutlined, ClearOutlined, } from "@ant-design/icons"; import { noticeList, readMessage, readAll } from "./api"; @@ -317,10 +317,10 @@ const NavCommon: React.FC = ({ title = "触客宝" }) => { > {title} 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 fed9b453d..97ea24520 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 51652b1e7..87090063e 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}
  • From fe12f78c1d5765110a51e400b844cdee9c2540b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Fri, 21 Nov 2025 10:34:55 +0800 Subject: [PATCH 06/49] =?UTF-8?q?=E4=BC=98=E5=8C=96MessageRecord=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E7=9A=84=E8=87=AA=E5=8A=A8=E6=BB=9A=E5=8A=A8=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E7=A1=AE=E4=BF=9D=E5=9C=A8=E6=96=B0=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E5=88=B0=E8=BE=BE=E4=B8=94=E6=B2=A1=E6=9C=89=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E7=8A=B6=E6=80=81=E5=8F=98=E5=8C=96=E6=97=B6=E6=89=8D?= =?UTF-8?q?=E6=BB=9A=E5=8A=A8=E5=88=B0=E5=BA=95=E9=83=A8=EF=BC=8C=E5=90=8C?= =?UTF-8?q?=E6=97=B6=E4=BF=AE=E5=A4=8D=E4=BA=86=E5=AF=B9=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E9=95=BF=E5=BA=A6=E7=9A=84=E5=BC=95=E7=94=A8=E4=BB=A5=E6=8F=90?= =?UTF-8?q?=E9=AB=98=E6=80=A7=E8=83=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ChatWindow/components/MessageRecord/index.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 4bfe7c513..a3de4b84b 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(); } From 3faee0d8be1ac6034d5bc69978727b53b7f1ec8a Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Fri, 21 Nov 2025 14:38:29 +0800 Subject: [PATCH 07/49] =?UTF-8?q?=E5=AE=A2=E6=9C=8D=E5=81=A5=E5=BA=B7?= =?UTF-8?q?=E5=88=86=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Server/application/command.php | 3 + .../CalculateWechatAccountScoreCommand.php | 311 +++++++++ .../CheckAndFixWechatAccountCommand.php | 159 ----- .../UpdateWechatAccountScoreCommand.php | 168 +++++ .../common/model/WechatAccountScore.php | 44 ++ .../WechatAccountHealthScoreService.php | 649 ++++++++++++++---- Server/crontab_tasks.md | 8 + .../Adapters/ChuKeBao/Adapter.php | 85 ++- 8 files changed, 1129 insertions(+), 298 deletions(-) create mode 100644 Server/application/command/CalculateWechatAccountScoreCommand.php delete mode 100644 Server/application/command/CheckAndFixWechatAccountCommand.php create mode 100644 Server/application/command/UpdateWechatAccountScoreCommand.php create mode 100644 Server/application/common/model/WechatAccountScore.php diff --git a/Server/application/command.php b/Server/application/command.php index f1d4d72bb..b5c4d0e55 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 000000000..dbc7ec5be --- /dev/null +++ b/Server/application/command/CalculateWechatAccountScoreCommand.php @@ -0,0 +1,311 @@ +setName('wechat:calculate-score') + ->setDescription('统一计算微信账号健康分(包含初始化、更新评分记录、批量计算)'); + } + + protected function execute(Input $input, Output $output) + { + $output->writeln("=========================================="); + $output->writeln("开始统一计算微信账号健康分..."); + $output->writeln("=========================================="); + + $startTime = time(); + $service = new WechatAccountHealthScoreService(); + + try { + // 步骤1: 初始化未计算基础分的账号 + $output->writeln("\n[步骤1] 初始化未计算基础分的账号..."); + $initStats = $this->initUncalculatedAccounts($service, $output); + $output->writeln("初始化完成:成功 {$initStats['success']} 条,失败 {$initStats['failed']} 条"); + + // 步骤2: 更新评分记录(根据wechatId和alias不一致情况) + $output->writeln("\n[步骤2] 更新评分记录(根据wechatId和alias不一致情况)..."); + $updateStats = $this->updateScoreRecords($service, $output); + $output->writeln("更新完成:处理了 {$updateStats['total']} 条记录"); + + // 步骤3: 批量更新健康分(只更新动态分,不重新计算基础分) + $output->writeln("\n[步骤3] 批量更新健康分(只更新动态分)..."); + $batchStats = $this->batchUpdateHealthScore($service, $output); + $output->writeln("批量更新完成:成功 {$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']} 条"); + + if (!empty($initStats['errors'])) { + $output->writeln("\n初始化错误详情:"); + foreach (array_slice($initStats['errors'], 0, 10) as $error) { + $output->writeln(" 账号ID {$error['accountId']}: {$error['error']}"); + } + if (count($initStats['errors']) > 10) { + $output->writeln(" ... 还有 " . (count($initStats['errors']) - 10) . " 个错误"); + } + } + + if (!empty($batchStats['errors'])) { + $output->writeln("\n批量更新错误详情:"); + foreach (array_slice($batchStats['errors'], 0, 10) as $error) { + $output->writeln(" 账号ID {$error['accountId']}: {$error['error']}"); + } + if (count($batchStats['errors']) > 10) { + $output->writeln(" ... 还有 " . (count($batchStats['errors']) - 10) . " 个错误"); + } + } + + } catch (\Exception $e) { + $output->writeln("\n错误: " . $e->getMessage()); + $output->writeln($e->getTraceAsString()); + } + } + + /** + * 初始化未计算基础分的账号 + * + * @param WechatAccountHealthScoreService $service + * @param Output $output + * @return array + */ + private function initUncalculatedAccounts($service, $output) + { + $stats = [ + 'total' => 0, + 'success' => 0, + 'failed' => 0, + 'errors' => [] + ]; + + // 获取所有未计算基础分的账号 + $accounts = Db::table('s2_wechat_account') + ->alias('a') + ->leftJoin(['s2_wechat_account_score' => 's'], 's.accountId = a.id') + ->where('a.isDeleted', 0) + ->where(function($query) { + $query->whereNull('s.id') + ->whereOr('s.baseScoreCalculated', 0); + }) + ->field('a.id, a.wechatId') + ->select(); + + $stats['total'] = count($accounts); + + if ($stats['total'] == 0) { + $output->writeln("没有需要初始化的账号"); + return $stats; + } + + $output->writeln("找到 {$stats['total']} 个需要初始化的账号"); + + $batchSize = 100; + $batches = array_chunk($accounts, $batchSize); + + foreach ($batches as $batchIndex => $batch) { + foreach ($batch as $account) { + try { + $service->calculateAndUpdate($account['id']); + $stats['success']++; + + if ($stats['success'] % 100 == 0) { + $output->write("."); + } + } catch (\Exception $e) { + $stats['failed']++; + $stats['errors'][] = [ + 'accountId' => $account['id'], + 'error' => $e->getMessage() + ]; + } + } + + if (($batchIndex + 1) % 10 == 0) { + $output->writeln(" 已处理 " . ($batchIndex + 1) * $batchSize . " 条"); + } + } + + return $stats; + } + + /** + * 更新评分记录(根据wechatId和alias不一致情况) + * + * @param WechatAccountHealthScoreService $service + * @param Output $output + * @return array + */ + private function updateScoreRecords($service, $output) + { + $stats = ['total' => 0]; + + // 查找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(); + + $allAccounts = array_merge($inconsistentAccounts, $consistentAccounts); + $stats['total'] = count($allAccounts); + + if ($stats['total'] == 0) { + $output->writeln("没有需要更新的账号"); + return $stats; + } + + $output->writeln("找到 {$stats['total']} 个需要更新的账号(不一致: " . count($inconsistentAccounts) . ",一致: " . count($consistentAccounts) . ")"); + + $updatedCount = 0; + + foreach ($allAccounts as $account) { + $isModifiedAlias = in_array($account['id'], array_column($inconsistentAccounts, 'id')); + $this->updateScoreRecord($account['id'], $isModifiedAlias, $service); + $updatedCount++; + + if ($updatedCount % 100 == 0) { + $output->write("."); + } + } + + if ($updatedCount > 0 && $updatedCount % 100 == 0) { + $output->writeln(""); + } + + return $stats; + } + + /** + * 批量更新健康分(只更新动态分) + * + * @param WechatAccountHealthScoreService $service + * @param Output $output + * @return array + */ + private function batchUpdateHealthScore($service, $output) + { + // 获取所有已计算基础分的账号 + $accountIds = Db::table('s2_wechat_account_score') + ->where('baseScoreCalculated', 1) + ->column('accountId'); + + $total = count($accountIds); + + if ($total == 0) { + $output->writeln("没有需要更新的账号"); + return ['success' => 0, 'failed' => 0, 'errors' => []]; + } + + $output->writeln("找到 {$total} 个需要更新动态分的账号"); + + $stats = $service->batchCalculateAndUpdate($accountIds, 100, false); + + return $stats; + } + + /** + * 更新评分记录 + * + * @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/command/CheckAndFixWechatAccountCommand.php b/Server/application/command/CheckAndFixWechatAccountCommand.php deleted file mode 100644 index e498f77ba..000000000 --- a/Server/application/command/CheckAndFixWechatAccountCommand.php +++ /dev/null @@ -1,159 +0,0 @@ -setName('wechat:check-and-fix') - ->setDescription('检测和修复s2_wechat_account表中wechatId和alias不一致的问题,并更新健康分'); - } - - protected function execute(Input $input, Output $output) - { - $output->writeln("开始检测和修复s2_wechat_account表..."); - - try { - // 1. 检测wechatId和alias不一致的记录 - $output->writeln("步骤1: 检测wechatId和alias不一致的记录..."); - $inconsistentAccounts = $this->findInconsistentAccounts(); - $output->writeln("发现 " . count($inconsistentAccounts) . " 条不一致记录"); - - if (empty($inconsistentAccounts)) { - $output->writeln("没有发现不一致的记录,任务完成!"); - return; - } - - // 2. 修复不一致的记录 - $output->writeln("步骤2: 修复不一致的记录..."); - $fixedCount = $this->fixInconsistentAccounts($inconsistentAccounts); - $output->writeln("已修复 " . $fixedCount . " 条记录"); - - // 3. 更新isModifiedAlias字段 - $output->writeln("步骤3: 更新isModifiedAlias字段..."); - $updatedCount = $this->updateModifiedAliasFlag(); - $output->writeln("已更新 " . $updatedCount . " 条记录的isModifiedAlias字段"); - - // 4. 重新计算健康分 - $output->writeln("步骤4: 重新计算健康分..."); - $healthScoreService = new WechatAccountHealthScoreService(); - $accountIds = array_column($inconsistentAccounts, 'id'); - $stats = $healthScoreService->batchCalculateAndUpdate($accountIds, 100); - $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()); - } - } - - /** - * 查找wechatId和alias不一致的记录 - * - * @return array - */ - private function findInconsistentAccounts() - { - // 查找wechatId和alias不一致的记录 - // 条件:wechatId不为空,alias不为空,且wechatId != alias - $accounts = Db::table('s2_wechat_account') - ->where('isDeleted', 0) - ->where('wechatId', '<>', '') - ->where('alias', '<>', '') - ->whereRaw('wechatId != alias') - ->field('id, wechatId, alias, nickname, isModifiedAlias') - ->select(); - - return $accounts ?: []; - } - - /** - * 修复不一致的记录 - * 策略:从s2_wechat_friend表中查找最新的alias值来更新 - * - * @param array $accounts 不一致的账号列表 - * @return int 修复数量 - */ - private function fixInconsistentAccounts($accounts) - { - $fixedCount = 0; - - foreach ($accounts as $account) { - $wechatId = $account['wechatId']; - - // 从s2_wechat_friend表中查找最新的alias值 - $latestAlias = Db::table('s2_wechat_friend') - ->where('wechatId', $wechatId) - ->where('alias', '<>', '') - ->order('updateTime', 'desc') - ->value('alias'); - - // 如果找到了最新的alias,则更新 - if (!empty($latestAlias) && $latestAlias !== $account['alias']) { - Db::table('s2_wechat_account') - ->where('id', $account['id']) - ->update([ - 'alias' => $latestAlias, - 'updateTime' => time() - ]); - $fixedCount++; - } - } - - return $fixedCount; - } - - /** - * 更新isModifiedAlias字段 - * 如果wechatId和alias不一致,则标记为已修改 - * - * @return int 更新数量 - */ - private function updateModifiedAliasFlag() - { - // 更新isModifiedAlias字段:wechatId != alias 的记录标记为1 - $updatedCount = Db::table('s2_wechat_account') - ->where('isDeleted', 0) - ->where('wechatId', '<>', '') - ->where('alias', '<>', '') - ->whereRaw('wechatId != alias') - ->update([ - 'isModifiedAlias' => 1, - 'updateTime' => time() - ]); - - // 更新isModifiedAlias字段:wechatId == alias 的记录标记为0 - Db::table('s2_wechat_account') - ->where('isDeleted', 0) - ->where('wechatId', '<>', '') - ->where('alias', '<>', '') - ->whereRaw('wechatId = alias') - ->update([ - 'isModifiedAlias' => 0, - 'updateTime' => time() - ]); - - return $updatedCount; - } -} - diff --git a/Server/application/command/UpdateWechatAccountScoreCommand.php b/Server/application/command/UpdateWechatAccountScoreCommand.php new file mode 100644 index 000000000..13ded8d43 --- /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/model/WechatAccountScore.php b/Server/application/common/model/WechatAccountScore.php new file mode 100644 index 000000000..43170f78d --- /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 index cda09cfd2..02a89a9b7 100644 --- a/Server/application/common/service/WechatAccountHealthScoreService.php +++ b/Server/application/common/service/WechatAccountHealthScoreService.php @@ -6,9 +6,15 @@ use think\Db; use think\Exception; /** - * 微信账号健康分评分服务 + * 微信账号健康分评分服务(优化版) * 基于《微信健康分规则v2.md》实现 * + * 优化点: + * 1. 基础分只计算一次 + * 2. 各个评分维度独立存储 + * 3. 使用独立的评分记录表 + * 4. 好友数量评分特殊处理(避免同步问题) + * * 健康分 = 基础分 + 动态分 * 基础分:60-100分(默认60分 + 基础信息10分 + 好友数量30分) * 动态分:扣分和加分规则 @@ -18,13 +24,14 @@ class WechatAccountHealthScoreService // 默认基础分 const DEFAULT_BASE_SCORE = 60; - // 基础信息权重和分数 - const BASE_INFO_WEIGHT = 0.2; + // 基础信息分数 const BASE_INFO_SCORE = 10; - // 好友数量权重和分数 - const FRIEND_COUNT_WEIGHT = 0.3; - const FRIEND_COUNT_MAX_SCORE = 30; + // 好友数量分数区间 + const FRIEND_COUNT_SCORE_0_50 = 3; + const FRIEND_COUNT_SCORE_51_500 = 6; + const FRIEND_COUNT_SCORE_501_3000 = 8; + const FRIEND_COUNT_SCORE_3001_PLUS = 12; // 动态分扣分规则 const PENALTY_FIRST_FREQUENT = -15; // 首次频繁扣15分 @@ -39,9 +46,10 @@ class WechatAccountHealthScoreService * * @param int $accountId 账号ID(s2_wechat_account表的id) * @param array $accountData 账号数据(可选,如果不传则从数据库查询) + * @param bool $forceRecalculateBase 是否强制重新计算基础分(默认false) * @return array 返回评分结果 */ - public function calculateAndUpdate($accountId, $accountData = null) + public function calculateAndUpdate($accountId, $accountData = null, $forceRecalculateBase = false) { try { // 获取账号数据 @@ -55,39 +63,68 @@ class WechatAccountHealthScoreService throw new Exception("账号不存在:{$accountId}"); } - // 计算基础分 - $baseScore = $this->calculateBaseScore($accountData); + $wechatId = $accountData['wechatId'] ?? ''; + if (empty($wechatId)) { + throw new Exception("账号wechatId为空:{$accountId}"); + } - // 计算动态分 - $dynamicScore = $this->calculateDynamicScore($accountData); + // 获取或创建评分记录 + $scoreRecord = $this->getOrCreateScoreRecord($accountId, $wechatId); + + // 计算基础分(只计算一次,除非强制重新计算) + if (!$scoreRecord['baseScoreCalculated'] || $forceRecalculateBase) { + $baseScoreData = $this->calculateBaseScore($accountData, $scoreRecord); + $this->updateBaseScore($accountId, $baseScoreData); + // 重新获取记录以获取最新数据 + $scoreRecord = $this->getScoreRecord($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); + + // 更新评分记录 $updateData = [ - 'healthScore' => $healthScore, - 'baseScore' => $baseScore, 'dynamicScore' => $dynamicScore, - 'scoreUpdateTime' => time() + '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() ]; - Db::table('s2_wechat_account') - ->where('id', $accountId) + Db::table('s2_wechat_account_score') + ->where('accountId', $accountId) ->update($updateData); return [ 'accountId' => $accountId, - 'wechatId' => $accountData['wechatId'] ?? '', + 'wechatId' => $wechatId, 'healthScore' => $healthScore, 'baseScore' => $baseScore, + 'baseInfoScore' => $scoreRecord['baseInfoScore'], + 'friendCountScore' => $scoreRecord['friendCountScore'], 'dynamicScore' => $dynamicScore, - 'baseInfoScore' => $this->getBaseInfoScore($accountData), - 'friendCountScore' => $this->getFriendCountScore($accountData['totalFriend'] ?? 0), - 'maxAddFriendPerDay' => $this->getMaxAddFriendPerDay($healthScore) + 'frequentPenalty' => $dynamicScoreData['frequentPenalty'], + 'noFrequentBonus' => $dynamicScoreData['noFrequentBonus'], + 'banPenalty' => $dynamicScoreData['banPenalty'], + 'maxAddFriendPerDay' => $maxAddFriendPerDay ]; } catch (Exception $e) { @@ -96,24 +133,119 @@ class WechatAccountHealthScoreService } /** - * 计算基础分 - * 基础分 = 默认60分 + 基础信息分(10分) + 好友数量分(30分) + * 获取或创建评分记录 + * + * @param int $accountId 账号ID + * @param string $wechatId 微信ID + * @return array 评分记录 + */ + private function getOrCreateScoreRecord($accountId, $wechatId) + { + $record = Db::table('s2_wechat_account_score') + ->where('accountId', $accountId) + ->find(); + + if (empty($record)) { + // 创建新记录 + $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('s2_wechat_account_score')->insert($data); + + return $data; + } + + return $record; + } + + /** + * 获取评分记录 + * + * @param int $accountId 账号ID + * @return array + */ + private function getScoreRecord($accountId) + { + return Db::table('s2_wechat_account_score') + ->where('accountId', $accountId) + ->find() ?: []; + } + + /** + * 计算基础分(只计算一次) + * 基础分 = 默认60分 + 基础信息分(10分) + 好友数量分(3-12分) * * @param array $accountData 账号数据 - * @return int 基础分 + * @param array $scoreRecord 现有评分记录 + * @return array 基础分数据 */ - private function calculateBaseScore($accountData) + private function calculateBaseScore($accountData, $scoreRecord = []) { $baseScore = self::DEFAULT_BASE_SCORE; // 基础信息分(已修改微信号得10分) - $baseScore += $this->getBaseInfoScore($accountData); + $baseInfoScore = $this->getBaseInfoScore($accountData); + $baseScore += $baseInfoScore; - // 好友数量分(最高30分) - $totalFriend = $accountData['totalFriend'] ?? 0; - $baseScore += $this->getFriendCountScore($totalFriend); + // 好友数量分(特殊处理:使用快照值,避免同步问题) + $friendCountScore = 0; + $friendCount = 0; + $friendCountSource = 'manual'; - return $baseScore; + // 如果已有评分记录且好友数量分已计算,使用历史值 + 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 基础分数据 + */ + private function updateBaseScore($accountId, $baseScoreData) + { + Db::table('s2_wechat_account_score') + ->where('accountId', $accountId) + ->update($baseScoreData); } /** @@ -125,23 +257,36 @@ class WechatAccountHealthScoreService */ private function getBaseInfoScore($accountData) { - // 检查是否已修改微信号 - // 如果isModifiedAlias字段为1,或者wechatId和alias不一致,则认为已修改 - $isModifiedAlias = isset($accountData['isModifiedAlias']) ? (int)$accountData['isModifiedAlias'] : 0; - $wechatId = trim($accountData['wechatId'] ?? ''); - $alias = trim($accountData['alias'] ?? ''); - - // 如果字段标记为已修改,或者wechatId和alias不一致,则得分 - if ($isModifiedAlias == 1 || (!empty($wechatId) && !empty($alias) && $wechatId !== $alias)) { + 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; + } + /** * 获取好友数量分 - * 根据好友数量区间得分(最高30分) + * 根据好友数量区间得分(最高12分) * * @param int $totalFriend 总好友数 * @return int 好友数量分 @@ -149,92 +294,292 @@ class WechatAccountHealthScoreService private function getFriendCountScore($totalFriend) { if ($totalFriend <= 50) { - return 3; // 0-50: 3分 + return self::FRIEND_COUNT_SCORE_0_50; } elseif ($totalFriend <= 500) { - return 6; // 51-500: 6分 + return self::FRIEND_COUNT_SCORE_51_500; } elseif ($totalFriend <= 3000) { - return 8; // 501-3000: 8分 + return self::FRIEND_COUNT_SCORE_501_3000; } else { - return 12; // 3001以上: 12分 + return self::FRIEND_COUNT_SCORE_3001_PLUS; } } + /** + * 手动更新好友数量分(用于处理同步问题) + * + * @param int $accountId 账号ID + * @param int $friendCount 好友数量 + * @param string $source 来源(manual=手动,sync=同步) + * @return bool + */ + public function updateFriendCountScore($accountId, $friendCount, $source = 'manual') + { + $scoreRecord = $this->getScoreRecord($accountId); + + // 如果基础分已计算,不允许修改好友数量分(除非是手动更新) + if (!empty($scoreRecord['baseScoreCalculated']) && $source === 'sync') { + // 同步数据不允许修改已计算的基础分 + return false; + } + + $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); + } + + Db::table('s2_wechat_account_score') + ->where('accountId', $accountId) + ->update($updateData); + + return true; + } + /** * 计算动态分 * 动态分 = 扣分 + 加分 * * @param array $accountData 账号数据 - * @return int 动态分 + * @param array $scoreRecord 现有评分记录 + * @return array 动态分数据 */ - private function calculateDynamicScore($accountData) + private function calculateDynamicScore($accountData, $scoreRecord) { - $dynamicScore = 0; + $result = [ + 'total' => 0, + 'frequentPenalty' => 0, + 'noFrequentBonus' => 0, + 'banPenalty' => 0, + 'lastFrequentTime' => null, + 'frequentCount' => 0, + 'lastNoFrequentTime' => null, + 'consecutiveNoFrequentDays' => 0, + 'isBanned' => 0 + ]; - // 处理扣分 - $dynamicScore += $this->calculatePenalty($accountData); + $accountId = $accountData['id'] ?? 0; + $wechatId = $accountData['wechatId'] ?? ''; - // 处理加分 - $dynamicScore += $this->calculateBonus($accountData); + if (empty($accountId) || empty($wechatId)) { + return $result; + } - return $dynamicScore; + // 继承现有数据 + 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); + $result['lastFrequentTime'] = $frequentData['lastFrequentTime'] ?? null; + $result['frequentCount'] = $frequentData['frequentCount'] ?? 0; + $result['frequentPenalty'] = $frequentData['frequentPenalty'] ?? 0; + + // 2. 检查封号记录(从s2_wechat_message表查询) + $banData = $this->checkBannedFromMessage($accountId, $wechatId); + if (!empty($banData)) { + $result['isBanned'] = $banData['isBanned']; + $result['banPenalty'] = $banData['banPenalty']; + } + + // 3. 计算不频繁加分(基于近30天的频繁记录,反向参考频繁规则) + $noFrequentData = $this->calculateNoFrequentBonus($accountId, $wechatId, $frequentData); + $result['noFrequentBonus'] = $noFrequentData['bonus'] ?? 0; + $result['consecutiveNoFrequentDays'] = $noFrequentData['consecutiveDays'] ?? 0; + $result['lastNoFrequentTime'] = $noFrequentData['lastNoFrequentTime'] ?? null; + + // 计算总分 + $result['total'] = $result['frequentPenalty'] + $result['noFrequentBonus'] + $result['banPenalty']; + + return $result; } /** - * 计算扣分 - * 首次频繁:-15分 - * 再次频繁:-25分 - * 封号:-60分 + * 从s2_friend_task表检查频繁记录 + * extra字段包含"操作过于频繁"即需要扣分 + * 只统计近30天的数据 * - * @param array $accountData 账号数据 - * @return int 扣分数 + * @param int $accountId 账号ID + * @param string $wechatId 微信ID + * @param array $scoreRecord 现有评分记录 + * @return array|null */ - private function calculatePenalty($accountData) + private function checkFrequentFromFriendTask($accountId, $wechatId, $scoreRecord) { + // 计算30天前的时间戳 + $thirtyDaysAgo = time() - (30 * 24 * 3600); + + // 查询包含"操作过于频繁"的记录(只统计近30天) + // extra字段可能是文本或JSON格式,使用LIKE查询 + $frequentTasks = Db::table('s2_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分 + } - // 检查是否有频繁记录 - $lastFrequentTime = $accountData['lastFrequentTime'] ?? null; - $frequentCount = $accountData['frequentCount'] ?? 0; + return [ + 'lastFrequentTime' => $latestFrequentTime, + 'frequentCount' => $frequentCount, + 'frequentPenalty' => $penalty + ]; + } + + /** + * 从s2_wechat_message表检查封号记录 + * content包含"你的账号被限制"且msgType为10000 + * 只统计近30天的数据 + * + * @param int $accountId 账号ID + * @param string $wechatId 微信ID + * @return array|null + */ + private function checkBannedFromMessage($accountId, $wechatId) + { + // 计算30天前的时间戳 + $thirtyDaysAgo = time() - (30 * 24 * 3600); - if (!empty($lastFrequentTime)) { - // 判断是首次频繁还是再次频繁 - if ($frequentCount == 1) { - $penalty += self::PENALTY_FIRST_FREQUENT; // 首次频繁-15分 - } elseif ($frequentCount >= 2) { - $penalty += self::PENALTY_SECOND_FREQUENT; // 再次频繁-25分 + // 查询封号消息(只统计近30天) + $banMessage = Db::table('s2_wechat_message') + ->where('wechatAccountId', $accountId) + ->where('msgType', 10000) + ->where('content', 'like', '%你的账号被限制%') + ->where('isDeleted', 0) + ->where('createTime', '>=', $thirtyDaysAgo) + ->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) + * @return array 包含bonus、consecutiveDays、lastNoFrequentTime + */ + private function calculateNoFrequentBonus($accountId, $wechatId, $frequentData) + { + $result = [ + 'bonus' => 0, + 'consecutiveDays' => 0, + 'lastNoFrequentTime' => null + ]; + + if (empty($accountId) || empty($wechatId)) { + return $result; + } + + // 计算30天前的时间戳 + $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)); } } - // 检查是否封号(这里需要根据实际业务逻辑判断,比如status字段或其他标识) - // 假设status=0表示封号 - $status = $accountData['status'] ?? 1; - if ($status == 0) { - $penalty += self::PENALTY_BANNED; // 封号-60分 + // 如果连续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 $penalty; - } - - /** - * 计算加分 - * 连续3天不触发频繁:每天+5分 - * - * @param array $accountData 账号数据 - * @return int 加分数 - */ - private function calculateBonus($accountData) - { - $bonus = 0; - - $lastNoFrequentTime = $accountData['lastNoFrequentTime'] ?? null; - $consecutiveNoFrequentDays = $accountData['consecutiveNoFrequentDays'] ?? 0; - - // 如果连续不频繁天数>=3,则每天+5分 - if ($consecutiveNoFrequentDays >= 3) { - $bonus = $consecutiveNoFrequentDays * self::BONUS_NO_FREQUENT_PER_DAY; - } - - return $bonus; + return $result; } /** @@ -252,11 +597,12 @@ class WechatAccountHealthScoreService /** * 批量计算并更新多个账号的健康分 * - * @param array $accountIds 账号ID数组 + * @param array $accountIds 账号ID数组(为空则处理所有账号) * @param int $batchSize 每批处理数量 + * @param bool $forceRecalculateBase 是否强制重新计算基础分 * @return array 处理结果统计 */ - public function batchCalculateAndUpdate($accountIds = [], $batchSize = 100) + public function batchCalculateAndUpdate($accountIds = [], $batchSize = 100, $forceRecalculateBase = false) { $stats = [ 'total' => 0, @@ -280,7 +626,7 @@ class WechatAccountHealthScoreService foreach ($batches as $batch) { foreach ($batch as $accountId) { try { - $this->calculateAndUpdate($accountId); + $this->calculateAndUpdate($accountId, null, $forceRecalculateBase); $stats['success']++; } catch (Exception $e) { $stats['failed']++; @@ -296,38 +642,22 @@ class WechatAccountHealthScoreService } /** - * 记录频繁事件 + * 记录频繁事件(已废弃,改为从s2_friend_task表自动检测) + * 保留此方法以兼容旧代码,实际频繁检测在calculateDynamicScore中完成 * * @param int $accountId 账号ID * @return bool */ public function recordFrequent($accountId) { - $accountData = Db::table('s2_wechat_account') - ->where('id', $accountId) - ->find(); - - if (empty($accountData)) { + // 频繁检测已改为从s2_friend_task表自动检测 + // 直接重新计算健康分即可 + try { + $this->calculateAndUpdate($accountId); + return true; + } catch (\Exception $e) { return false; } - - $frequentCount = ($accountData['frequentCount'] ?? 0) + 1; - - $updateData = [ - 'lastFrequentTime' => time(), - 'frequentCount' => $frequentCount, - 'consecutiveNoFrequentDays' => 0, // 重置连续不频繁天数 - 'lastNoFrequentTime' => null - ]; - - Db::table('s2_wechat_account') - ->where('id', $accountId) - ->update($updateData); - - // 重新计算健康分 - $this->calculateAndUpdate($accountId); - - return true; } /** @@ -338,21 +668,29 @@ class WechatAccountHealthScoreService */ public function recordNoFrequent($accountId) { - $accountData = Db::table('s2_wechat_account') - ->where('id', $accountId) - ->find(); + $scoreRecord = $this->getScoreRecord($accountId); - if (empty($accountData)) { - return false; + if (empty($scoreRecord)) { + // 如果记录不存在,先创建 + $accountData = Db::table('s2_wechat_account') + ->where('id', $accountId) + ->find(); + + if (empty($accountData)) { + return false; + } + + $this->getOrCreateScoreRecord($accountId, $accountData['wechatId']); + $scoreRecord = $this->getScoreRecord($accountId); } - $lastNoFrequentTime = $accountData['lastNoFrequentTime'] ?? null; - $consecutiveNoFrequentDays = $accountData['consecutiveNoFrequentDays'] ?? 0; + $lastNoFrequentTime = $scoreRecord['lastNoFrequentTime'] ?? null; + $consecutiveNoFrequentDays = $scoreRecord['consecutiveNoFrequentDays'] ?? 0; $currentTime = time(); // 如果上次不频繁时间是昨天或更早,则增加连续天数 if (empty($lastNoFrequentTime) || ($currentTime - $lastNoFrequentTime) >= 86400) { - // 如果间隔超过1天,重置为1天 + // 如果间隔超过2天,重置为1天 if (!empty($lastNoFrequentTime) && ($currentTime - $lastNoFrequentTime) > 86400 * 2) { $consecutiveNoFrequentDays = 1; } else { @@ -360,13 +698,21 @@ class WechatAccountHealthScoreService } } + // 计算加分(连续3天及以上才加分) + $bonus = 0; + if ($consecutiveNoFrequentDays >= 3) { + $bonus = $consecutiveNoFrequentDays * self::BONUS_NO_FREQUENT_PER_DAY; + } + $updateData = [ 'lastNoFrequentTime' => $currentTime, - 'consecutiveNoFrequentDays' => $consecutiveNoFrequentDays + 'consecutiveNoFrequentDays' => $consecutiveNoFrequentDays, + 'noFrequentBonus' => $bonus, + 'updateTime' => $currentTime ]; - Db::table('s2_wechat_account') - ->where('id', $accountId) + Db::table('s2_wechat_account_score') + ->where('accountId', $accountId) ->update($updateData); // 重新计算健康分 @@ -374,5 +720,38 @@ class WechatAccountHealthScoreService return true; } + + /** + * 获取账号健康分信息 + * + * @param int $accountId 账号ID + * @return array|null + */ + public function getHealthScore($accountId) + { + $scoreRecord = $this->getScoreRecord($accountId); + + if (empty($scoreRecord)) { + return null; + } + + return [ + '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 + ]; + } } - diff --git a/Server/crontab_tasks.md b/Server/crontab_tasks.md index f5849517e..f7d1dfc89 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/WeChatDeviceApi/Adapters/ChuKeBao/Adapter.php b/Server/extend/WeChatDeviceApi/Adapters/ChuKeBao/Adapter.php index adafc321a..247684bcc 100644 --- a/Server/extend/WeChatDeviceApi/Adapters/ChuKeBao/Adapter.php +++ b/Server/extend/WeChatDeviceApi/Adapters/ChuKeBao/Adapter.php @@ -18,6 +18,7 @@ use think\facade\Config; use think\facade\Log; use app\api\controller\FriendTaskController; use app\common\service\AuthService; +use app\common\service\WechatAccountHealthScoreService; use app\api\controller\WebSocketController; use Workerman\Lib\Timer; @@ -180,13 +181,11 @@ class Adapter implements WeChatServiceInterface ->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) From d296d019e54190d2c74623236dd48bc3918858a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Fri, 21 Nov 2025 14:55:56 +0800 Subject: [PATCH 08/49] Enhance MessageEnter component by adding a map selection feature. Introduced a new button for map visibility and integrated SelectMap component for improved user interaction. --- .../InputMessage/InputMessage.tsx | 0 .../InputMessage/index.module.scss | 147 --- .../StepSendMessage/index.module.scss | 265 ------ .../components/StepSendMessage/index.tsx | 6 - Moncter/src/pages/pc/ckbox/weChat/api.ts | 0 .../components/ProfileModules/index.tsx | 0 .../MessageEnter/components/selectMap.tsx | 900 ++++++++++++++++++ .../components/MessageEnter/index.tsx | 31 +- 8 files changed, 928 insertions(+), 421 deletions(-) delete mode 100644 Moncter/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/InputMessage/InputMessage.tsx delete mode 100644 Moncter/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/InputMessage/index.module.scss delete mode 100644 Moncter/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.module.scss delete mode 100644 Moncter/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/index.tsx delete mode 100644 Moncter/src/pages/pc/ckbox/weChat/api.ts delete mode 100644 Moncter/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/index.tsx create mode 100644 Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/selectMap.tsx diff --git a/Moncter/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/InputMessage/InputMessage.tsx b/Moncter/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/components/StepSendMessage/InputMessage/InputMessage.tsx deleted file mode 100644 index e69de29bb..000000000 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 b8085ce9b..000000000 --- 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 fee50c0ab..000000000 --- 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 082831d5b..000000000 --- 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 e69de29bb..000000000 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 e69de29bb..000000000 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 000000000..7393c08c8 --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/selectMap.tsx @@ -0,0 +1,900 @@ +import React, { useCallback, useEffect, useRef, useState } from "react"; +import { + AutoComplete, + Input as AntInput, + message, + Modal, + Spin, + Button, +} from "antd"; +import { ContractData, weChatGroup, ChatRecord } from "@/pages/pc/ckbox/data"; +import { useWebSocketStore } from "@/store/module/websocket/websocket"; + +declare const AMap: any; + +interface SelectMapProps { + visible: boolean; + onClose: () => void; + contract: ContractData | weChatGroup; + addMessage: (message: ChatRecord) => void; +} + +const SelectMap: React.FC = ({ + visible, + onClose, + contract, + addMessage, +}) => { + const [selectedLocation, setSelectedLocation] = useState(null); + const mapRef = useRef(null); + const markersRef = useRef([]); // 使用数组保存所有标记点 + const geocoderRef = useRef(null); // 保存 Geocoder 实例 + const placeSearchRef = useRef(null); // 保存 PlaceSearch 实例 + const geolocationRef = useRef(null); // 保存 Geolocation 实例 + const pendingClickRef = useRef<{ + lat: number; + lng: number; + lnglat: any; + } | null>(null); // 保存待处理的点击坐标 + const [options, setOptions] = useState([]); + const [mapLoading, setMapLoading] = useState(true); + + const sendCommand = useWebSocketStore(state => state.sendCommand); + + // 清除所有标记点 + const clearAllMarkers = useCallback(() => { + if (markersRef.current && markersRef.current.length > 0) { + console.log(`清除 ${markersRef.current.length} 个标记点`); + markersRef.current.forEach(marker => { + if (marker) { + marker.setMap(null); + marker = null; + } + }); + markersRef.current = []; + } + }, []); + + const addMarker = useCallback( + (lnglat: any) => { + console.log("addMarker 调用,坐标:", lnglat); + + // 先清除所有现有的标记点 + clearAllMarkers(); + + // 创建红色图标,保持默认倒水滴形状 + const redIcon = new AMap.Icon({ + size: new AMap.Size(25, 34), // 默认标记点尺寸 + image: "https://webapi.amap.com/theme/v1.3/markers/n/mark_r.png", // 红色标记点图片 + imageOffset: new AMap.Pixel(0, 0), + imageSize: new AMap.Size(25, 34), + }); + + // 创建新的标记点 + const newMarker = new AMap.Marker({ + position: lnglat, + map: mapRef.current, + icon: redIcon, + }); + + // 将新标记点添加到数组中 + markersRef.current.push(newMarker); + + mapRef.current.setCenter(lnglat); + mapRef.current.setZoom(16); // 确保缩放到合适级别 + console.log("新 marker 已添加并居中"); + }, + [clearAllMarkers], + ); + + // 通用的地址获取函数 + const getAddressForLocation = useCallback( + (lat: number, lng: number, lnglat: any) => { + console.log("=== getAddressForLocation 调用 ==="); + console.log("坐标:", { lat, lng }); + console.log( + "Geocoder ref 状态:", + geocoderRef.current ? "存在" : "不存在", + ); + + // 检查 Geocoder 是否已初始化 + if (!geocoderRef.current) { + console.warn("Geocoder 未初始化,无法获取地址"); + return false; + } + + // 使用更精确的坐标进行查询 + // 高德地图 getAddress 支持 LngLat 对象或 [lng, lat] 数组 + let queryLnglat: any; + if (lnglat && typeof lnglat.getLat === "function") { + // 如果传入的是 LngLat 对象,直接使用 + queryLnglat = lnglat; + } else { + // 否则创建新的 LngLat 对象或使用数组格式 + try { + queryLnglat = new AMap.LngLat(lng, lat); + } catch (error) { + console.error("创建 LngLat 对象失败,使用数组格式:", error); + queryLnglat = [lng, lat]; + } + } + console.log("调用 geocoder.getAddress,坐标:", queryLnglat); + + try { + geocoderRef.current.getAddress( + queryLnglat, + (status: string, result: any) => { + console.log("=== Geocoder 回调触发(通用函数) ==="); + console.log("Status:", status); + console.log( + "Result:", + result ? JSON.stringify(result, null, 2) : "null", + ); + + if ( + status === "complete" && + result && + result.info === "OK" && + result.regeocode + ) { + const regeocode = result.regeocode; + const formattedAddress = regeocode.formattedAddress || ""; + const addressComponent = regeocode.addressComponent || {}; + + // 构建详细地址信息 + let addressLabel = formattedAddress; + let poiName = "点击位置"; + + // 优先级1: 如果有POI信息,优先使用POI名称 + if (regeocode.pois && regeocode.pois.length > 0) { + const poi = regeocode.pois[0]; + poiName = poi.name || poiName; + const poiAddress = poi.address || ""; + addressLabel = poiAddress + ? `${poiName} ${poiAddress}` + : `${poiName} ${formattedAddress}`; + } + // 优先级2: 如果有建筑物信息 + else if (regeocode.buildings && regeocode.buildings.length > 0) { + const building = regeocode.buildings[0]; + poiName = building.name || addressComponent.building || poiName; + addressLabel = `${poiName} ${formattedAddress}`; + } + // 优先级3: 如果有AOI(兴趣区域)信息 + else if (regeocode.aois && regeocode.aois.length > 0) { + const aoi = regeocode.aois[0]; + poiName = aoi.name || poiName; + addressLabel = `${poiName} ${formattedAddress}`; + } + // 优先级4: 使用地址组件构建详细地址 + else if (addressComponent.building) { + poiName = addressComponent.building; + addressLabel = `${poiName} ${formattedAddress}`; + } + // 优先级5: 组合地址组件 + else { + 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.township) + parts.push(addressComponent.township); + if (addressComponent.street) + parts.push(addressComponent.street); + if (addressComponent.streetNumber) + parts.push(addressComponent.streetNumber); + + if (parts.length > 0) { + const fullAddress = parts.join(""); + poiName = + addressComponent.street || + addressComponent.district || + "点击位置"; + addressLabel = fullAddress || formattedAddress; + } else { + addressLabel = + formattedAddress || `纬度: ${lat}, 经度: ${lng}`; + } + } + + setSelectedLocation({ + lat: lat, + lng: lng, + label: addressLabel, + poiname: poiName, + }); + + message.success("地址信息获取成功"); + } else { + console.warn("=== Geocoder 返回异常,尝试 PlaceSearch ==="); + console.warn("Status:", status); + console.warn("Result:", result); + // Geocoder 失败,使用 PlaceSearch 作为备用 + if (placeSearchRef.current) { + try { + const searchLnglat = lnglat || [lng, lat]; + placeSearchRef.current.searchNearBy( + "", + searchLnglat, + 1000, + (searchStatus: string, searchResult: any) => { + if ( + searchStatus === "complete" && + searchResult && + searchResult.info === "OK" && + searchResult.poiList?.pois?.length > 0 + ) { + const poi = searchResult.poiList.pois[0]; + const poiLabel = poi.address + ? `${poi.name} ${poi.address}` + : poi.name; + setSelectedLocation({ + lat: lat, + lng: lng, + label: poiLabel, + poiname: poi.name, + }); + message.success("通过附近搜索获取到地址信息"); + } else { + console.warn("PlaceSearch 返回异常:", { + status: searchStatus, + result: searchResult, + }); + const coordLabel = `纬度: ${lat}, 经度: ${lng}`; + setSelectedLocation({ + lat: lat, + lng: lng, + label: coordLabel, + poiname: "点击位置", + }); + message.warning("无法获取详细地址信息,但坐标已记录"); + } + }, + ); + } catch (placeSearchError) { + console.error( + "PlaceSearch.searchNearBy 调用异常:", + placeSearchError, + ); + const coordLabel = `纬度: ${lat}, 经度: ${lng}`; + setSelectedLocation({ + lat: lat, + lng: lng, + label: coordLabel, + poiname: "点击位置", + }); + message.warning("无法获取详细地址信息,但坐标已记录"); + } + } else { + const coordLabel = `纬度: ${lat}, 经度: ${lng}`; + setSelectedLocation({ + lat: lat, + lng: lng, + label: coordLabel, + poiname: "点击位置", + }); + message.warning("无法获取详细地址信息,但坐标已记录"); + } + } + }, + ); + } catch (error) { + console.error("=== Geocoder.getAddress 调用异常 ===", error); + // 如果 geocoder 调用失败,尝试使用 PlaceSearch + if (placeSearchRef.current) { + console.log("尝试使用 PlaceSearch 作为备用方案"); + try { + placeSearchRef.current.searchNearBy( + "", + lnglat || [lng, lat], + 1000, + (status: string, result: any) => { + if ( + status === "complete" && + result && + result.info === "OK" && + result.poiList?.pois?.length > 0 + ) { + const poi = result.poiList.pois[0]; + const poiLabel = poi.address + ? `${poi.name} ${poi.address}` + : poi.name; + setSelectedLocation({ + lat: lat, + lng: lng, + label: poiLabel, + poiname: poi.name, + }); + message.success("通过附近搜索获取到地址信息"); + } else { + const coordLabel = `纬度: ${lat}, 经度: ${lng}`; + setSelectedLocation({ + lat: lat, + lng: lng, + label: coordLabel, + poiname: "点击位置", + }); + message.warning("无法获取详细地址信息,但坐标已记录"); + } + }, + ); + } catch (placeSearchError) { + console.error("PlaceSearch 调用也失败:", placeSearchError); + const coordLabel = `纬度: ${lat}, 经度: ${lng}`; + setSelectedLocation({ + lat: lat, + lng: lng, + label: coordLabel, + poiname: "点击位置", + }); + message.warning("无法获取详细地址信息,但坐标已记录"); + } + } else { + const coordLabel = `纬度: ${lat}, 经度: ${lng}`; + setSelectedLocation({ + lat: lat, + lng: lng, + label: coordLabel, + poiname: "点击位置", + }); + message.warning("无法获取详细地址信息,但坐标已记录"); + } + return false; + } + + return true; + }, + [], + ); + + const initMap = useCallback(() => { + console.log("initMap 执行中"); + setMapLoading(true); + + // 确保容器存在 + const container = document.getElementById("amap-container"); + if (!container) { + console.error("地图容器不存在"); + setMapLoading(false); + return; + } + + // 确保容器样式正确 + container.style.pointerEvents = "auto"; + container.style.cursor = "crosshair"; + container.style.position = "relative"; + container.style.zIndex = "1"; + + const map = new AMap.Map("amap-container", { + zoom: 16, + center: [118.113653, 24.470164], // 默认中心 + viewMode: "2D", // 明确指定视图模式 + }); + mapRef.current = map; + + // 添加超时机制,防止 loading 一直显示 + const loadingTimeout = setTimeout(() => { + console.warn("地图加载超时,强制关闭 loading"); + setMapLoading(false); + }, 10000); // 10秒超时 + + // 立即加载插件,不等待地图 complete 事件 + console.log("=== 立即开始加载 AMap 插件(地图创建后) ==="); + AMap.plugin( + [ + "AMap.AutoComplete", + "AMap.PlaceSearch", + "AMap.Geocoder", + "AMap.Geolocation", + ], + error => { + if (error) { + console.error("=== AMap 插件加载失败 ===", error); + message.error("地图插件加载失败,部分功能可能不可用"); + clearTimeout(loadingTimeout); + setMapLoading(false); + return; + } + console.log("=== AMap 插件加载成功 ==="); + + // 立即创建 PlaceSearch 和 Geocoder 实例 + const placeSearch = new AMap.PlaceSearch({ + city: "全国", + map: map, + }); + placeSearchRef.current = placeSearch; + console.log("PlaceSearch 实例已创建"); + + const geocoder = new AMap.Geocoder({ + city: "全国", + radius: 1000, // 搜索半径,单位米 + extensions: "all", // 返回详细信息,包括POI、建筑物等 + }); + geocoderRef.current = geocoder; + console.log("Geocoder 实例已创建并保存到 ref,现在可以立即使用"); + + // 创建 Geolocation 实例用于获取当前位置 + const geolocation = new AMap.Geolocation({ + enableHighAccuracy: true, // 是否使用高精度定位,默认:true + timeout: 10000, // 超过10秒后停止定位,默认:无穷大 + maximumAge: 0, // 定位结果缓存0毫秒,默认:0 + convert: true, // 自动偏移坐标,偏移后的坐标为高德坐标,默认:true + showButton: false, // 显示定位按钮,默认:true + buttonPosition: "RB", // 定位按钮停靠位置,默认:'LB',左下角 + showMarker: false, // 定位成功后在定位到的位置显示点标记,默认:true + showCircle: false, // 定位成功后用圆圈表示定位精度范围,默认:true + panToLocation: false, // 定位成功后将定位到的位置作为地图中心点,默认:true + zoomToAccuracy: false, // 定位成功后调整地图视野范围使定位位置及精度范围视野内可见,默认:false + }); + geolocationRef.current = geolocation; + + // 获取当前位置 + console.log("=== 开始获取当前位置 ==="); + geolocation.getCurrentPosition((status: string, result: any) => { + console.log("=== 定位回调触发 ==="); + console.log("定位状态:", status); + console.log("定位结果:", result); + + if (status === "complete") { + const { position, formattedAddress, addressComponent } = result; + const lat = position.lat; + const lng = position.lng; + const lnglat = new AMap.LngLat(lng, lat); + + console.log("定位成功,当前位置:", { lat, lng }); + console.log("定位地址:", formattedAddress); + + // 将地图中心设置为当前位置 + map.setCenter(lnglat); + map.setZoom(16); + + // 添加当前位置标记 + addMarker(lnglat); + + // 设置选中位置信息 + let addressLabel = formattedAddress || `纬度: ${lat}, 经度: ${lng}`; + let poiName = "当前位置"; + + // 尝试从地址组件中获取更详细的信息 + if (addressComponent) { + 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.streetNumber) + parts.push(addressComponent.streetNumber); + + if (parts.length > 0) { + addressLabel = parts.join(""); + } + + poiName = + addressComponent.street || + addressComponent.district || + "当前位置"; + } + + setSelectedLocation({ + lat: lat, + lng: lng, + label: addressLabel, + poiname: poiName, + }); + + message.success("已获取当前位置"); + } else { + console.warn("定位失败:", result); + message.warning( + "无法获取当前位置,请手动点击地图选择位置。原因: " + + (result.message || "定位服务不可用"), + ); + // 定位失败时,使用默认中心点 + console.log("使用默认中心点"); + } + }); + + // 如果有待处理的点击坐标,立即处理它 + if (pendingClickRef.current) { + console.log("=== 检测到待处理的点击坐标,立即处理 ==="); + const { lat, lng, lnglat } = pendingClickRef.current; + console.log("待处理坐标:", { lat, lng }); + setTimeout(() => { + const success = getAddressForLocation(lat, lng, lnglat); + if (success) { + console.log("✓ 待处理的坐标已成功获取地址"); + pendingClickRef.current = null; // 清除待处理坐标 + } else { + console.warn("待处理的坐标获取地址失败"); + } + }, 100); + } + }, + ); + + // 立即绑定点击事件,插件可能已加载或正在加载 + console.log("立即绑定点击事件(插件可能已初始化)"); + map.on("click", (e: any) => { + console.log("=== 地图点击事件触发 ==="); + console.log("点击事件对象:", e); + console.log("点击位置对象:", e.lnglat); + console.log( + "Geocoder ref 状态:", + geocoderRef.current ? "已初始化" : "未初始化", + ); + + if (!e || !e.lnglat) { + console.error("点击事件无效,缺少 lnglat"); + return; + } + + const lnglat = e.lnglat; + const lat = lnglat.getLat(); + const lng = lnglat.getLng(); + console.log(`点击坐标 - 纬度: ${lat}, 经度: ${lng}`); + + // 立即添加标记和居中 + addMarker(lnglat); + + // 设置基本 selectedLocation(至少有坐标) + setSelectedLocation({ + lat: lat, + lng: lng, + label: "正在获取地址信息...", + poiname: "点击位置", + }); + + // 如果 Geocoder 已初始化,立即使用它 + if (geocoderRef.current) { + console.log("Geocoder 已初始化,立即获取地址"); + getAddressForLocation(lat, lng, lnglat); + } else { + console.log("Geocoder 未初始化,保存坐标待插件加载完成后处理"); + // 保存待处理的坐标 + pendingClickRef.current = { lat, lng, lnglat }; + } + }); + + // 等待地图完全加载后关闭 loading + map.on("complete", () => { + console.log("地图加载完成"); + // 清除超时定时器 + clearTimeout(loadingTimeout); + // 关闭 loading + setMapLoading(false); + console.log("地图加载完成,loading 已关闭"); + + // 确保地图容器可点击 + const mapContainer = map.getContainer(); + if (mapContainer) { + mapContainer.style.pointerEvents = "auto"; + mapContainer.style.cursor = "crosshair"; + console.log("地图容器指针事件已启用"); + } + }); + + // 如果地图加载失败,也设置加载完成 + map.on("error", (error: any) => { + console.error("地图加载错误:", error); + clearTimeout(loadingTimeout); + setMapLoading(false); + message.error("地图加载失败"); + }); + }, [addMarker, getAddressForLocation]); + + const handleSearch = value => { + if (value) { + AMap.plugin("AMap.AutoComplete", () => { + const auto = new AMap.AutoComplete({ city: "全国" }); + auto.search(value, (status, result) => { + if (status === "complete") { + setOptions( + result.tips.map(tip => ({ + value: tip.name, + data: tip, + })), + ); + } + }); + }); + } else { + setOptions([]); + } + }; + + const onSelect = (value, option) => { + const { district, address, name, location } = option.data; + const lnglat = location; + setSelectedLocation({ + lat: lnglat.lat, + lng: lnglat.lng, + label: `${name} ${address || district}`, + poiname: name, + }); + addMarker(lnglat); + mapRef.current.setCenter(lnglat); + }; + + const handleModalChange = useCallback( + (visible: boolean) => { + if (visible) { + console.log("模态打开:开始加载地图脚本"); + setMapLoading(true); + setSelectedLocation(null); + const script = document.createElement("script"); + script.src = + "https://webapi.amap.com/maps?v=1.4.15&key=79370028f5763e46742125ed2e900c76&plugin=AMap.PlaceSearch,AMap.AutoComplete,AMap.Geocoder,AMap.Geolocation"; + script.async = true; + script.onload = () => { + console.log("脚本加载成功:开始初始化地图"); + setTimeout(() => initMap(), 100); // 添加延迟确保 DOM 就绪 + }; + script.onerror = () => { + console.error("脚本加载失败"); + message.error("地图加载失败,请检查网络或API密钥"); + setMapLoading(false); + }; + document.body.appendChild(script); + } else { + console.log("模态关闭:清理地图和脚本"); + // 清除所有标记点 + clearAllMarkers(); + // 重置 ref + geocoderRef.current = null; + placeSearchRef.current = null; + geolocationRef.current = null; + // Cleanup on close + const scripts = document.querySelectorAll( + 'script[src*="webapi.amap.com"]', + ); + scripts.forEach(s => { + if (document.body.contains(s)) { + document.body.removeChild(s); + } + }); + if (mapRef.current) { + mapRef.current.destroy(); + mapRef.current = null; + } + setMapLoading(false); + setOptions([]); // 重置搜索选项 + } + }, + [initMap, clearAllMarkers], + ); + + // 手动获取当前位置 + const handleGetCurrentLocation = useCallback(() => { + if (!geolocationRef.current) { + message.warning("定位服务未初始化,请稍候再试"); + return; + } + + console.log("=== 手动触发获取当前位置 ==="); + message.loading({ content: "正在获取当前位置...", key: "location" }); + + geolocationRef.current.getCurrentPosition((status: string, result: any) => { + message.destroy("location"); + console.log("=== 手动定位回调触发 ==="); + console.log("定位状态:", status); + console.log("定位结果:", result); + + if (status === "complete" && result && result.position) { + const { position, formattedAddress, addressComponent } = result; + const lat = position.lat; + const lng = position.lng; + const lnglat = new AMap.LngLat(lng, lat); + + console.log("定位成功,当前位置:", { lat, lng }); + + // 将地图中心设置为当前位置 + if (mapRef.current) { + mapRef.current.setCenter(lnglat); + mapRef.current.setZoom(16); + } + + // 添加当前位置标记 + addMarker(lnglat); + + // 设置选中位置信息 + let addressLabel = formattedAddress || `纬度: ${lat}, 经度: ${lng}`; + let poiName = "当前位置"; + + // 尝试从地址组件中获取更详细的信息 + if (addressComponent) { + 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.streetNumber) + parts.push(addressComponent.streetNumber); + + if (parts.length > 0) { + addressLabel = parts.join(""); + } + + poiName = + addressComponent.street || addressComponent.district || "当前位置"; + } + + setSelectedLocation({ + lat: lat, + lng: lng, + label: addressLabel, + poiname: poiName, + }); + + message.success("已获取当前位置"); + } else { + console.warn("定位失败:", result); + message.error( + "获取当前位置失败: " + (result?.message || "定位服务不可用"), + ); + } + }); + }, [addMarker]); + + const handleSendLocation = () => { + if (!selectedLocation || !selectedLocation.lat || !selectedLocation.lng) { + message.warning("请选择有效位置"); + return; + } + + const { lat, lng, label, poiname } = selectedLocation; + const content = ``; + + const messageId = +Date.now(); + const params = { + wechatAccountId: contract.wechatAccountId, + wechatChatroomId: contract?.chatroomId ? contract.id : 0, + wechatFriendId: contract?.chatroomId ? 0 : contract.id, + msgSubType: 0, + msgType: 48, + content: content, + seq: messageId, + }; + + // 构造本地消息 + const localMessage: ChatRecord = { + id: messageId, + wechatAccountId: contract.wechatAccountId, + wechatFriendId: contract?.chatroomId ? 0 : contract.id, + wechatChatroomId: contract?.chatroomId ? contract.id : 0, + tenantId: 0, + accountId: 0, + synergyAccountId: 0, + content: content, + 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); + + sendCommand("CmdSendMessage", params); + + onClose(); + setSelectedLocation(null); + }; + + useEffect(() => { + if (visible) { + handleModalChange(true); + // 确保容器在模态框打开后可以接收事件 + setTimeout(() => { + const container = document.getElementById("amap-container"); + if (container) { + container.style.pointerEvents = "auto"; + container.style.cursor = "crosshair"; + console.log( + "容器样式已设置,pointerEvents:", + container.style.pointerEvents, + ); + } + }, 200); + } else { + handleModalChange(false); + } + }, [visible, handleModalChange]); + + return ( + +
    + + + + +
    + + {/* 显示点击位置的信息 */} + {selectedLocation && ( +
    +
    + 位置信息: +
    +
    + 地址: {selectedLocation.label || "加载中..."} +
    +
    + 坐标: {selectedLocation.lat},{" "} + {selectedLocation.lng} +
    +
    + )} + +
    + {mapLoading && ( +
    + +
    + )} +
    { + // 添加原生点击事件监听作为备用 + console.log("容器原生点击事件触发", e); + }} + >
    +
    +
    + ); +}; + +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 864ccb6e9..9135624fd 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} /> + @@ -877,7 +652,7 @@ const SelectMap: React.FC = ({
    )}
    { 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) { From 7dcbcfacc1f0b5899719e72a15449053f86d1f27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Fri, 21 Nov 2025 16:33:02 +0800 Subject: [PATCH 10/49] =?UTF-8?q?=E9=87=8D=E7=BD=AE=E4=B8=80=E4=B8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MessageEnter/components/selectMap.tsx | 773 +++++++++++------- 1 file changed, 499 insertions(+), 274 deletions(-) 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 index 7492be75b..7393c08c8 100644 --- 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 @@ -10,7 +10,7 @@ import { import { ContractData, weChatGroup, ChatRecord } from "@/pages/pc/ckbox/data"; import { useWebSocketStore } from "@/store/module/websocket/websocket"; -declare const qq: any; +declare const AMap: any; interface SelectMapProps { visible: boolean; @@ -34,7 +34,7 @@ const SelectMap: React.FC = ({ const pendingClickRef = useRef<{ lat: number; lng: number; - latlng: any; + lnglat: any; } | null>(null); // 保存待处理的点击坐标 const [options, setOptions] = useState([]); const [mapLoading, setMapLoading] = useState(true); @@ -56,103 +56,282 @@ const SelectMap: React.FC = ({ }, []); const addMarker = useCallback( - (latlng: any) => { - console.log("addMarker 调用,坐标:", latlng); - - const qq = (window as any).qq; - if (!qq || !qq.maps || !mapRef.current) { - console.error("地图API未加载或地图未初始化"); - return; - } + (lnglat: any) => { + console.log("addMarker 调用,坐标:", lnglat); // 先清除所有现有的标记点 clearAllMarkers(); - // 创建新的标记点(腾讯地图默认红色标记) - const newMarker = new qq.maps.Marker({ - position: latlng, + // 创建红色图标,保持默认倒水滴形状 + const redIcon = new AMap.Icon({ + size: new AMap.Size(25, 34), // 默认标记点尺寸 + image: "https://webapi.amap.com/theme/v1.3/markers/n/mark_r.png", // 红色标记点图片 + imageOffset: new AMap.Pixel(0, 0), + imageSize: new AMap.Size(25, 34), + }); + + // 创建新的标记点 + const newMarker = new AMap.Marker({ + position: lnglat, map: mapRef.current, + icon: redIcon, }); // 将新标记点添加到数组中 markersRef.current.push(newMarker); - mapRef.current.setCenter(latlng); + mapRef.current.setCenter(lnglat); mapRef.current.setZoom(16); // 确保缩放到合适级别 console.log("新 marker 已添加并居中"); }, [clearAllMarkers], ); - // 通用的地址获取函数(使用腾讯地图逆地理编码API) - const getAddressForLocation = useCallback((lat: number, lng: number) => { - console.log("=== getAddressForLocation 调用 ==="); - console.log("坐标:", { lat, lng }); + // 通用的地址获取函数 + const getAddressForLocation = useCallback( + (lat: number, lng: number, lnglat: any) => { + console.log("=== getAddressForLocation 调用 ==="); + console.log("坐标:", { lat, lng }); + console.log( + "Geocoder ref 状态:", + geocoderRef.current ? "存在" : "不存在", + ); - // 使用腾讯地图逆地理编码API - const key = "5ZSBZ-23ICU-XDKVU-4ZQ7Z-O35AJ-XUF6S"; - const url = `https://apis.map.qq.com/ws/geocoder/v1/?location=${lat},${lng}&key=${key}&get_poi=1`; + // 检查 Geocoder 是否已初始化 + if (!geocoderRef.current) { + console.warn("Geocoder 未初始化,无法获取地址"); + return false; + } - fetch(url) - .then(response => response.json()) - .then(result => { - console.log("=== 腾讯地图逆地理编码回调 ==="); - console.log("Result:", result); + // 使用更精确的坐标进行查询 + // 高德地图 getAddress 支持 LngLat 对象或 [lng, lat] 数组 + let queryLnglat: any; + if (lnglat && typeof lnglat.getLat === "function") { + // 如果传入的是 LngLat 对象,直接使用 + queryLnglat = lnglat; + } else { + // 否则创建新的 LngLat 对象或使用数组格式 + try { + queryLnglat = new AMap.LngLat(lng, lat); + } catch (error) { + console.error("创建 LngLat 对象失败,使用数组格式:", error); + queryLnglat = [lng, lat]; + } + } + console.log("调用 geocoder.getAddress,坐标:", queryLnglat); - if (result.status === 0 && result.result) { - const data = result.result; - const formattedAddress = - data.formatted_addresses?.recommend || data.address || ""; - const addressComponent = data.address_component || {}; - const pois = data.pois || []; + try { + geocoderRef.current.getAddress( + queryLnglat, + (status: string, result: any) => { + console.log("=== Geocoder 回调触发(通用函数) ==="); + console.log("Status:", status); + console.log( + "Result:", + result ? JSON.stringify(result, null, 2) : "null", + ); - // 构建详细地址信息 - let addressLabel = formattedAddress; - let poiName = "点击位置"; + if ( + status === "complete" && + result && + result.info === "OK" && + result.regeocode + ) { + const regeocode = result.regeocode; + const formattedAddress = regeocode.formattedAddress || ""; + const addressComponent = regeocode.addressComponent || {}; - // 优先级1: 如果有POI信息,优先使用POI名称 - if (pois.length > 0) { - const poi = pois[0]; - poiName = poi.title || poiName; - const poiAddress = poi.address || ""; - addressLabel = poiAddress - ? `${poiName} ${poiAddress}` - : `${poiName} ${formattedAddress}`; - } - // 优先级2: 组合地址组件 - else { - 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); + // 构建详细地址信息 + let addressLabel = formattedAddress; + let poiName = "点击位置"; - if (parts.length > 0) { - const fullAddress = parts.join(""); - poiName = - addressComponent.street || - addressComponent.district || - "点击位置"; - addressLabel = fullAddress || formattedAddress; + // 优先级1: 如果有POI信息,优先使用POI名称 + if (regeocode.pois && regeocode.pois.length > 0) { + const poi = regeocode.pois[0]; + poiName = poi.name || poiName; + const poiAddress = poi.address || ""; + addressLabel = poiAddress + ? `${poiName} ${poiAddress}` + : `${poiName} ${formattedAddress}`; + } + // 优先级2: 如果有建筑物信息 + else if (regeocode.buildings && regeocode.buildings.length > 0) { + const building = regeocode.buildings[0]; + poiName = building.name || addressComponent.building || poiName; + addressLabel = `${poiName} ${formattedAddress}`; + } + // 优先级3: 如果有AOI(兴趣区域)信息 + else if (regeocode.aois && regeocode.aois.length > 0) { + const aoi = regeocode.aois[0]; + poiName = aoi.name || poiName; + addressLabel = `${poiName} ${formattedAddress}`; + } + // 优先级4: 使用地址组件构建详细地址 + else if (addressComponent.building) { + poiName = addressComponent.building; + addressLabel = `${poiName} ${formattedAddress}`; + } + // 优先级5: 组合地址组件 + else { + 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.township) + parts.push(addressComponent.township); + if (addressComponent.street) + parts.push(addressComponent.street); + if (addressComponent.streetNumber) + parts.push(addressComponent.streetNumber); + + if (parts.length > 0) { + const fullAddress = parts.join(""); + poiName = + addressComponent.street || + addressComponent.district || + "点击位置"; + addressLabel = fullAddress || formattedAddress; + } else { + addressLabel = + formattedAddress || `纬度: ${lat}, 经度: ${lng}`; + } + } + + setSelectedLocation({ + lat: lat, + lng: lng, + label: addressLabel, + poiname: poiName, + }); + + message.success("地址信息获取成功"); } else { - addressLabel = formattedAddress || `纬度: ${lat}, 经度: ${lng}`; + console.warn("=== Geocoder 返回异常,尝试 PlaceSearch ==="); + console.warn("Status:", status); + console.warn("Result:", result); + // Geocoder 失败,使用 PlaceSearch 作为备用 + if (placeSearchRef.current) { + try { + const searchLnglat = lnglat || [lng, lat]; + placeSearchRef.current.searchNearBy( + "", + searchLnglat, + 1000, + (searchStatus: string, searchResult: any) => { + if ( + searchStatus === "complete" && + searchResult && + searchResult.info === "OK" && + searchResult.poiList?.pois?.length > 0 + ) { + const poi = searchResult.poiList.pois[0]; + const poiLabel = poi.address + ? `${poi.name} ${poi.address}` + : poi.name; + setSelectedLocation({ + lat: lat, + lng: lng, + label: poiLabel, + poiname: poi.name, + }); + message.success("通过附近搜索获取到地址信息"); + } else { + console.warn("PlaceSearch 返回异常:", { + status: searchStatus, + result: searchResult, + }); + const coordLabel = `纬度: ${lat}, 经度: ${lng}`; + setSelectedLocation({ + lat: lat, + lng: lng, + label: coordLabel, + poiname: "点击位置", + }); + message.warning("无法获取详细地址信息,但坐标已记录"); + } + }, + ); + } catch (placeSearchError) { + console.error( + "PlaceSearch.searchNearBy 调用异常:", + placeSearchError, + ); + const coordLabel = `纬度: ${lat}, 经度: ${lng}`; + setSelectedLocation({ + lat: lat, + lng: lng, + label: coordLabel, + poiname: "点击位置", + }); + message.warning("无法获取详细地址信息,但坐标已记录"); + } + } else { + const coordLabel = `纬度: ${lat}, 经度: ${lng}`; + setSelectedLocation({ + lat: lat, + lng: lng, + label: coordLabel, + poiname: "点击位置", + }); + message.warning("无法获取详细地址信息,但坐标已记录"); + } } + }, + ); + } catch (error) { + console.error("=== Geocoder.getAddress 调用异常 ===", error); + // 如果 geocoder 调用失败,尝试使用 PlaceSearch + if (placeSearchRef.current) { + console.log("尝试使用 PlaceSearch 作为备用方案"); + try { + placeSearchRef.current.searchNearBy( + "", + lnglat || [lng, lat], + 1000, + (status: string, result: any) => { + if ( + status === "complete" && + result && + result.info === "OK" && + result.poiList?.pois?.length > 0 + ) { + const poi = result.poiList.pois[0]; + const poiLabel = poi.address + ? `${poi.name} ${poi.address}` + : poi.name; + setSelectedLocation({ + lat: lat, + lng: lng, + label: poiLabel, + poiname: poi.name, + }); + message.success("通过附近搜索获取到地址信息"); + } else { + const coordLabel = `纬度: ${lat}, 经度: ${lng}`; + setSelectedLocation({ + lat: lat, + lng: lng, + label: coordLabel, + poiname: "点击位置", + }); + message.warning("无法获取详细地址信息,但坐标已记录"); + } + }, + ); + } catch (placeSearchError) { + console.error("PlaceSearch 调用也失败:", placeSearchError); + const coordLabel = `纬度: ${lat}, 经度: ${lng}`; + setSelectedLocation({ + lat: lat, + lng: lng, + label: coordLabel, + poiname: "点击位置", + }); + message.warning("无法获取详细地址信息,但坐标已记录"); } - - setSelectedLocation({ - lat: lat, - lng: lng, - label: addressLabel, - poiname: poiName, - }); - - message.success("地址信息获取成功"); } else { - console.warn("逆地理编码返回异常:", result); const coordLabel = `纬度: ${lat}, 经度: ${lng}`; setSelectedLocation({ lat: lat, @@ -162,48 +341,20 @@ const SelectMap: React.FC = ({ }); message.warning("无法获取详细地址信息,但坐标已记录"); } - }) - .catch(error => { - console.error("=== 逆地理编码API调用异常 ===", error); - const coordLabel = `纬度: ${lat}, 经度: ${lng}`; - setSelectedLocation({ - lat: lat, - lng: lng, - label: coordLabel, - poiname: "点击位置", - }); - message.warning("无法获取详细地址信息,但坐标已记录"); - }); - }, []); + return false; + } + + return true; + }, + [], + ); const initMap = useCallback(() => { console.log("initMap 执行中"); setMapLoading(true); - // 检查 qq 对象是否已加载 - if (!(window as any).qq || !(window as any).qq.maps) { - console.warn("腾讯地图API尚未加载,等待中..."); - // 等待最多5秒,每100ms检查一次 - let attempts = 0; - const maxAttempts = 50; - const checkInterval = setInterval(() => { - attempts++; - if ((window as any).qq && (window as any).qq.maps) { - clearInterval(checkInterval); - console.log("腾讯地图API已加载,继续初始化"); - initMap(); - } else if (attempts >= maxAttempts) { - clearInterval(checkInterval); - console.error("腾讯地图API加载超时"); - message.error("地图加载失败,请刷新页面重试"); - setMapLoading(false); - } - }, 100); - return; - } - // 确保容器存在 - const container = document.getElementById("tencent-map-container"); + const container = document.getElementById("amap-container"); if (!container) { console.error("地图容器不存在"); setMapLoading(false); @@ -216,13 +367,10 @@ const SelectMap: React.FC = ({ container.style.position = "relative"; container.style.zIndex = "1"; - const qq = (window as any).qq; - - // 腾讯地图初始化(注意:坐标顺序是 lat, lng) - const center = new qq.maps.LatLng(24.470164, 118.113653); // 默认中心 - const map = new qq.maps.Map("tencent-map-container", { + const map = new AMap.Map("amap-container", { zoom: 16, - center: center, + center: [118.113653, 24.470164], // 默认中心 + viewMode: "2D", // 明确指定视图模式 }); mapRef.current = map; @@ -232,91 +380,165 @@ const SelectMap: React.FC = ({ setMapLoading(false); }, 10000); // 10秒超时 - // 标记服务已初始化 - geocoderRef.current = true; // 使用HTTP API,不需要实例 - placeSearchRef.current = true; // 使用HTTP API,不需要实例 - - // 获取当前位置(使用浏览器原生定位) - console.log("=== 开始获取当前位置 ==="); - if (navigator.geolocation) { - navigator.geolocation.getCurrentPosition( - position => { - const lat = position.coords.latitude; - const lng = position.coords.longitude; - const qq = (window as any).qq; - if (!qq || !qq.maps) { - console.error("腾讯地图API未加载"); - return; - } - const latlng = new qq.maps.LatLng(lat, lng); - - console.log("定位成功,当前位置:", { lat, lng }); - - // 将地图中心设置为当前位置 - map.setCenter(latlng); - map.setZoom(16); - - // 添加当前位置标记 - addMarker(latlng); - - // 获取地址信息 - getAddressForLocation(lat, lng); - }, - error => { - console.warn("定位失败:", error); - message.warning( - "无法获取当前位置,请手动点击地图选择位置。原因: " + - (error.message || "定位服务不可用"), - ); - // 定位失败时,使用默认中心点 - console.log("使用默认中心点"); + // 立即加载插件,不等待地图 complete 事件 + console.log("=== 立即开始加载 AMap 插件(地图创建后) ==="); + AMap.plugin( + [ + "AMap.AutoComplete", + "AMap.PlaceSearch", + "AMap.Geocoder", + "AMap.Geolocation", + ], + error => { + if (error) { + console.error("=== AMap 插件加载失败 ===", error); + message.error("地图插件加载失败,部分功能可能不可用"); clearTimeout(loadingTimeout); setMapLoading(false); - }, - { - enableHighAccuracy: true, - timeout: 10000, - maximumAge: 0, - }, - ); - } else { - console.warn("浏览器不支持定位"); - message.warning("浏览器不支持定位功能"); - clearTimeout(loadingTimeout); - setMapLoading(false); - } + return; + } + console.log("=== AMap 插件加载成功 ==="); - // 如果有待处理的点击坐标,立即处理它 - if (pendingClickRef.current) { - console.log("=== 检测到待处理的点击坐标,立即处理 ==="); - const { lat, lng } = pendingClickRef.current; - console.log("待处理坐标:", { lat, lng }); - setTimeout(() => { - getAddressForLocation(lat, lng); - console.log("✓ 待处理的坐标已成功获取地址"); - pendingClickRef.current = null; // 清除待处理坐标 - }, 100); - } + // 立即创建 PlaceSearch 和 Geocoder 实例 + const placeSearch = new AMap.PlaceSearch({ + city: "全国", + map: map, + }); + placeSearchRef.current = placeSearch; + console.log("PlaceSearch 实例已创建"); - // 绑定点击事件 - console.log("立即绑定点击事件"); - qq.maps.event.addListener(map, "click", (e: any) => { + const geocoder = new AMap.Geocoder({ + city: "全国", + radius: 1000, // 搜索半径,单位米 + extensions: "all", // 返回详细信息,包括POI、建筑物等 + }); + geocoderRef.current = geocoder; + console.log("Geocoder 实例已创建并保存到 ref,现在可以立即使用"); + + // 创建 Geolocation 实例用于获取当前位置 + const geolocation = new AMap.Geolocation({ + enableHighAccuracy: true, // 是否使用高精度定位,默认:true + timeout: 10000, // 超过10秒后停止定位,默认:无穷大 + maximumAge: 0, // 定位结果缓存0毫秒,默认:0 + convert: true, // 自动偏移坐标,偏移后的坐标为高德坐标,默认:true + showButton: false, // 显示定位按钮,默认:true + buttonPosition: "RB", // 定位按钮停靠位置,默认:'LB',左下角 + showMarker: false, // 定位成功后在定位到的位置显示点标记,默认:true + showCircle: false, // 定位成功后用圆圈表示定位精度范围,默认:true + panToLocation: false, // 定位成功后将定位到的位置作为地图中心点,默认:true + zoomToAccuracy: false, // 定位成功后调整地图视野范围使定位位置及精度范围视野内可见,默认:false + }); + geolocationRef.current = geolocation; + + // 获取当前位置 + console.log("=== 开始获取当前位置 ==="); + geolocation.getCurrentPosition((status: string, result: any) => { + console.log("=== 定位回调触发 ==="); + console.log("定位状态:", status); + console.log("定位结果:", result); + + if (status === "complete") { + const { position, formattedAddress, addressComponent } = result; + const lat = position.lat; + const lng = position.lng; + const lnglat = new AMap.LngLat(lng, lat); + + console.log("定位成功,当前位置:", { lat, lng }); + console.log("定位地址:", formattedAddress); + + // 将地图中心设置为当前位置 + map.setCenter(lnglat); + map.setZoom(16); + + // 添加当前位置标记 + addMarker(lnglat); + + // 设置选中位置信息 + let addressLabel = formattedAddress || `纬度: ${lat}, 经度: ${lng}`; + let poiName = "当前位置"; + + // 尝试从地址组件中获取更详细的信息 + if (addressComponent) { + 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.streetNumber) + parts.push(addressComponent.streetNumber); + + if (parts.length > 0) { + addressLabel = parts.join(""); + } + + poiName = + addressComponent.street || + addressComponent.district || + "当前位置"; + } + + setSelectedLocation({ + lat: lat, + lng: lng, + label: addressLabel, + poiname: poiName, + }); + + message.success("已获取当前位置"); + } else { + console.warn("定位失败:", result); + message.warning( + "无法获取当前位置,请手动点击地图选择位置。原因: " + + (result.message || "定位服务不可用"), + ); + // 定位失败时,使用默认中心点 + console.log("使用默认中心点"); + } + }); + + // 如果有待处理的点击坐标,立即处理它 + if (pendingClickRef.current) { + console.log("=== 检测到待处理的点击坐标,立即处理 ==="); + const { lat, lng, lnglat } = pendingClickRef.current; + console.log("待处理坐标:", { lat, lng }); + setTimeout(() => { + const success = getAddressForLocation(lat, lng, lnglat); + if (success) { + console.log("✓ 待处理的坐标已成功获取地址"); + pendingClickRef.current = null; // 清除待处理坐标 + } else { + console.warn("待处理的坐标获取地址失败"); + } + }, 100); + } + }, + ); + + // 立即绑定点击事件,插件可能已加载或正在加载 + console.log("立即绑定点击事件(插件可能已初始化)"); + map.on("click", (e: any) => { console.log("=== 地图点击事件触发 ==="); console.log("点击事件对象:", e); - console.log("点击位置对象:", e.latLng); + console.log("点击位置对象:", e.lnglat); + console.log( + "Geocoder ref 状态:", + geocoderRef.current ? "已初始化" : "未初始化", + ); - if (!e || !e.latLng) { - console.error("点击事件无效,缺少 latLng"); + if (!e || !e.lnglat) { + console.error("点击事件无效,缺少 lnglat"); return; } - const latlng = e.latLng; - const lat = latlng.getLat(); - const lng = latlng.getLng(); + const lnglat = e.lnglat; + const lat = lnglat.getLat(); + const lng = lnglat.getLng(); console.log(`点击坐标 - 纬度: ${lat}, 经度: ${lng}`); // 立即添加标记和居中 - addMarker(latlng); + addMarker(lnglat); // 设置基本 selectedLocation(至少有坐标) setSelectedLocation({ @@ -326,12 +548,19 @@ const SelectMap: React.FC = ({ poiname: "点击位置", }); - // 立即获取地址 - getAddressForLocation(lat, lng); + // 如果 Geocoder 已初始化,立即使用它 + if (geocoderRef.current) { + console.log("Geocoder 已初始化,立即获取地址"); + getAddressForLocation(lat, lng, lnglat); + } else { + console.log("Geocoder 未初始化,保存坐标待插件加载完成后处理"); + // 保存待处理的坐标 + pendingClickRef.current = { lat, lng, lnglat }; + } }); // 等待地图完全加载后关闭 loading - qq.maps.event.addListener(map, "tilesloaded", () => { + map.on("complete", () => { console.log("地图加载完成"); // 清除超时定时器 clearTimeout(loadingTimeout); @@ -347,63 +576,47 @@ const SelectMap: React.FC = ({ console.log("地图容器指针事件已启用"); } }); + + // 如果地图加载失败,也设置加载完成 + map.on("error", (error: any) => { + console.error("地图加载错误:", error); + clearTimeout(loadingTimeout); + setMapLoading(false); + message.error("地图加载失败"); + }); }, [addMarker, getAddressForLocation]); const handleSearch = value => { if (value) { - // 使用腾讯地图搜索建议API - const key = "5ZSBZ-23ICU-XDKVU-4ZQ7Z-O35AJ-XUF6S"; - const url = `https://apis.map.qq.com/ws/place/v1/suggestion?keyword=${encodeURIComponent(value)}&key=${key}®ion=全国`; - - fetch(url) - .then(response => response.json()) - .then(result => { - if (result.status === 0 && result.data) { + AMap.plugin("AMap.AutoComplete", () => { + const auto = new AMap.AutoComplete({ city: "全国" }); + auto.search(value, (status, result) => { + if (status === "complete") { setOptions( - result.data.map((item: any) => ({ - value: item.title, - data: { - name: item.title, - address: item.address, - district: item.adname, - location: { - lat: item.location.lat, - lng: item.location.lng, - }, - }, + result.tips.map(tip => ({ + value: tip.name, + data: tip, })), ); - } else { - setOptions([]); } - }) - .catch(error => { - console.error("搜索建议API调用失败:", error); - setOptions([]); }); + }); } else { setOptions([]); } }; const onSelect = (value, option) => { - const qq = (window as any).qq; - if (!qq || !qq.maps) { - message.error("地图API未加载,请稍后再试"); - return; - } const { district, address, name, location } = option.data; - const latlng = new qq.maps.LatLng(location.lat, location.lng); + const lnglat = location; setSelectedLocation({ - lat: location.lat, - lng: location.lng, - label: `${name} ${address || district || ""}`, + lat: lnglat.lat, + lng: lnglat.lng, + label: `${name} ${address || district}`, poiname: name, }); - addMarker(latlng); - if (mapRef.current) { - mapRef.current.setCenter(latlng); - } + addMarker(lnglat); + mapRef.current.setCenter(lnglat); }; const handleModalChange = useCallback( @@ -412,17 +625,9 @@ const SelectMap: React.FC = ({ console.log("模态打开:开始加载地图脚本"); setMapLoading(true); setSelectedLocation(null); - - // 检查是否已加载腾讯地图脚本 - if ((window as any).qq && (window as any).qq.maps) { - console.log("腾讯地图脚本已加载,直接初始化"); - setTimeout(() => initMap(), 100); - return; - } - const script = document.createElement("script"); script.src = - "https://map.qq.com/api/gljs?v=1.exp&key=5ZSBZ-23ICU-XDKVU-4ZQ7Z-O35AJ-XUF6S"; + "https://webapi.amap.com/maps?v=1.4.15&key=79370028f5763e46742125ed2e900c76&plugin=AMap.PlaceSearch,AMap.AutoComplete,AMap.Geocoder,AMap.Geolocation"; script.async = true; script.onload = () => { console.log("脚本加载成功:开始初始化地图"); @@ -443,14 +648,16 @@ const SelectMap: React.FC = ({ placeSearchRef.current = null; geolocationRef.current = null; // Cleanup on close - const scripts = document.querySelectorAll('script[src*="map.qq.com"]'); + const scripts = document.querySelectorAll( + 'script[src*="webapi.amap.com"]', + ); scripts.forEach(s => { if (document.body.contains(s)) { document.body.removeChild(s); } }); if (mapRef.current) { - // 腾讯地图没有destroy方法,设置为null即可 + mapRef.current.destroy(); mapRef.current = null; } setMapLoading(false); @@ -462,57 +669,75 @@ const SelectMap: React.FC = ({ // 手动获取当前位置 const handleGetCurrentLocation = useCallback(() => { - if (!navigator.geolocation) { - message.warning("浏览器不支持定位服务"); - return; - } - - const qq = (window as any).qq; - if (!qq || !qq.maps) { - message.error("地图API未加载,请稍后再试"); + if (!geolocationRef.current) { + message.warning("定位服务未初始化,请稍候再试"); return; } console.log("=== 手动触发获取当前位置 ==="); message.loading({ content: "正在获取当前位置...", key: "location" }); - navigator.geolocation.getCurrentPosition( - position => { - message.destroy("location"); - console.log("=== 手动定位回调触发 ==="); - const lat = position.coords.latitude; - const lng = position.coords.longitude; - const latlng = new qq.maps.LatLng(lat, lng); + geolocationRef.current.getCurrentPosition((status: string, result: any) => { + message.destroy("location"); + console.log("=== 手动定位回调触发 ==="); + console.log("定位状态:", status); + console.log("定位结果:", result); + + if (status === "complete" && result && result.position) { + const { position, formattedAddress, addressComponent } = result; + const lat = position.lat; + const lng = position.lng; + const lnglat = new AMap.LngLat(lng, lat); console.log("定位成功,当前位置:", { lat, lng }); // 将地图中心设置为当前位置 if (mapRef.current) { - mapRef.current.setCenter(latlng); + mapRef.current.setCenter(lnglat); mapRef.current.setZoom(16); } // 添加当前位置标记 - addMarker(latlng); + addMarker(lnglat); + + // 设置选中位置信息 + let addressLabel = formattedAddress || `纬度: ${lat}, 经度: ${lng}`; + let poiName = "当前位置"; + + // 尝试从地址组件中获取更详细的信息 + if (addressComponent) { + 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.streetNumber) + parts.push(addressComponent.streetNumber); + + if (parts.length > 0) { + addressLabel = parts.join(""); + } + + poiName = + addressComponent.street || addressComponent.district || "当前位置"; + } + + setSelectedLocation({ + lat: lat, + lng: lng, + label: addressLabel, + poiname: poiName, + }); - // 获取地址信息 - getAddressForLocation(lat, lng); message.success("已获取当前位置"); - }, - error => { - message.destroy("location"); - console.warn("定位失败:", error); + } else { + console.warn("定位失败:", result); message.error( - "获取当前位置失败: " + (error.message || "定位服务不可用"), + "获取当前位置失败: " + (result?.message || "定位服务不可用"), ); - }, - { - enableHighAccuracy: true, - timeout: 10000, - maximumAge: 0, - }, - ); - }, [addMarker, getAddressForLocation]); + } + }); + }, [addMarker]); const handleSendLocation = () => { if (!selectedLocation || !selectedLocation.lat || !selectedLocation.lng) { @@ -571,7 +796,7 @@ const SelectMap: React.FC = ({ handleModalChange(true); // 确保容器在模态框打开后可以接收事件 setTimeout(() => { - const container = document.getElementById("tencent-map-container"); + const container = document.getElementById("amap-container"); if (container) { container.style.pointerEvents = "auto"; container.style.cursor = "crosshair"; @@ -607,7 +832,7 @@ const SelectMap: React.FC = ({ @@ -652,7 +877,7 @@ const SelectMap: React.FC = ({
    )}
    Date: Fri, 21 Nov 2025 17:22:33 +0800 Subject: [PATCH 11/49] =?UTF-8?q?=E5=AE=A2=E6=9C=8D=E8=AF=84=E5=88=86?= =?UTF-8?q?=E4=BD=93=E7=B3=BB=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CalculateWechatAccountScoreCommand.php | 413 ++++++++++++---- .../WechatAccountHealthScoreService.php | 455 +++++++++++++++--- 2 files changed, 711 insertions(+), 157 deletions(-) diff --git a/Server/application/command/CalculateWechatAccountScoreCommand.php b/Server/application/command/CalculateWechatAccountScoreCommand.php index dbc7ec5be..aeb737848 100644 --- a/Server/application/command/CalculateWechatAccountScoreCommand.php +++ b/Server/application/command/CalculateWechatAccountScoreCommand.php @@ -6,6 +6,7 @@ use think\console\Command; use think\console\Input; use think\console\Output; use think\Db; +use think\facade\Log; use app\common\service\WechatAccountHealthScoreService; /** @@ -17,36 +18,122 @@ use app\common\service\WechatAccountHealthScoreService; */ class CalculateWechatAccountScoreCommand extends Command { + /** + * 数据库表名 + */ + const TABLE_WECHAT_ACCOUNT = 's2_wechat_account'; + const TABLE_WECHAT_ACCOUNT_SCORE = 's2_wechat_account_score'; protected function configure() { $this->setName('wechat:calculate-score') - ->setDescription('统一计算微信账号健康分(包含初始化、更新评分记录、批量计算)'); + ->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(); - $service = new WechatAccountHealthScoreService(); + + 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: 初始化未计算基础分的账号 - $output->writeln("\n[步骤1] 初始化未计算基础分的账号..."); - $initStats = $this->initUncalculatedAccounts($service, $output); - $output->writeln("初始化完成:成功 {$initStats['success']} 条,失败 {$initStats['failed']} 条"); + 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不一致情况) - $output->writeln("\n[步骤2] 更新评分记录(根据wechatId和alias不一致情况)..."); - $updateStats = $this->updateScoreRecords($service, $output); - $output->writeln("更新完成:处理了 {$updateStats['total']} 条记录"); + 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: 批量更新健康分(只更新动态分,不重新计算基础分) - $output->writeln("\n[步骤3] 批量更新健康分(只更新动态分)..."); - $batchStats = $this->batchUpdateHealthScore($service, $output); - $output->writeln("批量更新完成:成功 {$batchStats['success']} 条,失败 {$batchStats['failed']} 条"); + 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(); @@ -60,40 +147,89 @@ class CalculateWechatAccountScoreCommand extends Command $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 (\Exception $e) { - $output->writeln("\n错误: " . $e->getMessage()); + } 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 + * @param WechatAccountHealthScoreService $service 健康分服务实例 + * @param Output $output 输出对象 + * @return array 处理结果统计 + * @throws \Exception 如果查询或处理过程中出现错误 */ - private function initUncalculatedAccounts($service, $output) + private function initUncalculatedAccounts($service, $output, $accountId = null, $batchSize = 50) { $stats = [ 'total' => 0, @@ -102,41 +238,67 @@ class CalculateWechatAccountScoreCommand extends Command 'errors' => [] ]; - // 获取所有未计算基础分的账号 - $accounts = Db::table('s2_wechat_account') - ->alias('a') - ->leftJoin(['s2_wechat_account_score' => 's'], 's.accountId = a.id') - ->where('a.isDeleted', 0) - ->where(function($query) { - $query->whereNull('s.id') - ->whereOr('s.baseScoreCalculated', 0); - }) - ->field('a.id, a.wechatId') - ->select(); + 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']} 个需要初始化的账号"); - $batchSize = 100; + // 优化批处理:使用传入的批处理大小 $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'] % 100 == 0) { + 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() @@ -144,9 +306,12 @@ class CalculateWechatAccountScoreCommand extends Command } } - if (($batchIndex + 1) % 10 == 0) { - $output->writeln(" 已处理 " . ($batchIndex + 1) * $batchSize . " 条"); - } + $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; @@ -155,52 +320,90 @@ class CalculateWechatAccountScoreCommand extends Command /** * 更新评分记录(根据wechatId和alias不一致情况) * - * @param WechatAccountHealthScoreService $service - * @param Output $output - * @return array + * @param WechatAccountHealthScoreService $service 健康分服务实例 + * @param Output $output 输出对象 + * @return array 处理结果统计 + * @throws \Exception 如果查询或处理过程中出现错误 */ - private function updateScoreRecords($service, $output) + private function updateScoreRecords($service, $output, $accountId = null, $batchSize = 50) { $stats = ['total' => 0]; - // 查找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(); + 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; - foreach ($allAccounts as $account) { - $isModifiedAlias = in_array($account['id'], array_column($inconsistentAccounts, 'id')); - $this->updateScoreRecord($account['id'], $isModifiedAlias, $service); - $updatedCount++; + // 优化批处理:使用传入的批处理大小 + $batches = array_chunk($allAccounts, $batchSize); + $batchCount = count($batches); + + Log::info("将分 {$batchCount} 批更新评分记录,每批 {$batchSize} 个账号"); + + foreach ($batches as $batchIndex => $batch) { + $batchStartTime = microtime(true); + $batchUpdatedCount = 0; - if ($updatedCount % 100 == 0) { - $output->write("."); + 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) { @@ -213,27 +416,44 @@ class CalculateWechatAccountScoreCommand extends Command /** * 批量更新健康分(只更新动态分) * - * @param WechatAccountHealthScoreService $service - * @param Output $output - * @return array + * @param WechatAccountHealthScoreService $service 健康分服务实例 + * @param Output $output 输出对象 + * @return array 处理结果统计 + * @throws \Exception 如果查询或处理过程中出现错误 */ - private function batchUpdateHealthScore($service, $output) + private function batchUpdateHealthScore($service, $output, $accountId = null, $batchSize = 50, $forceRecalculate = false) { - // 获取所有已计算基础分的账号 - $accountIds = Db::table('s2_wechat_account_score') - ->where('baseScoreCalculated', 1) - ->column('accountId'); + 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} 个需要更新动态分的账号"); - $stats = $service->batchCalculateAndUpdate($accountIds, 100, false); + // 使用传入的批处理大小和强制重新计算标志 + Log::info("使用批量大小 {$batchSize} 进行批量更新健康分,强制重新计算基础分: " . ($forceRecalculate ? 'true' : 'false')); + $stats = $service->batchCalculateAndUpdate($accountIds, $batchSize, $forceRecalculate); return $stats; } @@ -245,31 +465,47 @@ class CalculateWechatAccountScoreCommand extends Command * @param bool $isModifiedAlias 是否已修改微信号 * @param WechatAccountHealthScoreService $service 评分服务 */ + /** + * 更新评分记录 + * + * @param int $accountId 账号ID + * @param bool $isModifiedAlias 是否已修改微信号 + * @param WechatAccountHealthScoreService $service 评分服务 + * @return bool 是否成功更新 + */ private function updateScoreRecord($accountId, $isModifiedAlias, $service) { - // 获取账号数据 - $accountData = Db::table('s2_wechat_account') - ->where('id', $accountId) - ->find(); + Log::debug("开始更新账号 {$accountId} 的评分记录,isModifiedAlias: " . ($isModifiedAlias ? 'true' : 'false')); - if (empty($accountData)) { - return; - } - - // 确保评分记录存在 - $scoreRecord = Db::table('s2_wechat_account_score') - ->where('accountId', $accountId) - ->find(); + 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('s2_wechat_account_score') + $scoreRecord = Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE) ->where('accountId', $accountId) ->find(); } if (empty($scoreRecord)) { + Log::warning("账号 {$accountId} 的评分记录创建失败,跳过更新"); return; } @@ -297,15 +533,26 @@ class CalculateWechatAccountScoreCommand extends Command $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; } - Db::table('s2_wechat_account_score') + $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/common/service/WechatAccountHealthScoreService.php b/Server/application/common/service/WechatAccountHealthScoreService.php index 02a89a9b7..d6246a268 100644 --- a/Server/application/common/service/WechatAccountHealthScoreService.php +++ b/Server/application/common/service/WechatAccountHealthScoreService.php @@ -4,6 +4,8 @@ namespace app\common\service; use think\Db; use think\Exception; +use think\facade\Log; +use think\facade\Cache; /** * 微信账号健康分评分服务(优化版) @@ -14,33 +16,63 @@ use think\Exception; * 2. 各个评分维度独立存储 * 3. 使用独立的评分记录表 * 4. 好友数量评分特殊处理(避免同步问题) + * 5. 动态分仅统计近30天数据 + * 6. 优化数据库查询,减少重复计算 + * 7. 添加完善的日志记录,便于问题排查 * * 健康分 = 基础分 + 动态分 * 基础分:60-100分(默认60分 + 基础信息10分 + 好友数量30分) * 动态分:扣分和加分规则 + * + * @author Your Name + * @version 2.0.0 */ class WechatAccountHealthScoreService { - // 默认基础分 + /** + * 缓存相关配置 + */ + const CACHE_PREFIX = 'wechat_health_score:'; // 缓存前缀 + const CACHE_TTL = 3600; // 缓存有效期(秒) + + /** + * 默认基础分 + */ const DEFAULT_BASE_SCORE = 60; - // 基础信息分数 + /** + * 基础信息分数 + */ const BASE_INFO_SCORE = 10; - // 好友数量分数区间 - const FRIEND_COUNT_SCORE_0_50 = 3; - const FRIEND_COUNT_SCORE_51_500 = 6; - const FRIEND_COUNT_SCORE_501_3000 = 8; - const FRIEND_COUNT_SCORE_3001_PLUS = 12; + /** + * 好友数量分数区间 + */ + const FRIEND_COUNT_SCORE_0_50 = 3; // 0-50个好友 + const FRIEND_COUNT_SCORE_51_500 = 6; // 51-500个好友 + const FRIEND_COUNT_SCORE_501_3000 = 8; // 501-3000个好友 + const FRIEND_COUNT_SCORE_3001_PLUS = 12; // 3001+个好友 - // 动态分扣分规则 - const PENALTY_FIRST_FREQUENT = -15; // 首次频繁扣15分 + /** + * 动态分扣分规则 + */ + const PENALTY_FIRST_FREQUENT = -15; // 首次频繁扣15分 const PENALTY_SECOND_FREQUENT = -25; // 再次频繁扣25分 const PENALTY_BANNED = -60; // 封号扣60分 - // 动态分加分规则 + /** + * 动态分加分规则 + */ const BONUS_NO_FREQUENT_PER_DAY = 5; // 连续3天不触发频繁,每天+5分 + /** + * 数据库表名 + */ + const TABLE_WECHAT_ACCOUNT = 's2_wechat_account'; + const TABLE_WECHAT_ACCOUNT_SCORE = 's2_wechat_account_score'; + const TABLE_FRIEND_TASK = 's2_friend_task'; + const TABLE_WECHAT_MESSAGE = 's2_wechat_message'; + /** * 计算并更新账号健康分 * @@ -48,38 +80,65 @@ class WechatAccountHealthScoreService * @param array $accountData 账号数据(可选,如果不传则从数据库查询) * @param bool $forceRecalculateBase 是否强制重新计算基础分(默认false) * @return array 返回评分结果 + * @throws Exception 如果计算过程中出现错误 */ public function calculateAndUpdate($accountId, $accountData = null, $forceRecalculateBase = false) { + // 参数验证 + if (empty($accountId) || !is_numeric($accountId)) { + $errorMsg = "无效的账号ID: " . (is_scalar($accountId) ? $accountId : gettype($accountId)); + Log::error($errorMsg); + throw new Exception($errorMsg); + } + try { + Log::info("开始计算账号健康分,accountId: {$accountId}, forceRecalculateBase: " . ($forceRecalculateBase ? 'true' : 'false')); + // 获取账号数据 if (empty($accountData)) { - $accountData = Db::table('s2_wechat_account') + $accountData = Db::table(self::TABLE_WECHAT_ACCOUNT) ->where('id', $accountId) ->find(); + + Log::debug("查询账号数据: " . ($accountData ? "成功" : "失败")); } if (empty($accountData)) { - throw new Exception("账号不存在:{$accountId}"); + $errorMsg = "账号不存在:{$accountId}"; + Log::error($errorMsg); + throw new Exception($errorMsg); } $wechatId = $accountData['wechatId'] ?? ''; if (empty($wechatId)) { - throw new Exception("账号wechatId为空:{$accountId}"); + $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); // 计算总分 @@ -93,6 +152,9 @@ class WechatAccountHealthScoreService // 计算每日最大加人次数 $maxAddFriendPerDay = $this->getMaxAddFriendPerDay($healthScore); + Log::info("健康分计算结果,accountId: {$accountId}, baseScore: {$baseScore}, dynamicScore: {$dynamicScore}, " . + "healthScore: {$healthScore}, maxAddFriendPerDay: {$maxAddFriendPerDay}"); + // 更新评分记录 $updateData = [ 'dynamicScore' => $dynamicScore, @@ -109,11 +171,16 @@ class WechatAccountHealthScoreService 'updateTime' => time() ]; - Db::table('s2_wechat_account_score') + $updateResult = Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE) ->where('accountId', $accountId) ->update($updateData); + + // 更新成功后,清除缓存 + if ($updateResult !== false) { + $this->clearScoreCache($accountId); + } - return [ + $result = [ 'accountId' => $accountId, 'wechatId' => $wechatId, 'healthScore' => $healthScore, @@ -127,8 +194,19 @@ class WechatAccountHealthScoreService 'maxAddFriendPerDay' => $maxAddFriendPerDay ]; - } catch (Exception $e) { - throw new Exception("计算健康分失败:" . $e->getMessage()); + 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); } } @@ -141,11 +219,13 @@ class WechatAccountHealthScoreService */ private function getOrCreateScoreRecord($accountId, $wechatId) { - $record = Db::table('s2_wechat_account_score') - ->where('accountId', $accountId) - ->find(); + // 尝试获取现有记录 + $record = $this->getScoreRecord($accountId); + // 如果记录不存在,创建新记录 if (empty($record)) { + Log::info("为账号 {$accountId} 创建新的评分记录"); + // 创建新记录 $data = [ 'accountId' => $accountId, @@ -163,7 +243,7 @@ class WechatAccountHealthScoreService 'updateTime' => time() ]; - Db::table('s2_wechat_account_score')->insert($data); + Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE)->insert($data); return $data; } @@ -175,13 +255,33 @@ class WechatAccountHealthScoreService * 获取评分记录 * * @param int $accountId 账号ID - * @return array + * @param bool $useCache 是否使用缓存(默认true) + * @return array 评分记录,如果不存在则返回空数组 */ - private function getScoreRecord($accountId) + private function getScoreRecord($accountId, $useCache = true) { - return Db::table('s2_wechat_account_score') + // 生成缓存键 + $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() ?: []; + ->find(); + + // 如果记录存在且使用缓存,则缓存记录 + if ($record && $useCache) { + Cache::set($cacheKey, $record, self::CACHE_TTL); + Log::debug("缓存评分记录,accountId: {$accountId}"); + } + + return $record ?: []; } /** @@ -240,12 +340,41 @@ class WechatAccountHealthScoreService * * @param int $accountId 账号ID * @param array $baseScoreData 基础分数据 + * @return bool 更新是否成功 */ private function updateBaseScore($accountId, $baseScoreData) { - Db::table('s2_wechat_account_score') - ->where('accountId', $accountId) - ->update($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; } /** @@ -310,16 +439,44 @@ class WechatAccountHealthScoreService * @param int $accountId 账号ID * @param int $friendCount 好友数量 * @param string $source 来源(manual=手动,sync=同步) - * @return bool + * @return bool 更新是否成功 + * @throws Exception 如果参数无效或更新过程中出现错误 */ public function updateFriendCountScore($accountId, $friendCount, $source = 'manual') { - $scoreRecord = $this->getScoreRecord($accountId); + // 参数验证 + if (empty($accountId) || !is_numeric($accountId)) { + $errorMsg = "无效的账号ID: " . (is_scalar($accountId) ? $accountId : gettype($accountId)); + Log::error($errorMsg); + throw new Exception($errorMsg); + } - // 如果基础分已计算,不允许修改好友数量分(除非是手动更新) - if (!empty($scoreRecord['baseScoreCalculated']) && $source === 'sync') { - // 同步数据不允许修改已计算的基础分 - return false; + 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); @@ -348,16 +505,36 @@ class WechatAccountHealthScoreService $updateData['maxAddFriendPerDay'] = $this->getMaxAddFriendPerDay($healthScore); } - Db::table('s2_wechat_account_score') - ->where('accountId', $accountId) - ->update($updateData); - - return true; + 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 现有评分记录 @@ -365,6 +542,11 @@ class WechatAccountHealthScoreService */ private function calculateDynamicScore($accountData, $scoreRecord) { + $accountId = $accountData['id'] ?? 0; + $wechatId = $accountData['wechatId'] ?? ''; + + Log::debug("开始计算动态分,accountId: {$accountId}, wechatId: {$wechatId}"); + $result = [ 'total' => 0, 'frequentPenalty' => 0, @@ -377,13 +559,33 @@ class WechatAccountHealthScoreService 'isBanned' => 0 ]; - $accountId = $accountData['id'] ?? 0; - $wechatId = $accountData['wechatId'] ?? ''; - 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; @@ -396,20 +598,20 @@ class WechatAccountHealthScoreService } // 1. 检查频繁记录(从s2_friend_task表查询,只统计近30天) - $frequentData = $this->checkFrequentFromFriendTask($accountId, $wechatId, $scoreRecord); + $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); + $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); + $noFrequentData = $this->calculateNoFrequentBonus($accountId, $wechatId, $frequentData, $thirtyDaysAgo); $result['noFrequentBonus'] = $noFrequentData['bonus'] ?? 0; $result['consecutiveNoFrequentDays'] = $noFrequentData['consecutiveDays'] ?? 0; $result['lastNoFrequentTime'] = $noFrequentData['lastNoFrequentTime'] ?? null; @@ -417,6 +619,10 @@ class WechatAccountHealthScoreService // 计算总分 $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; } @@ -428,16 +634,20 @@ class WechatAccountHealthScoreService * @param int $accountId 账号ID * @param string $wechatId 微信ID * @param array $scoreRecord 现有评分记录 + * @param int $thirtyDaysAgo 30天前的时间戳(可选,如果已计算则传入以避免重复计算) * @return array|null */ - private function checkFrequentFromFriendTask($accountId, $wechatId, $scoreRecord) + private function checkFrequentFromFriendTask($accountId, $wechatId, $scoreRecord, $thirtyDaysAgo = null) { - // 计算30天前的时间戳 - $thirtyDaysAgo = time() - (30 * 24 * 3600); + // 如果没有传入30天前的时间戳,则计算 + if ($thirtyDaysAgo === null) { + $thirtyDaysAgo = time() - (30 * 24 * 3600); + } // 查询包含"操作过于频繁"的记录(只统计近30天) // extra字段可能是文本或JSON格式,使用LIKE查询 - $frequentTasks = Db::table('s2_friend_task') + // 优化查询:只查询必要的字段,减少数据传输量 + $frequentTasks = Db::table(self::TABLE_FRIEND_TASK) ->where('wechatAccountId', $accountId) ->where('createTime', '>=', $thirtyDaysAgo) ->where(function($query) use ($wechatId) { @@ -448,7 +658,7 @@ class WechatAccountHealthScoreService ->where(function($query) { // 检查extra字段是否包含"操作过于频繁"(可能是文本或JSON) $query->where('extra', 'like', '%操作过于频繁%') - ->whereOr('extra', 'like', '%"操作过于频繁"%'); + ->whereOr('extra', 'like', '%"当前账号存在安全风险"%'); }) ->order('createTime', 'desc') ->field('id, createTime, extra') @@ -491,20 +701,25 @@ class WechatAccountHealthScoreService * * @param int $accountId 账号ID * @param string $wechatId 微信ID + * @param int $thirtyDaysAgo 30天前的时间戳(可选,如果已计算则传入以避免重复计算) * @return array|null */ - private function checkBannedFromMessage($accountId, $wechatId) + private function checkBannedFromMessage($accountId, $wechatId, $thirtyDaysAgo = null) { - // 计算30天前的时间戳 - $thirtyDaysAgo = time() - (30 * 24 * 3600); + // 如果没有传入30天前的时间戳,则计算 + if ($thirtyDaysAgo === null) { + $thirtyDaysAgo = time() - (30 * 24 * 3600); + } // 查询封号消息(只统计近30天) - $banMessage = Db::table('s2_wechat_message') + // 优化查询:只查询必要的字段,减少数据传输量 + $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(); @@ -530,9 +745,10 @@ class WechatAccountHealthScoreService * @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) + private function calculateNoFrequentBonus($accountId, $wechatId, $frequentData, $thirtyDaysAgo = null) { $result = [ 'bonus' => 0, @@ -544,8 +760,10 @@ class WechatAccountHealthScoreService return $result; } - // 计算30天前的时间戳 - $thirtyDaysAgo = time() - (30 * 24 * 3600); + // 如果没有传入30天前的时间戳,则计算 + if ($thirtyDaysAgo === null) { + $thirtyDaysAgo = time() - (30 * 24 * 3600); + } $currentTime = time(); // 获取最后一次频繁时间(30天内最后一次频繁的时间) @@ -601,29 +819,54 @@ class WechatAccountHealthScoreService * @param int $batchSize 每批处理数量 * @param bool $forceRecalculateBase 是否强制重新计算基础分 * @return array 处理结果统计 + * @throws Exception 如果参数无效或批量处理过程中出现严重错误 */ public function batchCalculateAndUpdate($accountIds = [], $batchSize = 100, $forceRecalculateBase = false) { - $stats = [ - 'total' => 0, - 'success' => 0, - 'failed' => 0, - 'errors' => [] - ]; - - // 如果没有指定账号ID,则处理所有账号 - if (empty($accountIds)) { - $accountIds = Db::table('s2_wechat_account') - ->where('isDeleted', 0) - ->column('id'); + // 参数验证 + 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 $batch) { + foreach ($batches as $batchIndex => $batch) { + $batchStartTime = microtime(true); + Log::info("开始处理第 " . ($batchIndex + 1) . " 批,共 " . count($batch) . " 个账号"); + foreach ($batch as $accountId) { try { $this->calculateAndUpdate($accountId, null, $forceRecalculateBase); @@ -634,11 +877,30 @@ class WechatAccountHealthScoreService '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); + } } /** @@ -672,7 +934,7 @@ class WechatAccountHealthScoreService if (empty($scoreRecord)) { // 如果记录不存在,先创建 - $accountData = Db::table('s2_wechat_account') + $accountData = Db::table(self::TABLE_WECHAT_ACCOUNT) ->where('id', $accountId) ->find(); @@ -725,17 +987,36 @@ class WechatAccountHealthScoreService * 获取账号健康分信息 * * @param int $accountId 账号ID + * @param bool $useCache 是否使用缓存(默认true) + * @param bool $forceRecalculate 是否强制重新计算(默认false) * @return array|null */ - public function getHealthScore($accountId) + public function getHealthScore($accountId, $useCache = true, $forceRecalculate = false) { - $scoreRecord = $this->getScoreRecord($accountId); + // 如果强制重新计算,则不使用缓存 + 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; } - return [ + $healthScoreInfo = [ 'accountId' => $scoreRecord['accountId'], 'wechatId' => $scoreRecord['wechatId'], 'healthScore' => $scoreRecord['healthScore'] ?? 0, @@ -753,5 +1034,31 @@ class WechatAccountHealthScoreService '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; } } From 6c9551be05dbed1024fa76223d570de459c8fab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Fri, 21 Nov 2025 18:30:12 +0800 Subject: [PATCH 12/49] =?UTF-8?q?=E8=85=BE=E8=AE=AF=E5=9C=B0=E5=9B=BE?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E9=94=81=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Touchkebao/index.html | 4 + .../components/selectMap.module.scss | 111 ++ .../MessageEnter/components/selectMap.tsx | 1235 +++++------------ 3 files changed, 487 insertions(+), 863 deletions(-) create mode 100644 Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/selectMap.module.scss diff --git a/Touchkebao/index.html b/Touchkebao/index.html index 92656ef95..d0dcd4e9b 100644 --- a/Touchkebao/index.html +++ b/Touchkebao/index.html @@ -11,6 +11,10 @@ +
    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 000000000..f0a77bb3d --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/selectMap.module.scss @@ -0,0 +1,111 @@ +.selectMapContainer { + display: flex; + flex-direction: column; + height: 600px; + gap: 16px; +} + +.searchArea { + flex-shrink: 0; + position: relative; +} + +.searchInput { + width: 100%; +} + +.searchResults { + position: absolute; + top: 100%; + left: 0; + right: 0; + z-index: 1000; + 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; + + :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 index 7393c08c8..d2658da60 100644 --- 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 @@ -1,22 +1,45 @@ -import React, { useCallback, useEffect, useRef, useState } from "react"; -import { - AutoComplete, - Input as AntInput, - message, - Modal, - Spin, - Button, -} from "antd"; -import { ContractData, weChatGroup, ChatRecord } from "@/pages/pc/ckbox/data"; +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"; -declare const AMap: any; +// 声明腾讯地图类型 +declare global { + interface Window { + qq: any; + } +} interface SelectMapProps { visible: boolean; onClose: () => void; - contract: ContractData | weChatGroup; - addMessage: (message: ChatRecord) => 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 = ({ @@ -24,874 +47,360 @@ const SelectMap: React.FC = ({ onClose, contract, addMessage, + onConfirm, }) => { - const [selectedLocation, setSelectedLocation] = useState(null); - const mapRef = useRef(null); - const markersRef = useRef([]); // 使用数组保存所有标记点 - const geocoderRef = useRef(null); // 保存 Geocoder 实例 - const placeSearchRef = useRef(null); // 保存 PlaceSearch 实例 - const geolocationRef = useRef(null); // 保存 Geolocation 实例 - const pendingClickRef = useRef<{ - lat: number; - lng: number; - lnglat: any; - } | null>(null); // 保存待处理的点击坐标 - const [options, setOptions] = useState([]); - const [mapLoading, setMapLoading] = useState(true); - - const sendCommand = useWebSocketStore(state => state.sendCommand); - - // 清除所有标记点 - const clearAllMarkers = useCallback(() => { - if (markersRef.current && markersRef.current.length > 0) { - console.log(`清除 ${markersRef.current.length} 个标记点`); - markersRef.current.forEach(marker => { - if (marker) { - marker.setMap(null); - marker = null; - } - }); - markersRef.current = []; - } - }, []); - - const addMarker = useCallback( - (lnglat: any) => { - console.log("addMarker 调用,坐标:", lnglat); - - // 先清除所有现有的标记点 - clearAllMarkers(); - - // 创建红色图标,保持默认倒水滴形状 - const redIcon = new AMap.Icon({ - size: new AMap.Size(25, 34), // 默认标记点尺寸 - image: "https://webapi.amap.com/theme/v1.3/markers/n/mark_r.png", // 红色标记点图片 - imageOffset: new AMap.Pixel(0, 0), - imageSize: new AMap.Size(25, 34), - }); - - // 创建新的标记点 - const newMarker = new AMap.Marker({ - position: lnglat, - map: mapRef.current, - icon: redIcon, - }); - - // 将新标记点添加到数组中 - markersRef.current.push(newMarker); - - mapRef.current.setCenter(lnglat); - mapRef.current.setZoom(16); // 确保缩放到合适级别 - console.log("新 marker 已添加并居中"); - }, - [clearAllMarkers], + 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 mapContainerRef = useRef(null); + const geocoderRef = useRef(null); + const searchServiceRef = useRef(null); + const markerRef = useRef(null); + const { sendCommand } = useWebSocketStore.getState(); - // 通用的地址获取函数 - const getAddressForLocation = useCallback( - (lat: number, lng: number, lnglat: any) => { - console.log("=== getAddressForLocation 调用 ==="); - console.log("坐标:", { lat, lng }); - console.log( - "Geocoder ref 状态:", - geocoderRef.current ? "存在" : "不存在", - ); - - // 检查 Geocoder 是否已初始化 - if (!geocoderRef.current) { - console.warn("Geocoder 未初始化,无法获取地址"); - return false; - } - - // 使用更精确的坐标进行查询 - // 高德地图 getAddress 支持 LngLat 对象或 [lng, lat] 数组 - let queryLnglat: any; - if (lnglat && typeof lnglat.getLat === "function") { - // 如果传入的是 LngLat 对象,直接使用 - queryLnglat = lnglat; - } else { - // 否则创建新的 LngLat 对象或使用数组格式 - try { - queryLnglat = new AMap.LngLat(lng, lat); - } catch (error) { - console.error("创建 LngLat 对象失败,使用数组格式:", error); - queryLnglat = [lng, lat]; - } - } - console.log("调用 geocoder.getAddress,坐标:", queryLnglat); - - try { - geocoderRef.current.getAddress( - queryLnglat, - (status: string, result: any) => { - console.log("=== Geocoder 回调触发(通用函数) ==="); - console.log("Status:", status); - console.log( - "Result:", - result ? JSON.stringify(result, null, 2) : "null", - ); - - if ( - status === "complete" && - result && - result.info === "OK" && - result.regeocode - ) { - const regeocode = result.regeocode; - const formattedAddress = regeocode.formattedAddress || ""; - const addressComponent = regeocode.addressComponent || {}; - - // 构建详细地址信息 - let addressLabel = formattedAddress; - let poiName = "点击位置"; - - // 优先级1: 如果有POI信息,优先使用POI名称 - if (regeocode.pois && regeocode.pois.length > 0) { - const poi = regeocode.pois[0]; - poiName = poi.name || poiName; - const poiAddress = poi.address || ""; - addressLabel = poiAddress - ? `${poiName} ${poiAddress}` - : `${poiName} ${formattedAddress}`; - } - // 优先级2: 如果有建筑物信息 - else if (regeocode.buildings && regeocode.buildings.length > 0) { - const building = regeocode.buildings[0]; - poiName = building.name || addressComponent.building || poiName; - addressLabel = `${poiName} ${formattedAddress}`; - } - // 优先级3: 如果有AOI(兴趣区域)信息 - else if (regeocode.aois && regeocode.aois.length > 0) { - const aoi = regeocode.aois[0]; - poiName = aoi.name || poiName; - addressLabel = `${poiName} ${formattedAddress}`; - } - // 优先级4: 使用地址组件构建详细地址 - else if (addressComponent.building) { - poiName = addressComponent.building; - addressLabel = `${poiName} ${formattedAddress}`; - } - // 优先级5: 组合地址组件 - else { - 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.township) - parts.push(addressComponent.township); - if (addressComponent.street) - parts.push(addressComponent.street); - if (addressComponent.streetNumber) - parts.push(addressComponent.streetNumber); - - if (parts.length > 0) { - const fullAddress = parts.join(""); - poiName = - addressComponent.street || - addressComponent.district || - "点击位置"; - addressLabel = fullAddress || formattedAddress; - } else { - addressLabel = - formattedAddress || `纬度: ${lat}, 经度: ${lng}`; - } - } - - setSelectedLocation({ - lat: lat, - lng: lng, - label: addressLabel, - poiname: poiName, - }); - - message.success("地址信息获取成功"); - } else { - console.warn("=== Geocoder 返回异常,尝试 PlaceSearch ==="); - console.warn("Status:", status); - console.warn("Result:", result); - // Geocoder 失败,使用 PlaceSearch 作为备用 - if (placeSearchRef.current) { - try { - const searchLnglat = lnglat || [lng, lat]; - placeSearchRef.current.searchNearBy( - "", - searchLnglat, - 1000, - (searchStatus: string, searchResult: any) => { - if ( - searchStatus === "complete" && - searchResult && - searchResult.info === "OK" && - searchResult.poiList?.pois?.length > 0 - ) { - const poi = searchResult.poiList.pois[0]; - const poiLabel = poi.address - ? `${poi.name} ${poi.address}` - : poi.name; - setSelectedLocation({ - lat: lat, - lng: lng, - label: poiLabel, - poiname: poi.name, - }); - message.success("通过附近搜索获取到地址信息"); - } else { - console.warn("PlaceSearch 返回异常:", { - status: searchStatus, - result: searchResult, - }); - const coordLabel = `纬度: ${lat}, 经度: ${lng}`; - setSelectedLocation({ - lat: lat, - lng: lng, - label: coordLabel, - poiname: "点击位置", - }); - message.warning("无法获取详细地址信息,但坐标已记录"); - } - }, - ); - } catch (placeSearchError) { - console.error( - "PlaceSearch.searchNearBy 调用异常:", - placeSearchError, - ); - const coordLabel = `纬度: ${lat}, 经度: ${lng}`; - setSelectedLocation({ - lat: lat, - lng: lng, - label: coordLabel, - poiname: "点击位置", - }); - message.warning("无法获取详细地址信息,但坐标已记录"); - } - } else { - const coordLabel = `纬度: ${lat}, 经度: ${lng}`; - setSelectedLocation({ - lat: lat, - lng: lng, - label: coordLabel, - poiname: "点击位置", - }); - message.warning("无法获取详细地址信息,但坐标已记录"); - } - } - }, - ); - } catch (error) { - console.error("=== Geocoder.getAddress 调用异常 ===", error); - // 如果 geocoder 调用失败,尝试使用 PlaceSearch - if (placeSearchRef.current) { - console.log("尝试使用 PlaceSearch 作为备用方案"); - try { - placeSearchRef.current.searchNearBy( - "", - lnglat || [lng, lat], - 1000, - (status: string, result: any) => { - if ( - status === "complete" && - result && - result.info === "OK" && - result.poiList?.pois?.length > 0 - ) { - const poi = result.poiList.pois[0]; - const poiLabel = poi.address - ? `${poi.name} ${poi.address}` - : poi.name; - setSelectedLocation({ - lat: lat, - lng: lng, - label: poiLabel, - poiname: poi.name, - }); - message.success("通过附近搜索获取到地址信息"); - } else { - const coordLabel = `纬度: ${lat}, 经度: ${lng}`; - setSelectedLocation({ - lat: lat, - lng: lng, - label: coordLabel, - poiname: "点击位置", - }); - message.warning("无法获取详细地址信息,但坐标已记录"); - } - }, - ); - } catch (placeSearchError) { - console.error("PlaceSearch 调用也失败:", placeSearchError); - const coordLabel = `纬度: ${lat}, 经度: ${lng}`; - setSelectedLocation({ - lat: lat, - lng: lng, - label: coordLabel, - poiname: "点击位置", - }); - message.warning("无法获取详细地址信息,但坐标已记录"); - } - } else { - const coordLabel = `纬度: ${lat}, 经度: ${lng}`; - setSelectedLocation({ - lat: lat, - lng: lng, - label: coordLabel, - poiname: "点击位置", - }); - message.warning("无法获取详细地址信息,但坐标已记录"); - } - return false; - } - - return true; - }, - [], - ); - - const initMap = useCallback(() => { - console.log("initMap 执行中"); - setMapLoading(true); - - // 确保容器存在 - const container = document.getElementById("amap-container"); - if (!container) { - console.error("地图容器不存在"); - setMapLoading(false); - return; - } - - // 确保容器样式正确 - container.style.pointerEvents = "auto"; - container.style.cursor = "crosshair"; - container.style.position = "relative"; - container.style.zIndex = "1"; - - const map = new AMap.Map("amap-container", { - zoom: 16, - center: [118.113653, 24.470164], // 默认中心 - viewMode: "2D", // 明确指定视图模式 - }); - mapRef.current = map; - - // 添加超时机制,防止 loading 一直显示 - const loadingTimeout = setTimeout(() => { - console.warn("地图加载超时,强制关闭 loading"); - setMapLoading(false); - }, 10000); // 10秒超时 - - // 立即加载插件,不等待地图 complete 事件 - console.log("=== 立即开始加载 AMap 插件(地图创建后) ==="); - AMap.plugin( - [ - "AMap.AutoComplete", - "AMap.PlaceSearch", - "AMap.Geocoder", - "AMap.Geolocation", - ], - error => { - if (error) { - console.error("=== AMap 插件加载失败 ===", error); - message.error("地图插件加载失败,部分功能可能不可用"); - clearTimeout(loadingTimeout); - setMapLoading(false); - return; - } - console.log("=== AMap 插件加载成功 ==="); - - // 立即创建 PlaceSearch 和 Geocoder 实例 - const placeSearch = new AMap.PlaceSearch({ - city: "全国", - map: map, - }); - placeSearchRef.current = placeSearch; - console.log("PlaceSearch 实例已创建"); - - const geocoder = new AMap.Geocoder({ - city: "全国", - radius: 1000, // 搜索半径,单位米 - extensions: "all", // 返回详细信息,包括POI、建筑物等 - }); - geocoderRef.current = geocoder; - console.log("Geocoder 实例已创建并保存到 ref,现在可以立即使用"); - - // 创建 Geolocation 实例用于获取当前位置 - const geolocation = new AMap.Geolocation({ - enableHighAccuracy: true, // 是否使用高精度定位,默认:true - timeout: 10000, // 超过10秒后停止定位,默认:无穷大 - maximumAge: 0, // 定位结果缓存0毫秒,默认:0 - convert: true, // 自动偏移坐标,偏移后的坐标为高德坐标,默认:true - showButton: false, // 显示定位按钮,默认:true - buttonPosition: "RB", // 定位按钮停靠位置,默认:'LB',左下角 - showMarker: false, // 定位成功后在定位到的位置显示点标记,默认:true - showCircle: false, // 定位成功后用圆圈表示定位精度范围,默认:true - panToLocation: false, // 定位成功后将定位到的位置作为地图中心点,默认:true - zoomToAccuracy: false, // 定位成功后调整地图视野范围使定位位置及精度范围视野内可见,默认:false - }); - geolocationRef.current = geolocation; - - // 获取当前位置 - console.log("=== 开始获取当前位置 ==="); - geolocation.getCurrentPosition((status: string, result: any) => { - console.log("=== 定位回调触发 ==="); - console.log("定位状态:", status); - console.log("定位结果:", result); - - if (status === "complete") { - const { position, formattedAddress, addressComponent } = result; - const lat = position.lat; - const lng = position.lng; - const lnglat = new AMap.LngLat(lng, lat); - - console.log("定位成功,当前位置:", { lat, lng }); - console.log("定位地址:", formattedAddress); - - // 将地图中心设置为当前位置 - map.setCenter(lnglat); - map.setZoom(16); - - // 添加当前位置标记 - addMarker(lnglat); - - // 设置选中位置信息 - let addressLabel = formattedAddress || `纬度: ${lat}, 经度: ${lng}`; - let poiName = "当前位置"; - - // 尝试从地址组件中获取更详细的信息 - if (addressComponent) { - 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.streetNumber) - parts.push(addressComponent.streetNumber); - - if (parts.length > 0) { - addressLabel = parts.join(""); - } - - poiName = - addressComponent.street || - addressComponent.district || - "当前位置"; - } - - setSelectedLocation({ - lat: lat, - lng: lng, - label: addressLabel, - poiname: poiName, - }); - - message.success("已获取当前位置"); - } else { - console.warn("定位失败:", result); - message.warning( - "无法获取当前位置,请手动点击地图选择位置。原因: " + - (result.message || "定位服务不可用"), - ); - // 定位失败时,使用默认中心点 - console.log("使用默认中心点"); - } - }); - - // 如果有待处理的点击坐标,立即处理它 - if (pendingClickRef.current) { - console.log("=== 检测到待处理的点击坐标,立即处理 ==="); - const { lat, lng, lnglat } = pendingClickRef.current; - console.log("待处理坐标:", { lat, lng }); - setTimeout(() => { - const success = getAddressForLocation(lat, lng, lnglat); - if (success) { - console.log("✓ 待处理的坐标已成功获取地址"); - pendingClickRef.current = null; // 清除待处理坐标 - } else { - console.warn("待处理的坐标获取地址失败"); - } - }, 100); - } - }, - ); - - // 立即绑定点击事件,插件可能已加载或正在加载 - console.log("立即绑定点击事件(插件可能已初始化)"); - map.on("click", (e: any) => { - console.log("=== 地图点击事件触发 ==="); - console.log("点击事件对象:", e); - console.log("点击位置对象:", e.lnglat); - console.log( - "Geocoder ref 状态:", - geocoderRef.current ? "已初始化" : "未初始化", - ); - - if (!e || !e.lnglat) { - console.error("点击事件无效,缺少 lnglat"); - return; - } - - const lnglat = e.lnglat; - const lat = lnglat.getLat(); - const lng = lnglat.getLng(); - console.log(`点击坐标 - 纬度: ${lat}, 经度: ${lng}`); - - // 立即添加标记和居中 - addMarker(lnglat); - - // 设置基本 selectedLocation(至少有坐标) - setSelectedLocation({ - lat: lat, - lng: lng, - label: "正在获取地址信息...", - poiname: "点击位置", - }); - - // 如果 Geocoder 已初始化,立即使用它 - if (geocoderRef.current) { - console.log("Geocoder 已初始化,立即获取地址"); - getAddressForLocation(lat, lng, lnglat); - } else { - console.log("Geocoder 未初始化,保存坐标待插件加载完成后处理"); - // 保存待处理的坐标 - pendingClickRef.current = { lat, lng, lnglat }; - } - }); - - // 等待地图完全加载后关闭 loading - map.on("complete", () => { - console.log("地图加载完成"); - // 清除超时定时器 - clearTimeout(loadingTimeout); - // 关闭 loading - setMapLoading(false); - console.log("地图加载完成,loading 已关闭"); - - // 确保地图容器可点击 - const mapContainer = map.getContainer(); - if (mapContainer) { - mapContainer.style.pointerEvents = "auto"; - mapContainer.style.cursor = "crosshair"; - console.log("地图容器指针事件已启用"); - } - }); - - // 如果地图加载失败,也设置加载完成 - map.on("error", (error: any) => { - console.error("地图加载错误:", error); - clearTimeout(loadingTimeout); - setMapLoading(false); - message.error("地图加载失败"); - }); - }, [addMarker, getAddressForLocation]); - - const handleSearch = value => { - if (value) { - AMap.plugin("AMap.AutoComplete", () => { - const auto = new AMap.AutoComplete({ city: "全国" }); - auto.search(value, (status, result) => { - if (status === "complete") { - setOptions( - result.tips.map(tip => ({ - value: tip.name, - data: tip, - })), - ); - } - }); - }); - } else { - setOptions([]); - } - }; - - const onSelect = (value, option) => { - const { district, address, name, location } = option.data; - const lnglat = location; - setSelectedLocation({ - lat: lnglat.lat, - lng: lnglat.lng, - label: `${name} ${address || district}`, - poiname: name, - }); - addMarker(lnglat); - mapRef.current.setCenter(lnglat); - }; - - const handleModalChange = useCallback( - (visible: boolean) => { - if (visible) { - console.log("模态打开:开始加载地图脚本"); - setMapLoading(true); - setSelectedLocation(null); - const script = document.createElement("script"); - script.src = - "https://webapi.amap.com/maps?v=1.4.15&key=79370028f5763e46742125ed2e900c76&plugin=AMap.PlaceSearch,AMap.AutoComplete,AMap.Geocoder,AMap.Geolocation"; - script.async = true; - script.onload = () => { - console.log("脚本加载成功:开始初始化地图"); - setTimeout(() => initMap(), 100); // 添加延迟确保 DOM 就绪 - }; - script.onerror = () => { - console.error("脚本加载失败"); - message.error("地图加载失败,请检查网络或API密钥"); - setMapLoading(false); - }; - document.body.appendChild(script); - } else { - console.log("模态关闭:清理地图和脚本"); - // 清除所有标记点 - clearAllMarkers(); - // 重置 ref - geocoderRef.current = null; - placeSearchRef.current = null; - geolocationRef.current = null; - // Cleanup on close - const scripts = document.querySelectorAll( - 'script[src*="webapi.amap.com"]', - ); - scripts.forEach(s => { - if (document.body.contains(s)) { - document.body.removeChild(s); - } - }); - if (mapRef.current) { - mapRef.current.destroy(); - mapRef.current = null; - } - setMapLoading(false); - setOptions([]); // 重置搜索选项 - } - }, - [initMap, clearAllMarkers], - ); - - // 手动获取当前位置 - const handleGetCurrentLocation = useCallback(() => { - if (!geolocationRef.current) { - message.warning("定位服务未初始化,请稍候再试"); - return; - } - - console.log("=== 手动触发获取当前位置 ==="); - message.loading({ content: "正在获取当前位置...", key: "location" }); - - geolocationRef.current.getCurrentPosition((status: string, result: any) => { - message.destroy("location"); - console.log("=== 手动定位回调触发 ==="); - console.log("定位状态:", status); - console.log("定位结果:", result); - - if (status === "complete" && result && result.position) { - const { position, formattedAddress, addressComponent } = result; - const lat = position.lat; - const lng = position.lng; - const lnglat = new AMap.LngLat(lng, lat); - - console.log("定位成功,当前位置:", { lat, lng }); - - // 将地图中心设置为当前位置 - if (mapRef.current) { - mapRef.current.setCenter(lnglat); - mapRef.current.setZoom(16); - } - - // 添加当前位置标记 - addMarker(lnglat); - - // 设置选中位置信息 - let addressLabel = formattedAddress || `纬度: ${lat}, 经度: ${lng}`; - let poiName = "当前位置"; - - // 尝试从地址组件中获取更详细的信息 - if (addressComponent) { - 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.streetNumber) - parts.push(addressComponent.streetNumber); - - if (parts.length > 0) { - addressLabel = parts.join(""); - } - - poiName = - addressComponent.street || addressComponent.district || "当前位置"; - } - - setSelectedLocation({ - lat: lat, - lng: lng, - label: addressLabel, - poiname: poiName, - }); - - message.success("已获取当前位置"); - } else { - console.warn("定位失败:", result); - message.error( - "获取当前位置失败: " + (result?.message || "定位服务不可用"), - ); - } - }); - }, [addMarker]); - - const handleSendLocation = () => { - if (!selectedLocation || !selectedLocation.lat || !selectedLocation.lng) { - message.warning("请选择有效位置"); - return; - } - - const { lat, lng, label, poiname } = selectedLocation; - const content = ``; - - const messageId = +Date.now(); - const params = { - wechatAccountId: contract.wechatAccountId, - wechatChatroomId: contract?.chatroomId ? contract.id : 0, - wechatFriendId: contract?.chatroomId ? 0 : contract.id, - msgSubType: 0, - msgType: 48, - content: content, - seq: messageId, - }; - - // 构造本地消息 - const localMessage: ChatRecord = { - id: messageId, - wechatAccountId: contract.wechatAccountId, - wechatFriendId: contract?.chatroomId ? 0 : contract.id, - wechatChatroomId: contract?.chatroomId ? contract.id : 0, - tenantId: 0, - accountId: 0, - synergyAccountId: 0, - content: content, - 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); - - sendCommand("CmdSendMessage", params); - - onClose(); - setSelectedLocation(null); - }; - + // 初始化地图 useEffect(() => { - if (visible) { - handleModalChange(true); - // 确保容器在模态框打开后可以接收事件 - setTimeout(() => { - const container = document.getElementById("amap-container"); - if (container) { - container.style.pointerEvents = "auto"; - container.style.cursor = "crosshair"; - console.log( - "容器样式已设置,pointerEvents:", - container.style.pointerEvents, - ); + if (visible && mapContainerRef.current && window.qq && window.qq.maps) { + // 创建地图实例 + const center = new window.qq.maps.LatLng(39.908823, 116.39747); // 默认北京 + const mapInstance = new window.qq.maps.Map(mapContainerRef.current, { + center: center, + zoom: 13, + }); + + setMap(mapInstance); + + // 创建地理编码服务 + geocoderRef.current = new window.qq.maps.Geocoder({ + complete: (result: any) => { + setIsReverseGeocoding(false); + if (result && result.detail) { + const detail = result.detail; + const location = detail.location || detail.latLng; + if (location) { + setSelectedLocation({ + x: location.lng?.toString() || location.getLng().toString(), + y: location.lat?.toString() || location.getLat().toString(), + scale: "16", + label: + detail.address || detail.formatted_addresses?.recommend || "", + poiname: + detail.addressComponents?.street || detail.address || "", + maptype: "0", + poiid: detail.poiid || "", + }); + } + } + }, + error: () => { + setIsReverseGeocoding(false); + message.error("获取地址信息失败"); + }, + }); + + // 创建搜索服务 + searchServiceRef.current = new window.qq.maps.SearchService({ + complete: (result: any) => { + setIsSearching(false); + if (result && result.detail) { + const pois = result.detail.pois || []; + if (pois.length > 0) { + const searchResults = pois.map((poi: any) => { + const location = poi.location || poi.latLng; + return { + id: + poi.id || + `${location.lat || location.getLat()},${ + location.lng || location.getLng() + }`, + title: poi.title || poi.name || "", + address: poi.address || poi.ad_info?.adcode || "", + location: { + lat: location.lat || location.getLat(), + lng: location.lng || location.getLng(), + }, + adcode: poi.adcode || poi.ad_info?.adcode || "", + city: poi.city || poi.ad_info?.city || "", + district: poi.district || poi.ad_info?.district || "", + }; + }); + setSearchResults(searchResults); + } else { + setSearchResults([]); + message.info("未找到相关地址"); + } + } else { + setSearchResults([]); + message.info("未找到相关地址"); + } + }, + error: () => { + setIsSearching(false); + message.error("搜索失败,请重试"); + }, + }); + + // 地图点击事件 + window.qq.maps.event.addListener(mapInstance, "click", (event: any) => { + const lat = event.latLng.lat; + const lng = event.latLng.lng; + + // 更新标记点 + if (markerRef.current) { + markerRef.current.setMap(null); } - }, 200); - } else { - handleModalChange(false); + + const newMarker = new window.qq.maps.Marker({ + position: new window.qq.maps.LatLng(lat, lng), + map: mapInstance, + }); + + markerRef.current = newMarker; + + // 反向地理编码 + setIsReverseGeocoding(true); + geocoderRef.current.getAddress(new window.qq.maps.LatLng(lat, lng)); + }); + + return () => { + if (mapInstance) { + window.qq.maps.event.clearListeners(mapInstance, "click"); + } + }; } - }, [visible, handleModalChange]); + }, [visible]); + + // 搜索地址 + const handleSearch = () => { + if (!searchValue.trim()) { + message.warning("请输入搜索关键词"); + return; + } + + if (!searchServiceRef.current) { + message.error("搜索服务未初始化"); + return; + } + + setIsSearching(true); + searchServiceRef.current.search(searchValue); + }; + + // 选择搜索结果 + const handleSelectResult = (result: SearchResult) => { + if (!map) return; + + const lat = result.location.lat; + const lng = result.location.lng; + + // 移动地图中心 + map.setCenter(new window.qq.maps.LatLng(lat, lng)); + map.setZoom(16); + + // 更新标记点 + if (markerRef.current) { + markerRef.current.setMap(null); + } + + const newMarker = new window.qq.maps.Marker({ + position: new window.qq.maps.LatLng(lat, lng), + map: map, + }); + + markerRef.current = newMarker; + + // 设置选中的位置信息 + setSelectedLocation({ + x: lng.toString(), + y: lat.toString(), + scale: "16", + label: result.address || result.title, + poiname: result.title || "", + maptype: "0", + poiid: result.id || "", + }); + + // 清空搜索结果 + setSearchResults([]); + setSearchValue(""); + }; + + // 确认选择 + const handleConfirm = () => { + if (!selectedLocation) { + message.warning("请先选择位置"); + return; + } + + // 生成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); + + // 发送消息到服务器 + 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(); + }; + + // 关闭弹窗 + const handleClose = () => { + setSearchValue(""); + setSearchResults([]); + setSelectedLocation(null); + if (markerRef.current) { + markerRef.current.setMap(null); + markerRef.current = null; + } + setIsSearching(false); + setIsReverseGeocoding(false); + onClose(); + }; return ( -
    - - - + open={visible} + onCancel={handleClose} + width={900} + centered + footer={[ + , -
    + {isReverseGeocoding ? "正在获取地址信息..." : "确认"} + , + ]} + > +
    + {/* 搜索区域 */} +
    + setSearchValue(e.target.value)} + onPressEnter={handleSearch} + prefix={} + suffix={ + + } + className={styles.searchInput} + /> - {/* 显示点击位置的信息 */} - {selectedLocation && ( -
    -
    - 位置信息: -
    -
    - 地址: {selectedLocation.label || "加载中..."} -
    -
    - 坐标: {selectedLocation.lat},{" "} - {selectedLocation.lng} -
    + {/* 搜索结果列表 */} + {searchResults.length > 0 && ( +
    + ( + handleSelectResult(item)} + > + } + title={item.title} + description={item.address} + /> + + )} + /> +
    + )}
    - )} -
    - {mapLoading && ( -
    - + {/* 地图区域 */} +
    +
    + {isReverseGeocoding && ( +
    + +
    + )} +
    + + {/* 选中位置信息 */} + {selectedLocation && ( +
    +
    + 已选择位置 +
    +
    + {selectedLocation.label || selectedLocation.poiname} +
    +
    + 经度: {selectedLocation.x}, 纬度: {selectedLocation.y} +
    )} -
    { - // 添加原生点击事件监听作为备用 - console.log("容器原生点击事件触发", e); - }} - >
    ); From 782031cbc6c451a76fc705a0f9e5eb3b351853b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Fri, 21 Nov 2025 19:02:41 +0800 Subject: [PATCH 13/49] =?UTF-8?q?=E5=A2=9E=E5=BC=BASelectMap=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=EF=BC=8C=E6=B7=BB=E5=8A=A0=E7=94=A8=E6=88=B7=E5=BD=93?= =?UTF-8?q?=E5=89=8D=E4=BD=8D=E7=BD=AE=E8=8E=B7=E5=8F=96=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=E5=9C=B0=E5=9D=80=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E5=92=8C=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86?= =?UTF-8?q?=EF=BC=8C=E6=94=B9=E8=BF=9B=E5=8A=A0=E8=BD=BD=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=EF=BC=8C=E6=8F=90=E5=8D=87=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E4=BD=93=E9=AA=8C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MessageEnter/components/selectMap.tsx | 145 +++++++++++++++--- 1 file changed, 120 insertions(+), 25 deletions(-) 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 index d2658da60..bc75ddc5f 100644 --- 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 @@ -57,6 +57,7 @@ const SelectMap: React.FC = ({ ); const [map, setMap] = useState(null); const [isReverseGeocoding, setIsReverseGeocoding] = useState(false); + const [isLocating, setIsLocating] = useState(false); const mapContainerRef = useRef(null); const geocoderRef = useRef(null); const searchServiceRef = useRef(null); @@ -79,27 +80,70 @@ const SelectMap: React.FC = ({ geocoderRef.current = new window.qq.maps.Geocoder({ complete: (result: any) => { setIsReverseGeocoding(false); - if (result && result.detail) { - const detail = result.detail; - const location = detail.location || detail.latLng; - if (location) { - setSelectedLocation({ - x: location.lng?.toString() || location.getLng().toString(), - y: location.lat?.toString() || location.getLat().toString(), - scale: "16", - label: - detail.address || detail.formatted_addresses?.recommend || "", - poiname: - detail.addressComponents?.street || detail.address || "", - maptype: "0", - poiid: detail.poiid || "", - }); + try { + if (result && result.detail) { + const detail = result.detail; + const location = detail.location || detail.latLng; + if (location) { + const lat = + location.lat || (location.getLat ? location.getLat() : null); + const lng = + location.lng || (location.getLng ? location.getLng() : null); + + if (lat && lng) { + // 构建地址标签 + let addressLabel = ""; + if (detail.formatted_addresses) { + addressLabel = + detail.formatted_addresses.recommend || + detail.formatted_addresses.rough || + ""; + } + if (!addressLabel && detail.address) { + addressLabel = detail.address; + } + if (!addressLabel && detail.addressComponents) { + const addr = detail.addressComponents; + const parts = []; + if (addr.province) parts.push(addr.province); + if (addr.city) parts.push(addr.city); + if (addr.district) parts.push(addr.district); + if (addr.street) parts.push(addr.street); + if (addr.street_number) parts.push(addr.street_number); + addressLabel = parts.join(""); + } + if (!addressLabel) { + addressLabel = `${lat.toFixed(6)}, ${lng.toFixed(6)}`; + } + + setSelectedLocation({ + x: lng.toString(), + y: lat.toString(), + scale: "16", + label: addressLabel, + poiname: + detail.addressComponents?.street || detail.poiid || "", + maptype: "0", + poiid: detail.poiid || "", + }); + } else { + message.warning("无法解析位置信息"); + } + } else { + message.warning("未找到位置信息"); + } + } else { + message.warning("获取地址信息失败:返回数据为空"); } + } catch (error) { + console.error("解析地址信息错误:", error); + message.error("解析地址信息失败"); } }, - error: () => { + error: (error: any) => { setIsReverseGeocoding(false); - message.error("获取地址信息失败"); + console.error("反向地理编码错误:", error); + message.error("获取地址信息失败,请稍后重试"); }, }); @@ -163,10 +207,60 @@ const SelectMap: React.FC = ({ markerRef.current = newMarker; // 反向地理编码 - setIsReverseGeocoding(true); - geocoderRef.current.getAddress(new window.qq.maps.LatLng(lat, lng)); + if (geocoderRef.current) { + setIsReverseGeocoding(true); + geocoderRef.current.getAddress(new window.qq.maps.LatLng(lat, lng)); + } }); + // 获取用户当前位置 + if (navigator.geolocation) { + setIsLocating(true); + navigator.geolocation.getCurrentPosition( + position => { + setIsLocating(false); + const userLat = position.coords.latitude; + const userLng = position.coords.longitude; + + // 移动地图中心到用户位置 + const userLocation = new window.qq.maps.LatLng(userLat, userLng); + mapInstance.setCenter(userLocation); + mapInstance.setZoom(16); + + // 添加标记点 + if (markerRef.current) { + markerRef.current.setMap(null); + } + + const newMarker = new window.qq.maps.Marker({ + position: userLocation, + map: mapInstance, + }); + + markerRef.current = newMarker; + + // 获取用户位置的地址信息 + if (geocoderRef.current) { + setIsReverseGeocoding(true); + geocoderRef.current.getAddress(userLocation); + } + }, + error => { + setIsLocating(false); + console.error("获取位置失败:", error); + // 如果获取位置失败,使用默认位置(北京) + message.info("无法获取您的位置,已定位到默认位置"); + }, + { + enableHighAccuracy: true, + timeout: 10000, + maximumAge: 0, + }, + ); + } else { + message.info("您的浏览器不支持地理定位功能"); + } + return () => { if (mapInstance) { window.qq.maps.event.clearListeners(mapInstance, "click"); @@ -309,6 +403,7 @@ const SelectMap: React.FC = ({ } setIsSearching(false); setIsReverseGeocoding(false); + setIsLocating(false); onClose(); }; @@ -379,12 +474,12 @@ const SelectMap: React.FC = ({ {/* 地图区域 */}
    -
    - {isReverseGeocoding && ( -
    - -
    - )} + +
    +
    {/* 选中位置信息 */} From 21de87ca32efd51f3125d2eaf259f2376e3839bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Sat, 22 Nov 2025 11:45:53 +0800 Subject: [PATCH 14/49] =?UTF-8?q?=E6=9B=B4=E6=96=B0SelectMap=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E4=BB=A5=E4=BD=BF=E7=94=A8=E6=96=B0=E7=89=88=E8=85=BE?= =?UTF-8?q?=E8=AE=AF=E5=9C=B0=E5=9B=BEAPI=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=9C=B0=E5=9B=BE=E5=8A=A0=E8=BD=BD=E5=92=8C=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE=E8=8E=B7=E5=8F=96=E9=80=BB=E8=BE=91=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86=E5=92=8C?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E4=BD=93=E9=AA=8C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Touchkebao/index.html | 2 +- .../MessageEnter/components/selectMap.tsx | 742 +++++++++++------- Touchkebao/腾讯地图定位服务修复说明.md | 73 ++ 3 files changed, 534 insertions(+), 283 deletions(-) create mode 100644 Touchkebao/腾讯地图定位服务修复说明.md diff --git a/Touchkebao/index.html b/Touchkebao/index.html index d0dcd4e9b..f1c04fe6d 100644 --- a/Touchkebao/index.html +++ b/Touchkebao/index.html @@ -13,7 +13,7 @@ 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 index bc75ddc5f..d09316cec 100644 --- 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 @@ -4,10 +4,11 @@ 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 { - qq: any; + TMap: any; + geolocationRef: any; // 全局IP定位服务引用 } } @@ -58,281 +59,454 @@ const SelectMap: React.FC = ({ 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 searchServiceRef = useRef(null); + const suggestServiceRef = useRef(null); const markerRef = useRef(null); const { sendCommand } = useWebSocketStore.getState(); + // 加载腾讯地图SDK + useEffect(() => { + // 检查TMap是否已经加载 + if (window.TMap) { + setTmapLoaded(true); + 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加载成功"); + setTmapLoaded(true); + }; + script.onerror = () => { + console.error("腾讯地图SDK加载失败"); + message.error("地图加载失败,请刷新页面重试"); + }; + document.head.appendChild(script); + + return () => { + // 清理script标签 + if (document.head.contains(script)) { + document.head.removeChild(script); + } + }; + }, []); + // 初始化地图 useEffect(() => { - if (visible && mapContainerRef.current && window.qq && window.qq.maps) { - // 创建地图实例 - const center = new window.qq.maps.LatLng(39.908823, 116.39747); // 默认北京 - const mapInstance = new window.qq.maps.Map(mapContainerRef.current, { - center: center, - zoom: 13, - }); + if (visible && mapContainerRef.current && tmapLoaded && window.TMap) { + console.log("开始初始化地图"); - setMap(mapInstance); - - // 创建地理编码服务 - geocoderRef.current = new window.qq.maps.Geocoder({ - complete: (result: any) => { - setIsReverseGeocoding(false); - try { - if (result && result.detail) { - const detail = result.detail; - const location = detail.location || detail.latLng; - if (location) { - const lat = - location.lat || (location.getLat ? location.getLat() : null); - const lng = - location.lng || (location.getLng ? location.getLng() : null); - - if (lat && lng) { - // 构建地址标签 - let addressLabel = ""; - if (detail.formatted_addresses) { - addressLabel = - detail.formatted_addresses.recommend || - detail.formatted_addresses.rough || - ""; - } - if (!addressLabel && detail.address) { - addressLabel = detail.address; - } - if (!addressLabel && detail.addressComponents) { - const addr = detail.addressComponents; - const parts = []; - if (addr.province) parts.push(addr.province); - if (addr.city) parts.push(addr.city); - if (addr.district) parts.push(addr.district); - if (addr.street) parts.push(addr.street); - if (addr.street_number) parts.push(addr.street_number); - addressLabel = parts.join(""); - } - if (!addressLabel) { - addressLabel = `${lat.toFixed(6)}, ${lng.toFixed(6)}`; - } - - setSelectedLocation({ - x: lng.toString(), - y: lat.toString(), - scale: "16", - label: addressLabel, - poiname: - detail.addressComponents?.street || detail.poiid || "", - maptype: "0", - poiid: detail.poiid || "", - }); - } else { - message.warning("无法解析位置信息"); - } - } else { - message.warning("未找到位置信息"); - } - } else { - message.warning("获取地址信息失败:返回数据为空"); - } - } catch (error) { - console.error("解析地址信息错误:", error); - message.error("解析地址信息失败"); - } - }, - error: (error: any) => { - setIsReverseGeocoding(false); - console.error("反向地理编码错误:", error); - message.error("获取地址信息失败,请稍后重试"); - }, - }); - - // 创建搜索服务 - searchServiceRef.current = new window.qq.maps.SearchService({ - complete: (result: any) => { - setIsSearching(false); - if (result && result.detail) { - const pois = result.detail.pois || []; - if (pois.length > 0) { - const searchResults = pois.map((poi: any) => { - const location = poi.location || poi.latLng; - return { - id: - poi.id || - `${location.lat || location.getLat()},${ - location.lng || location.getLng() - }`, - title: poi.title || poi.name || "", - address: poi.address || poi.ad_info?.adcode || "", - location: { - lat: location.lat || location.getLat(), - lng: location.lng || location.getLng(), - }, - adcode: poi.adcode || poi.ad_info?.adcode || "", - city: poi.city || poi.ad_info?.city || "", - district: poi.district || poi.ad_info?.district || "", - }; - }); - setSearchResults(searchResults); - } else { - setSearchResults([]); - message.info("未找到相关地址"); - } - } else { - setSearchResults([]); - message.info("未找到相关地址"); - } - }, - error: () => { - setIsSearching(false); - message.error("搜索失败,请重试"); - }, - }); - - // 地图点击事件 - window.qq.maps.event.addListener(mapInstance, "click", (event: any) => { - const lat = event.latLng.lat; - const lng = event.latLng.lng; - - // 更新标记点 - if (markerRef.current) { - markerRef.current.setMap(null); - } - - const newMarker = new window.qq.maps.Marker({ - position: new window.qq.maps.LatLng(lat, lng), - map: mapInstance, + try { + // 创建地图实例 + const center = new window.TMap.LatLng(39.908823, 116.39747); // 默认北京 + const mapInstance = new window.TMap.Map(mapContainerRef.current, { + center: center, + zoom: 13, + rotation: 0, + pitch: 0, }); - markerRef.current = newMarker; + setMap(mapInstance); - // 反向地理编码 - if (geocoderRef.current) { - setIsReverseGeocoding(true); - geocoderRef.current.getAddress(new window.qq.maps.LatLng(lat, lng)); - } - }); + // 创建地理编码服务(用于反向地理编码) + geocoderRef.current = new window.TMap.service.Geocoder(); - // 获取用户当前位置 - if (navigator.geolocation) { - setIsLocating(true); - navigator.geolocation.getCurrentPosition( - position => { - setIsLocating(false); - const userLat = position.coords.latitude; - const userLng = position.coords.longitude; + // 使用腾讯地图内置的定位服务 + window.geolocationRef = window.TMap.service.Geolocation; - // 移动地图中心到用户位置 - const userLocation = new window.qq.maps.LatLng(userLat, userLng); - mapInstance.setCenter(userLocation); - mapInstance.setZoom(16); + // 创建搜索建议服务 + suggestServiceRef.current = new window.TMap.service.Suggestion({ + pageSize: 10, + autoExtend: true, + }); - // 添加标记点 + // 地图点击事件处理函数 + const handleMapClick = (evt: any) => { + try { + 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 newMarker = new window.qq.maps.Marker({ - position: userLocation, + // 创建新标记 + const newMarker = new window.TMap.MultiMarker({ + id: "marker-layer", map: mapInstance, + geometries: [ + { + id: "selected-marker", + styleId: "marker", + position: new window.TMap.LatLng(lat, lng), + properties: { + title: "选中位置", + }, + }, + ], }); markerRef.current = newMarker; - // 获取用户位置的地址信息 - if (geocoderRef.current) { - setIsReverseGeocoding(true); - geocoderRef.current.getAddress(userLocation); - } - }, - error => { + // 设置基本位置信息(防止白屏) + setSelectedLocation({ + x: lng.toString(), + y: lat.toString(), + scale: "16", + label: `${lat.toFixed(6)}, ${lng.toFixed(6)}`, + poiname: "选中位置", + maptype: "0", + poiid: "", + }); + + // 反向地理编码获取地址 + setIsReverseGeocoding(true); + geocoderRef.current + .getAddress({ location: new window.TMap.LatLng(lat, lng) }) + .then((result: any) => { + 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.toFixed(6)}, ${lng.toFixed(6)}`; + } + + setSelectedLocation({ + x: lng.toString(), + y: lat.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) => { + setIsReverseGeocoding(false); + console.error("反向地理编码错误:", error); + message.warning("获取详细地址信息失败,将使用坐标显示"); + }); + } catch (error) { + console.error("地图点击处理错误:", error); + message.error("处理地图点击时出错,请重试"); + } + }; + + // 绑定地图点击事件 + mapInstance.on("click", handleMapClick); + + // 使用腾讯地图API初始化用户位置 + const initializeUserLocation = ( + lat: number, + lng: number, + isDefault: boolean = false, + ) => { + console.log(isDefault ? "使用默认位置:" : "用户位置:", lat, lng); + + // 移动地图中心到位置 + const userLocation = new window.TMap.LatLng(lat, lng); + mapInstance.setCenter(userLocation); + mapInstance.setZoom(16); + + // 添加标记点 + if (markerRef.current) { + markerRef.current.setMap(null); + markerRef.current = null; + } + + const newMarker = new window.TMap.MultiMarker({ + id: "marker-layer", + map: mapInstance, + geometries: [ + { + id: "user-location", + styleId: "marker", + position: userLocation, + properties: { + title: isDefault ? "默认位置" : "当前位置", + }, + }, + ], + }); + + markerRef.current = newMarker; + + // 使用腾讯地图服务获取该位置的地址信息 + setIsReverseGeocoding(true); + geocoderRef.current + .getAddress({ location: userLocation }) + .then((result: any) => { + 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.toFixed(6)}, ${lng.toFixed(6)}`; + + setSelectedLocation({ + x: lng.toString(), + y: lat.toString(), + scale: "16", + label: addressLabel, + poiname: + addressComponent.street || + (isDefault ? "默认位置" : "当前位置"), + maptype: "0", + poiid: resultData.poi_id || "", + }); + } + }) + .catch((error: any) => { + setIsReverseGeocoding(false); + console.error("获取地址信息失败:", error); + // 即使获取地址失败,也设置基本的位置信息 + setSelectedLocation({ + x: lng.toString(), + y: lat.toString(), + scale: "16", + label: `${lat.toFixed(6)}, ${lng.toFixed(6)}`, + poiname: isDefault ? "默认位置" : "当前位置", + maptype: "0", + poiid: "", + }); + }); + }; + + // 使用腾讯地图IP定位获取用户位置 + setIsLocating(true); + try { + if (window.geolocationRef) { + window.geolocationRef.getLocation({ + timeout: 10000, + convert: true, + success: function (result: any) { + setIsLocating(false); + if (result && result.location) { + const { lat, lng } = result.location; + message.info("已定位到您的大致位置"); + initializeUserLocation(lat, lng, false); + } else { + // IP定位失败:使用默认位置 + message.info("无法获取您的位置,已定位到北京"); + // 使用默认位置(北京市) + initializeUserLocation(39.908823, 116.39747, true); + } + }, + error: function () { + setIsLocating(false); + message.info("无法获取您的位置,已定位到北京"); + // 使用默认位置(北京市) + initializeUserLocation(39.908823, 116.39747, true); + }, + }); + } else { + // 地理编码服务未初始化:使用默认位置 setIsLocating(false); - console.error("获取位置失败:", error); - // 如果获取位置失败,使用默认位置(北京) - message.info("无法获取您的位置,已定位到默认位置"); - }, - { - enableHighAccuracy: true, - timeout: 10000, - maximumAge: 0, - }, - ); - } else { - message.info("您的浏览器不支持地理定位功能"); + message.info("无法获取您的位置,已定位到北京"); + // 使用默认位置(北京市) + initializeUserLocation(39.908823, 116.39747, true); + } + } catch (error) { + // 捕获任何可能的错误,防止白屏 + console.error("定位过程中发生错误:", error); + setIsLocating(false); + message.error("定位服务出现异常,已定位到北京"); + // 使用默认位置(北京市) + initializeUserLocation(39.908823, 116.39747, true); + } + + return () => { + // 清理地图事件监听 + if (mapInstance) { + mapInstance.off("click", handleMapClick); + } + }; + } catch (error) { + console.error("初始化地图时出错:", error); + message.error("地图加载失败,请刷新页面重试"); + setIsLocating(false); + } + } + }, [visible, tmapLoaded]); + + // 搜索地址(获取搜索建议) + const handleSearch = () => { + try { + if (!searchValue.trim()) { + message.warning("请输入搜索关键词"); + return; } - return () => { - if (mapInstance) { - window.qq.maps.event.clearListeners(mapInstance, "click"); - } - }; - } - }, [visible]); + if (!suggestServiceRef.current) { + message.error("搜索服务未初始化,请刷新页面重试"); + return; + } - // 搜索地址 - const handleSearch = () => { - if (!searchValue.trim()) { - message.warning("请输入搜索关键词"); - return; - } + setIsSearching(true); + suggestServiceRef.current + .getSuggestions({ + keyword: searchValue, + location: map ? map.getCenter() : undefined, + }) + .then((result: any) => { + setIsSearching(false); + console.log("搜索建议结果:", result); - if (!searchServiceRef.current) { - message.error("搜索服务未初始化"); - return; + 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([]); } - - setIsSearching(true); - searchServiceRef.current.search(searchValue); }; // 选择搜索结果 const handleSelectResult = (result: SearchResult) => { - if (!map) return; + try { + if (!map) { + message.error("地图未初始化,请刷新页面重试"); + return; + } - const lat = result.location.lat; - const lng = result.location.lng; + const lat = result.location.lat; + const lng = result.location.lng; - // 移动地图中心 - map.setCenter(new window.qq.maps.LatLng(lat, lng)); - map.setZoom(16); + console.log("选择搜索结果:", result); - // 更新标记点 - if (markerRef.current) { - markerRef.current.setMap(null); + // 移动地图中心 + map.setCenter(new window.TMap.LatLng(lat, lng)); + map.setZoom(16); + + // 更新标记点 + if (markerRef.current) { + markerRef.current.setMap(null); + markerRef.current = null; + } + + const newMarker = new window.TMap.MultiMarker({ + id: "marker-layer", + map: map, + geometries: [ + { + id: "selected-poi", + styleId: "marker", + position: new window.TMap.LatLng(lat, lng), + properties: { + title: result.title, + }, + }, + ], + }); + + markerRef.current = newMarker; + + // 设置选中的位置信息 + setSelectedLocation({ + x: lng.toString(), + y: lat.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 newMarker = new window.qq.maps.Marker({ - position: new window.qq.maps.LatLng(lat, lng), - map: map, - }); - - markerRef.current = newMarker; - - // 设置选中的位置信息 - setSelectedLocation({ - x: lng.toString(), - y: lat.toString(), - scale: "16", - label: result.address || result.title, - poiname: result.title || "", - maptype: "0", - poiid: result.id || "", - }); - - // 清空搜索结果 - setSearchResults([]); - setSearchValue(""); }; // 确认选择 const handleConfirm = () => { - if (!selectedLocation) { - message.warning("请先选择位置"); - return; - } + try { + if (!selectedLocation) { + message.warning("请先选择位置"); + return; + } - // 生成XML格式的位置信息 - const locationXml = ` = ({ maptype="${selectedLocation.maptype}" poiid="${selectedLocation.poiid}" />`; - // 如果有onConfirm回调,调用它 - if (onConfirm) { - onConfirm(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); + + // 发送消息到服务器 + 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("发送位置信息时出错,请重试"); } - - // 如果有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); - - // 发送消息到服务器 - 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(); }; // 关闭弹窗 diff --git a/Touchkebao/腾讯地图定位服务修复说明.md b/Touchkebao/腾讯地图定位服务修复说明.md new file mode 100644 index 000000000..601db72f6 --- /dev/null +++ b/Touchkebao/腾讯地图定位服务修复说明.md @@ -0,0 +1,73 @@ +# 腾讯地图定位服务修复说明 + +## 问题描述 + +在 `selectMap.tsx` 文件中使用腾讯地图定位服务时出现以下错误: + +``` +TypeError: window.TMap.service.Location is not a constructor + at selectMap.tsx:121:33 +``` + +## 原因分析 + +错误原因是尝试将 `TMap.service.Location` 作为构造函数使用,但在腾讯地图 GL API 中,定位服务不是通过构造函数方式创建的。 + +## 修复方法 + +### 1. 修改定位服务的初始化方式 + +将原来的代码: + +```typescript +// 创建IP定位服务 +window.geolocationRef = new window.TMap.service.Location({ + timeout: 10000, + convert: true, +}); +``` + +修改为: + +```typescript +// 使用腾讯地图内置的定位服务 +window.geolocationRef = window.TMap.service.Geolocation; +``` + +### 2. 修改定位服务的调用方式 + +在调用定位服务时,将配置参数直接传入 `getLocation` 方法: + +```typescript +window.geolocationRef.getLocation({ + timeout: 10000, + convert: true, + success: function (result: any) { + // 处理成功回调 + }, + error: function () { + // 处理错误回调 + }, +}); +``` + +## 技术说明 + +1. **腾讯地图 GL API 中的定位服务**: + - 正确的服务名称是 `TMap.service.Geolocation`,而非 `TMap.service.Location` + - 它是一个对象,不需要使用 `new` 关键字实例化 + - 配置参数应该直接传递给 `getLocation` 方法 + +2. **定位服务参数**: + - `timeout`:定位超时时间,单位毫秒 + - `convert`:是否将坐标转换为腾讯地图坐标系 + +3. **回调处理**: + - `success`:定位成功回调函数,返回位置信息 + - `error`:定位失败回调函数 + +## 注意事项 + +1. 确保腾讯地图 SDK 已正确加载 +2. 确保 API 密钥有定位服务的权限 +3. 定位精度可能受网络环境影响 From ab2ee050def8d64474a52d351f7b601970c0e570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Sat, 22 Nov 2025 11:53:54 +0800 Subject: [PATCH 15/49] =?UTF-8?q?=E4=BC=98=E5=8C=96SelectMap=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=EF=BC=8C=E6=9B=B4=E6=96=B0IP=E5=AE=9A=E4=BD=8D?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E7=9A=84=E5=AE=9E=E7=8E=B0=EF=BC=8C=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91?= =?UTF-8?q?=EF=BC=8C=E7=A1=AE=E4=BF=9D=E7=94=A8=E6=88=B7=E4=BD=8D=E7=BD=AE?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E6=9B=B4=E5=8A=A0=E5=87=86=E7=A1=AE=E5=92=8C?= =?UTF-8?q?=E7=A8=B3=E5=AE=9A=EF=BC=8C=E5=90=8C=E6=97=B6=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F=E4=BB=A5=E6=8F=90=E5=8D=87=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E4=BD=93=E9=AA=8C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/selectMap.module.scss | 6 +++- .../MessageEnter/components/selectMap.tsx | 28 +++++++++---------- 2 files changed, 19 insertions(+), 15 deletions(-) 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 index f0a77bb3d..b8f657244 100644 --- 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 @@ -8,10 +8,13 @@ .searchArea { flex-shrink: 0; position: relative; + z-index: 10000; } .searchInput { width: 100%; + position: relative; + z-index: 10000; } .searchResults { @@ -19,7 +22,7 @@ top: 100%; left: 0; right: 0; - z-index: 1000; + z-index: 10001; background: #fff; border: 1px solid #e8e8e8; border-radius: 4px; @@ -27,6 +30,7 @@ margin-top: 4px; max-height: 300px; overflow-y: auto; + pointer-events: auto; :global(.ant-list-item) { cursor: pointer; 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 index d09316cec..7df7fdfc6 100644 --- 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 @@ -8,7 +8,7 @@ import styles from "./selectMap.module.scss"; declare global { interface Window { TMap: any; - geolocationRef: any; // 全局IP定位服务引用 + geolocationRef: any; // 全局IP定位服务引用(TMap.service.IPLocation实例) } } @@ -117,8 +117,8 @@ const SelectMap: React.FC = ({ // 创建地理编码服务(用于反向地理编码) geocoderRef.current = new window.TMap.service.Geocoder(); - // 使用腾讯地图内置的定位服务 - window.geolocationRef = window.TMap.service.Geolocation; + // 创建IP定位服务 + window.geolocationRef = new window.TMap.service.IPLocation(); // 创建搜索建议服务 suggestServiceRef.current = new window.TMap.service.Suggestion({ @@ -327,13 +327,13 @@ const SelectMap: React.FC = ({ setIsLocating(true); try { if (window.geolocationRef) { - window.geolocationRef.getLocation({ - timeout: 10000, - convert: true, - success: function (result: any) { + window.geolocationRef + .locate() + .then((result: any) => { setIsLocating(false); - if (result && result.location) { - const { lat, lng } = result.location; + console.log("IP定位结果:", result); + if (result && result.result && result.result.location) { + const { lat, lng } = result.result.location; message.info("已定位到您的大致位置"); initializeUserLocation(lat, lng, false); } else { @@ -342,16 +342,16 @@ const SelectMap: React.FC = ({ // 使用默认位置(北京市) initializeUserLocation(39.908823, 116.39747, true); } - }, - error: function () { + }) + .catch((error: any) => { setIsLocating(false); + console.error("IP定位失败:", error); message.info("无法获取您的位置,已定位到北京"); // 使用默认位置(北京市) initializeUserLocation(39.908823, 116.39747, true); - }, - }); + }); } else { - // 地理编码服务未初始化:使用默认位置 + // IP定位服务未初始化:使用默认位置 setIsLocating(false); message.info("无法获取您的位置,已定位到北京"); // 使用默认位置(北京市) From 43b60a0049696bb9b3ed3ddd25d74d561be273f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Sat, 22 Nov 2025 14:16:40 +0800 Subject: [PATCH 16/49] =?UTF-8?q?=E4=BC=98=E5=8C=96SelectMap=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=EF=BC=8C=E5=A2=9E=E5=BC=BATMap=20API=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E5=92=8C=E5=88=9D=E5=A7=8B=E5=8C=96=E9=80=BB=E8=BE=91?= =?UTF-8?q?=EF=BC=8C=E6=B7=BB=E5=8A=A0API=E5=8F=AF=E7=94=A8=E6=80=A7?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=EF=BC=8C=E6=94=B9=E8=BF=9B=E6=A0=87=E8=AE=B0?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F=E5=88=9B=E5=BB=BA=E5=92=8C=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E5=A4=84=E7=90=86=EF=BC=8C=E6=8F=90=E5=8D=87=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E4=BD=93=E9=AA=8C=E5=92=8C=E7=A8=B3=E5=AE=9A=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MessageEnter/components/selectMap.tsx | 793 ++++++++++++------ 1 file changed, 547 insertions(+), 246 deletions(-) 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 index 7df7fdfc6..71de2273a 100644 --- 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 @@ -70,7 +70,17 @@ const SelectMap: React.FC = ({ useEffect(() => { // 检查TMap是否已经加载 if (window.TMap) { - setTmapLoaded(true); + // 等待 API 完全初始化 + const checkAPIReady = () => { + if (window.TMap && window.TMap.Map) { + console.log("腾讯地图SDK已加载,API 可用"); + setTmapLoaded(true); + } else { + // 如果 API 还未完全初始化,等待一段时间后重试 + setTimeout(checkAPIReady, 100); + } + }; + checkAPIReady(); return; } @@ -80,8 +90,19 @@ const SelectMap: React.FC = ({ "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加载成功"); - setTmapLoaded(true); + 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加载失败"); @@ -97,220 +118,359 @@ const SelectMap: React.FC = ({ }; }, []); + // 检查 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, + }); - try { - // 创建地图实例 - const center = new window.TMap.LatLng(39.908823, 116.39747); // 默认北京 - const mapInstance = new window.TMap.Map(mapContainerRef.current, { - center: center, - zoom: 13, - rotation: 0, - pitch: 0, - }); + // 检查容器尺寸,确保容器有有效的宽高 + const checkContainerSize = () => { + if (!mapContainerRef.current) return false; + const rect = mapContainerRef.current.getBoundingClientRect(); + return rect.width > 0 && rect.height > 0; + }; - setMap(mapInstance); + let mapInstance: any = null; + let handleMapClickFn: ((evt: any) => void) | null = null; + let delayTimer: NodeJS.Timeout | null = null; + let isMounted = true; // 标记弹窗是否仍然打开 - // 创建地理编码服务(用于反向地理编码) - geocoderRef.current = new window.TMap.service.Geocoder(); + // 初始化地图函数(使用箭头函数避免函数声明位置问题) + const initializeMap = () => { + if (!mapContainerRef.current) return; - // 创建IP定位服务 - window.geolocationRef = new window.TMap.service.IPLocation(); - - // 创建搜索建议服务 - suggestServiceRef.current = new window.TMap.service.Suggestion({ - pageSize: 10, - autoExtend: true, - }); - - // 地图点击事件处理函数 - const handleMapClick = (evt: any) => { - try { - 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 newMarker = new window.TMap.MultiMarker({ - id: "marker-layer", - map: mapInstance, - geometries: [ - { - id: "selected-marker", - styleId: "marker", - position: new window.TMap.LatLng(lat, lng), - properties: { - title: "选中位置", - }, - }, - ], - }); - - markerRef.current = newMarker; - - // 设置基本位置信息(防止白屏) - setSelectedLocation({ - x: lng.toString(), - y: lat.toString(), - scale: "16", - label: `${lat.toFixed(6)}, ${lng.toFixed(6)}`, - poiname: "选中位置", - maptype: "0", - poiid: "", - }); - - // 反向地理编码获取地址 - setIsReverseGeocoding(true); - geocoderRef.current - .getAddress({ location: new window.TMap.LatLng(lat, lng) }) - .then((result: any) => { - 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.toFixed(6)}, ${lng.toFixed(6)}`; - } - - setSelectedLocation({ - x: lng.toString(), - y: lat.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) => { - setIsReverseGeocoding(false); - console.error("反向地理编码错误:", error); - message.warning("获取详细地址信息失败,将使用坐标显示"); - }); - } catch (error) { - console.error("地图点击处理错误:", error); - message.error("处理地图点击时出错,请重试"); - } - }; - - // 绑定地图点击事件 - mapInstance.on("click", handleMapClick); - - // 使用腾讯地图API初始化用户位置 - const initializeUserLocation = ( - lat: number, - lng: number, - isDefault: boolean = false, - ) => { - console.log(isDefault ? "使用默认位置:" : "用户位置:", lat, lng); - - // 移动地图中心到位置 - const userLocation = new window.TMap.LatLng(lat, lng); - mapInstance.setCenter(userLocation); - mapInstance.setZoom(16); - - // 添加标记点 - if (markerRef.current) { - markerRef.current.setMap(null); - markerRef.current = null; + try { + // 再次检查容器尺寸 + const rect = mapContainerRef.current.getBoundingClientRect(); + if (rect.width <= 0 || rect.height <= 0) { + console.error("地图容器尺寸无效:", rect); + message.error("地图容器尺寸无效,请刷新页面重试"); + return; } - const newMarker = new window.TMap.MultiMarker({ - id: "marker-layer", - map: mapInstance, - geometries: [ - { - id: "user-location", - styleId: "marker", - position: userLocation, - properties: { - title: isDefault ? "默认位置" : "当前位置", - }, - }, - ], + // 创建地图实例 + 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, }); - markerRef.current = newMarker; + setMap(mapInstance); - // 使用腾讯地图服务获取该位置的地址信息 - setIsReverseGeocoding(true); - geocoderRef.current - .getAddress({ location: userLocation }) - .then((result: any) => { - setIsReverseGeocoding(false); - if (result && result.result) { - const resultData = result.result; - const formattedAddresses = resultData.formatted_addresses || {}; - const addressComponent = resultData.address_component || {}; + // 创建地理编码服务(用于反向地理编码) + geocoderRef.current = new window.TMap.service.Geocoder(); - const addressLabel = - formattedAddresses.recommend || - formattedAddresses.rough || - resultData.address || - `${lat.toFixed(6)}, ${lng.toFixed(6)}`; + // 创建IP定位服务 + window.geolocationRef = new window.TMap.service.IPLocation(); - setSelectedLocation({ - x: lng.toString(), - y: lat.toString(), - scale: "16", - label: addressLabel, - poiname: - addressComponent.street || - (isDefault ? "默认位置" : "当前位置"), - maptype: "0", - poiid: resultData.poi_id || "", - }); + // 创建搜索建议服务 + suggestServiceRef.current = new window.TMap.service.Suggestion({ + pageSize: 10, + autoExtend: true, + }); + + // 地图点击事件处理函数 + handleMapClickFn = (evt: any) => { + try { + // 检查弹窗是否仍然打开,以及必要的API是否可用 + if (!isMounted || !mapInstance || !mapContainerRef.current) { + return; } - }) - .catch((error: any) => { - setIsReverseGeocoding(false); - console.error("获取地址信息失败:", error); - // 即使获取地址失败,也设置基本的位置信息 + + // 检查 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; + + // 设置基本位置信息(防止白屏) + setSelectedLocation({ + x: lng.toString(), + y: lat.toString(), + scale: "16", + label: `${lat.toFixed(6)}, ${lng.toFixed(6)}`, + 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.toFixed(6)}, ${lng.toFixed(6)}`; + } + + setSelectedLocation({ + x: lng.toString(), + y: lat.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); + // 即使创建标记失败,也设置基本的位置信息 setSelectedLocation({ x: lng.toString(), y: lat.toString(), @@ -320,63 +480,186 @@ const SelectMap: React.FC = ({ maptype: "0", poiid: "", }); - }); - }; + return; + } - // 使用腾讯地图IP定位获取用户位置 - setIsLocating(true); - try { - if (window.geolocationRef) { - window.geolocationRef - .locate() + // 使用腾讯地图服务获取该位置的地址信息 + if (!isMounted || !geocoderRef.current || !userLocation) { + return; + } + + setIsReverseGeocoding(true); + geocoderRef.current + .getAddress({ location: userLocation }) .then((result: any) => { - 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); + // 检查弹窗是否仍然打开 + 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.toFixed(6)}, ${lng.toFixed(6)}`; + + setSelectedLocation({ + x: lng.toString(), + y: lat.toString(), + scale: "16", + label: addressLabel, + poiname: + addressComponent.street || + (isDefault ? "默认位置" : "当前位置"), + maptype: "0", + poiid: resultData.poi_id || "", + }); } }) .catch((error: any) => { - setIsLocating(false); - console.error("IP定位失败:", error); - message.info("无法获取您的位置,已定位到北京"); - // 使用默认位置(北京市) - initializeUserLocation(39.908823, 116.39747, true); + // 检查弹窗是否仍然打开 + if (!isMounted) { + return; + } + setIsReverseGeocoding(false); + console.error("获取地址信息失败:", error); + // 即使获取地址失败,也设置基本的位置信息 + setSelectedLocation({ + x: lng.toString(), + y: lat.toString(), + scale: "16", + label: `${lat.toFixed(6)}, ${lng.toFixed(6)}`, + poiname: isDefault ? "默认位置" : "当前位置", + maptype: "0", + poiid: "", + }); }); - } else { - // IP定位服务未初始化:使用默认位置 - setIsLocating(false); - message.info("无法获取您的位置,已定位到北京"); - // 使用默认位置(北京市) - initializeUserLocation(39.908823, 116.39747, true); + }; + + // 使用腾讯地图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); + console.error("初始化地图时出错:", error); + message.error("地图加载失败,请刷新页面重试"); setIsLocating(false); - message.error("定位服务出现异常,已定位到北京"); - // 使用默认位置(北京市) - initializeUserLocation(39.908823, 116.39747, true); + } + }; + + // 使用 requestAnimationFrame 确保容器尺寸正确后再初始化 + const initTimer = requestAnimationFrame(() => { + // 再次检查容器尺寸 + if (!checkContainerSize()) { + console.log("容器尺寸无效,延迟初始化地图"); + delayTimer = setTimeout(() => { + if (checkContainerSize() && mapContainerRef.current) { + initializeMap(); + } else { + console.error("地图容器尺寸仍然无效"); + message.error("地图容器初始化失败,请刷新页面重试"); + } + }, 100); + return; } - return () => { - // 清理地图事件监听 - if (mapInstance) { - mapInstance.off("click", handleMapClick); + // 容器尺寸有效,立即初始化 + initializeMap(); + }); + + // 清理函数 + return () => { + // 标记弹窗已关闭 + isMounted = false; + // 取消 requestAnimationFrame + cancelAnimationFrame(initTimer); + // 清理延迟定时器 + if (delayTimer) { + clearTimeout(delayTimer); + } + // 清理地图事件监听 + if (mapInstance && handleMapClickFn) { + try { + mapInstance.off("click", handleMapClickFn); + } catch (error) { + console.error("清理地图事件监听失败:", error); } - }; - } catch (error) { - console.error("初始化地图时出错:", error); - message.error("地图加载失败,请刷新页面重试"); - setIsLocating(false); - } + } + // 清理地图实例 + 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]); @@ -445,6 +728,13 @@ const SelectMap: React.FC = ({ return; } + // 检查 TMap API 是否可用 + if (!checkTMapAPI()) { + console.error("TMap API 不可用,无法创建标记点"); + message.error("地图API不可用,请刷新页面重试"); + return; + } + const lat = result.location.lat; const lng = result.location.lng; @@ -460,9 +750,20 @@ const SelectMap: React.FC = ({ 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", From deac306f699eda2bd5e008cfa1c2e1eac81f446b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Sat, 22 Nov 2025 15:57:16 +0800 Subject: [PATCH 17/49] =?UTF-8?q?=E4=BF=AE=E6=AD=A3SelectMap=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E4=B8=AD=E7=9A=84=E7=BB=8F=E7=BA=AC=E5=BA=A6=E6=A0=87?= =?UTF-8?q?=E8=AE=B0=E9=A1=BA=E5=BA=8F=EF=BC=8C=E6=9B=B4=E6=96=B0XML?= =?UTF-8?q?=E7=94=9F=E6=88=90=E9=80=BB=E8=BE=91=E4=BB=A5=E7=A1=AE=E4=BF=9D?= =?UTF-8?q?=E7=89=B9=E6=AE=8A=E5=AD=97=E7=AC=A6=E8=BD=AC=E4=B9=89=EF=BC=8C?= =?UTF-8?q?=E6=8F=90=E5=8D=87=E5=9C=B0=E5=9B=BE=E4=BD=8D=E7=BD=AE=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E7=9A=84=E5=87=86=E7=A1=AE=E6=80=A7=E5=92=8C=E7=A8=B3?= =?UTF-8?q?=E5=AE=9A=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MessageEnter/components/selectMap.tsx | 93 +++++++++++++------ 1 file changed, 63 insertions(+), 30 deletions(-) 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 index 71de2273a..093b30670 100644 --- 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 @@ -34,8 +34,8 @@ interface SearchResult { } interface LocationData { - x: string; // 经度 - y: string; // 纬度 + x: string; // 纬度 + y: string; // 经度 scale: string; // 缩放级别 label: string; // 地址标签 poiname: string; // POI名称 @@ -66,6 +66,17 @@ const SelectMap: React.FC = ({ 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是否已经加载 @@ -311,11 +322,12 @@ const SelectMap: React.FC = ({ markerRef.current = newMarker; // 设置基本位置信息(防止白屏) + // 经纬度格式化为6位小数(微信位置消息标准格式) setSelectedLocation({ - x: lng.toString(), - y: lat.toString(), + x: lat.toString(), + y: lng.toString(), scale: "16", - label: `${lat.toFixed(6)}, ${lng.toFixed(6)}`, + label: `${lat}, ${lng}`, poiname: "选中位置", maptype: "0", poiid: "", @@ -368,12 +380,13 @@ const SelectMap: React.FC = ({ } if (!addressLabel) { - addressLabel = `${lat.toFixed(6)}, ${lng.toFixed(6)}`; + addressLabel = `${lat}, ${lng}`; } + // 经纬度格式化为6位小数(微信位置消息标准格式) setSelectedLocation({ - x: lng.toString(), - y: lat.toString(), + x: lat.toString(), + y: lng.toString(), scale: "16", label: addressLabel, poiname: addressComponent.street || "未知位置", @@ -471,11 +484,12 @@ const SelectMap: React.FC = ({ } catch (error) { console.error("创建标记点失败:", error); // 即使创建标记失败,也设置基本的位置信息 + // 经纬度格式化为6位小数(微信位置消息标准格式) setSelectedLocation({ - x: lng.toString(), - y: lat.toString(), + x: lat.toString(), + y: lng.toString(), scale: "16", - label: `${lat.toFixed(6)}, ${lng.toFixed(6)}`, + label: `${lat}, ${lng}`, poiname: isDefault ? "默认位置" : "当前位置", maptype: "0", poiid: "", @@ -507,11 +521,12 @@ const SelectMap: React.FC = ({ formattedAddresses.recommend || formattedAddresses.rough || resultData.address || - `${lat.toFixed(6)}, ${lng.toFixed(6)}`; + `${lat}, ${lng}`; + // 经纬度格式化为6位小数(微信位置消息标准格式) setSelectedLocation({ - x: lng.toString(), - y: lat.toString(), + x: lat.toString(), + y: lng.toString(), scale: "16", label: addressLabel, poiname: @@ -530,11 +545,12 @@ const SelectMap: React.FC = ({ setIsReverseGeocoding(false); console.error("获取地址信息失败:", error); // 即使获取地址失败,也设置基本的位置信息 + // 经纬度格式化为6位小数(微信位置消息标准格式) setSelectedLocation({ - x: lng.toString(), - y: lat.toString(), + x: lat.toString(), + y: lng.toString(), scale: "16", - label: `${lat.toFixed(6)}, ${lng.toFixed(6)}`, + label: `${lat}, ${lng}`, poiname: isDefault ? "默认位置" : "当前位置", maptype: "0", poiid: "", @@ -779,9 +795,10 @@ const SelectMap: React.FC = ({ markerRef.current = newMarker; // 设置选中的位置信息 + // 经纬度格式化为6位小数(微信位置消息标准格式) setSelectedLocation({ - x: lng.toString(), - y: lat.toString(), + x: lat.toString(), + y: lng.toString(), scale: "16", label: result.address || result.title, poiname: result.title || "", @@ -806,16 +823,31 @@ const SelectMap: React.FC = ({ return; } - // 生成XML格式的位置信息 - const locationXml = ``; + // 转义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) { @@ -850,6 +882,7 @@ const SelectMap: React.FC = ({ }; addMessage(localMessage); + console.log(locationXml); // 发送消息到服务器 sendCommand("CmdSendMessage", { @@ -971,7 +1004,7 @@ const SelectMap: React.FC = ({ {selectedLocation.label || selectedLocation.poiname}
    - 经度: {selectedLocation.x}, 纬度: {selectedLocation.y} + 经度: {selectedLocation.y}, 纬度: {selectedLocation.x}
    )} From bda57a84e862ff4572f84222e8ba1d533798d3fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Sat, 22 Nov 2025 16:15:33 +0800 Subject: [PATCH 18/49] =?UTF-8?q?=E4=BC=98=E5=8C=96LocationMessage?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=EF=BC=8C=E6=B7=BB=E5=8A=A0=E7=BB=8F=E7=BA=AC?= =?UTF-8?q?=E5=BA=A6=E6=A0=BC=E5=BC=8F=E5=8C=96=E5=8A=9F=E8=83=BD=EF=BC=8C?= =?UTF-8?q?=E7=94=9F=E6=88=90=E9=9D=99=E6=80=81=E5=9C=B0=E5=9B=BE=E9=A2=84?= =?UTF-8?q?=E8=A7=88=E5=9B=BEURL=EF=BC=8C=E6=94=B9=E8=BF=9B=E4=BD=8D?= =?UTF-8?q?=E7=BD=AE=E4=BF=A1=E6=81=AF=E5=B1=95=E7=A4=BA=EF=BC=8C=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA=E7=94=A8=E6=88=B7=E4=BD=93=E9=AA=8C=E5=92=8C=E8=A7=86?= =?UTF-8?q?=E8=A7=89=E6=95=88=E6=9E=9C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LocationMessage.module.scss | 76 ++++++++++++++- .../components/LocationMessage/index.tsx | 92 +++++++++++++------ 2 files changed, 137 insertions(+), 31 deletions(-) diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/LocationMessage/LocationMessage.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/LocationMessage/LocationMessage.module.scss index 61ef47744..99bdc3e43 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/LocationMessage/LocationMessage.module.scss +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/LocationMessage/LocationMessage.module.scss @@ -9,7 +9,7 @@ // 位置消息基础样式 .locationMessage { - max-width: 420px; + width: 420px; margin: 4px 0; } @@ -21,6 +21,8 @@ cursor: pointer; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); overflow: hidden; + display: flex; + flex-direction: column; &:hover { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); @@ -33,6 +35,45 @@ } } +// 地图预览区域 +.mapPreview { + position: relative; + width: 100%; + height: 200px; + overflow: hidden; + background: #f5f5f5; + display: flex; + align-items: center; + justify-content: center; +} + +.mapImage { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} + +.mapPlaceholder { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: none; + flex-direction: column; + align-items: center; + justify-content: center; + background: #f5f5f5; + color: #999; + font-size: 14px; + gap: 8px; + + span:first-child { + font-size: 32px; + } +} + // 位置消息头部 .locationHeader { display: flex; @@ -70,6 +111,21 @@ // 位置消息内容 .locationContent { padding: 12px 16px; + display: flex; + flex-direction: column; + gap: 4px; +} + +.roadName { + font-size: 16px; + font-weight: 600; + color: #1a1a1a; + line-height: 1.4; + display: -webkit-box; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; } .poiName { @@ -89,9 +145,8 @@ font-size: 13px; color: #666; line-height: 1.5; - margin-bottom: 12px; display: -webkit-box; - -webkit-line-clamp: 3; + -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; text-overflow: ellipsis; @@ -175,13 +230,17 @@ // 响应式设计 @media (max-width: 768px) { .locationMessage { - max-width: 280px; + width: 280px; } .locationCard { border-radius: 10px; } + .mapPreview { + height: 150px; + } + .locationHeader { padding: 10px 14px 6px; } @@ -253,6 +312,15 @@ } } + .mapPreview { + background: #2a2a2a; + } + + .mapPlaceholder { + background: #2a2a2a; + color: #999; + } + .locationHeader { background: linear-gradient(135deg, #2a2a2a 0%, #1a1a1a 100%); border-bottom-color: #333; diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/LocationMessage/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/LocationMessage/index.tsx index c2d6d8a43..4456f3e8b 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/LocationMessage/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/components/LocationMessage/index.tsx @@ -85,10 +85,36 @@ const LocationMessage: React.FC = ({ content }) => { return renderErrorMessage("[位置消息 - 解析失败]"); } - // 生成地图链接 + // 格式化经纬度为6位小数 + const formatCoordinate = (coord: string): string => { + const num = parseFloat(coord); + if (isNaN(num)) { + return coord; // 如果无法解析,返回原值 + } + return num.toFixed(6); + }; + + // 生成地图链接(用于点击跳转) const generateMapUrl = (lat: string, lng: string, label: string) => { + const formattedLat = formatCoordinate(lat); + const formattedLng = formatCoordinate(lng); // 使用腾讯地图链接 - return `https://apis.map.qq.com/uri/v1/marker?marker=coord:${lat},${lng};title:${encodeURIComponent(label)}&referer=wechat`; + return `https://apis.map.qq.com/uri/v1/marker?marker=coord:${formattedLng},${formattedLat};title:${encodeURIComponent(label)}&referer=wechat`; + }; + + // 生成静态地图预览图URL + const generateStaticMapUrl = ( + lat: string, + lng: string, + width: number = 420, + height: number = 200, + ) => { + const formattedLat = formatCoordinate(lat); + const formattedLng = formatCoordinate(lng); + const key = "7DZBZ-ZSRK3-QJN3W-O5VTV-4E2P6-7GFYX"; + const zoom = locationData.scale || "15"; + // 腾讯地图静态地图API + return `https://apis.map.qq.com/ws/staticmap/v2/?center=${formattedLng},${formattedLat}&zoom=${zoom}&size=${width}x${height}&markers=${formattedLng},${formattedLat}&key=${key}`; }; const mapUrl = generateMapUrl( @@ -97,12 +123,18 @@ const LocationMessage: React.FC = ({ content }) => { locationData.label, ); + const staticMapUrl = generateStaticMapUrl( + locationData.y, + locationData.x, + 420, + 200, + ); + // 处理POI信息 - const poiName = locationData.poiname || locationData.label; - const poiCategory = locationData.poiCategoryTips - ? locationData.poiCategoryTips.split(":")[0] - : ""; - const poiPhone = locationData.poiPhone || ""; + // 提取道路名称(如果有的话,从label中提取) + const roadName = + locationData.poiname.split(/[((]/)[0] || locationData.label; + const detailAddress = locationData.label; return (
    @@ -110,29 +142,35 @@ const LocationMessage: React.FC = ({ content }) => { className={styles.locationCard} onClick={() => window.open(mapUrl, "_blank")} > - {/* 位置详情 */} + {/* 地图预览图 */} +
    + {locationData.label} { + // 如果图片加载失败,显示占位符 + const target = e.target as HTMLImageElement; + target.style.display = "none"; + const placeholder = target.nextElementSibling as HTMLElement; + if (placeholder) { + placeholder.style.display = "flex"; + } + }} + /> +
    + 📍 + 地图加载中... +
    +
    + + {/* 位置信息 */}
    - {/* POI名称 */} - {poiName &&
    {poiName}
    } + {/* 道路名称 */} + {roadName &&
    {roadName}
    } {/* 详细地址 */} -
    {locationData.label}
    - - {/* POI分类和电话 */} -
    - {poiCategory && ( -
    - 🏷️ - {poiCategory} -
    - )} - {poiPhone && ( -
    - 📞 - {poiPhone} -
    - )} -
    +
    {detailAddress}
    From 1786dec900ec91a28719fc7ade31e1c45745da67 Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Mon, 24 Nov 2025 10:13:30 +0800 Subject: [PATCH 19/49] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/WebSocketController.php | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Server/application/api/controller/WebSocketController.php b/Server/application/api/controller/WebSocketController.php index 773cbb3dd..5b714997a 100644 --- a/Server/application/api/controller/WebSocketController.php +++ b/Server/application/api/controller/WebSocketController.php @@ -457,7 +457,7 @@ class WebSocketController extends BaseController // 构建请求参数 $params = [ - "cmdType" => 'CmdDownloadMomentImagesResult', + "cmdType" => 'CmdDownloadMomentImages', "snsId" => $data['snsId'], "urls" => $data['snsUrls'], "wechatAccountId" => $data['wechatAccountId'], @@ -558,15 +558,15 @@ 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'])){ + $snsData = [ + 'snsId' => $moment['snsId'], + 'snsUrls' => $momentEntity['urls'], + 'wechatAccountId' => $wechatAccountId, + ]; + $this->getMomentSourceRealUrl($snsData); + } } //Log::write('朋友圈数据已存入数据库,共' . count($momentList) . '条'); From 30b33ec974059ede3ee8f6dcd4fef64ef2f5f994 Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Mon, 24 Nov 2025 11:03:31 +0800 Subject: [PATCH 20/49] =?UTF-8?q?=E6=9C=8B=E5=8F=8B=E5=9C=88=E9=87=87?= =?UTF-8?q?=E9=9B=86=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/WebSocketController.php | 12 ++++-------- .../cunkebao/controller/ContentLibraryController.php | 8 ++++---- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/Server/application/api/controller/WebSocketController.php b/Server/application/api/controller/WebSocketController.php index 5b714997a..b9b8a8604 100644 --- a/Server/application/api/controller/WebSocketController.php +++ b/Server/application/api/controller/WebSocketController.php @@ -463,16 +463,10 @@ class WebSocketController extends BaseController "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,8 +552,10 @@ class WebSocketController extends BaseController $dataToSave['create_time'] = time(); $res = WechatMoments::create($dataToSave); } + + // 获取资源链接 - if(empty($momentEntity['resUrls']) && !empty($momentEntity['urls'])){ + if(empty($momentEntity['resUrls']) && !empty($momentEntity['urls']) && $moment['type'] == 1) { $snsData = [ 'snsId' => $moment['snsId'], 'snsUrls' => $momentEntity['urls'], diff --git a/Server/application/cunkebao/controller/ContentLibraryController.php b/Server/application/cunkebao/controller/ContentLibraryController.php index e4a2f6391..692911232 100644 --- a/Server/application/cunkebao/controller/ContentLibraryController.php +++ b/Server/application/cunkebao/controller/ContentLibraryController.php @@ -1070,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; @@ -1159,7 +1159,7 @@ class ContentLibraryController extends Controller } // 返回采集结果 - return json([ + return json_encode([ 'code' => 200, 'msg' => '采集任务执行完成', 'data' => [ @@ -1169,7 +1169,7 @@ class ContentLibraryController extends Controller 'skipped' => $totalLibraries - $successCount - $failCount, 'results' => $results ] - ]); + ],256); } /** @@ -1206,7 +1206,7 @@ class ContentLibraryController extends Controller ->whereIn('id', $friendIds) ->where('isDeleted', 0) ->select(); - + if (empty($friends)) { return [ 'status' => 'failed', From c801490c2f5bf2350b1b510e3c3db09542b00fa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Mon, 24 Nov 2025 15:37:57 +0800 Subject: [PATCH 21/49] =?UTF-8?q?=E5=88=A0=E9=99=A4=E8=85=BE=E8=AE=AF?= =?UTF-8?q?=E5=9C=B0=E5=9B=BE=E5=AE=9A=E4=BD=8D=E6=9C=8D=E5=8A=A1=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3=EF=BC=8C=E6=9B=B4?= =?UTF-8?q?=E6=96=B0CustomerList=E7=BB=84=E4=BB=B6=E6=A0=B7=E5=BC=8F?= =?UTF-8?q?=E4=BB=A5=E6=94=AF=E6=8C=81=E7=A6=BB=E7=BA=BF=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E7=8A=B6=E6=80=81=EF=BC=8C=E8=B0=83=E6=95=B4MessageList?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E7=9A=84=E5=8A=A0=E8=BD=BD=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E7=AE=A1=E7=90=86=EF=BC=8C=E5=A2=9E=E5=BC=BAfilter=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E4=BB=A5=E6=94=AF=E6=8C=81XML=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE=E6=B6=88=E6=81=AF=E7=9A=84=E8=AF=86=E5=88=AB?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/CustomerList/com.module.scss | 5 ++ .../weChat/components/CustomerList/index.tsx | 6 +- .../SidebarMenu/MessageList/index.tsx | 4 +- Touchkebao/src/utils/filter.ts | 5 ++ Touchkebao/消息功能规划.md | 0 Touchkebao/腾讯地图定位服务修复说明.md | 73 ------------------- 6 files changed, 13 insertions(+), 80 deletions(-) create mode 100644 Touchkebao/消息功能规划.md delete mode 100644 Touchkebao/腾讯地图定位服务修复说明.md 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 9cc2941eb..29e92bf8e 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 9ce88a1f3..55ba297f8 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 2625b5815..0e39a94b4 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/filter.ts b/Touchkebao/src/utils/filter.ts index 5e2d37547..22887a8bd 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/Touchkebao/消息功能规划.md b/Touchkebao/消息功能规划.md new file mode 100644 index 000000000..e69de29bb diff --git a/Touchkebao/腾讯地图定位服务修复说明.md b/Touchkebao/腾讯地图定位服务修复说明.md deleted file mode 100644 index 601db72f6..000000000 --- a/Touchkebao/腾讯地图定位服务修复说明.md +++ /dev/null @@ -1,73 +0,0 @@ -# 腾讯地图定位服务修复说明 - -## 问题描述 - -在 `selectMap.tsx` 文件中使用腾讯地图定位服务时出现以下错误: - -``` -TypeError: window.TMap.service.Location is not a constructor - at selectMap.tsx:121:33 -``` - -## 原因分析 - -错误原因是尝试将 `TMap.service.Location` 作为构造函数使用,但在腾讯地图 GL API 中,定位服务不是通过构造函数方式创建的。 - -## 修复方法 - -### 1. 修改定位服务的初始化方式 - -将原来的代码: - -```typescript -// 创建IP定位服务 -window.geolocationRef = new window.TMap.service.Location({ - timeout: 10000, - convert: true, -}); -``` - -修改为: - -```typescript -// 使用腾讯地图内置的定位服务 -window.geolocationRef = window.TMap.service.Geolocation; -``` - -### 2. 修改定位服务的调用方式 - -在调用定位服务时,将配置参数直接传入 `getLocation` 方法: - -```typescript -window.geolocationRef.getLocation({ - timeout: 10000, - convert: true, - success: function (result: any) { - // 处理成功回调 - }, - error: function () { - // 处理错误回调 - }, -}); -``` - -## 技术说明 - -1. **腾讯地图 GL API 中的定位服务**: - - 正确的服务名称是 `TMap.service.Geolocation`,而非 `TMap.service.Location` - - 它是一个对象,不需要使用 `new` 关键字实例化 - - 配置参数应该直接传递给 `getLocation` 方法 - -2. **定位服务参数**: - - `timeout`:定位超时时间,单位毫秒 - - `convert`:是否将坐标转换为腾讯地图坐标系 - -3. **回调处理**: - - `success`:定位成功回调函数,返回位置信息 - - `error`:定位失败回调函数 - -## 注意事项 - -1. 确保腾讯地图 SDK 已正确加载 -2. 确保 API 密钥有定位服务的权限 -3. 定位精度可能受网络环境影响 From 5a52b56d895789ca7dcee163a306501a0d0d0aa3 Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Mon, 24 Nov 2025 17:18:49 +0800 Subject: [PATCH 22/49] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/model/WechatAccountModel.php | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/Server/application/api/model/WechatAccountModel.php b/Server/application/api/model/WechatAccountModel.php index 919da478e..001d3c631 100644 --- a/Server/application/api/model/WechatAccountModel.php +++ b/Server/application/api/model/WechatAccountModel.php @@ -8,26 +8,4 @@ class WechatAccountModel extends Model { // 设置表名 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 From cd411906630f4108f64dad267a0f4150ead769e3 Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Tue, 25 Nov 2025 14:52:46 +0800 Subject: [PATCH 23/49] =?UTF-8?q?=E5=A5=BD=E5=8F=8B=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Server/application/chukebao/config/route.php | 2 + .../controller/WechatFriendController.php | 156 ++++++++++++++++++ 2 files changed, 158 insertions(+) diff --git a/Server/application/chukebao/config/route.php b/Server/application/chukebao/config/route.php index 289e6fb0d..56e17f519 100644 --- a/Server/application/chukebao/config/route.php +++ b/Server/application/chukebao/config/route.php @@ -14,6 +14,8 @@ Route::group('v1/', function () { Route::get('list', 'app\chukebao\controller\WechatFriendController@getList'); // 获取好友列表 Route::get('detail', 'app\chukebao\controller\WechatFriendController@getDetail'); // 获取好友详情 Route::post('updateInfo', 'app\chukebao\controller\WechatFriendController@updateFriendInfo'); // 更新好友资料 + // 添加好友任务记录相关接口 + Route::get('addTaskList', 'app\chukebao\controller\WechatFriendController@getAddTaskList'); // 获取添加好友任务记录列表(包含添加者信息、状态、时间等,支持状态筛选,无需传好友ID) }); //群相关 Route::group('wechatChatroom/', function () { diff --git a/Server/application/chukebao/controller/WechatFriendController.php b/Server/application/chukebao/controller/WechatFriendController.php index 644bd0a90..931d8ce63 100644 --- a/Server/application/chukebao/controller/WechatFriendController.php +++ b/Server/application/chukebao/controller/WechatFriendController.php @@ -166,4 +166,160 @@ class WechatFriendController extends BaseController return ResponseHelper::success(['id' => $friendId]); } + + /** + * 获取添加好友任务记录列表(全新功能) + * 返回当前账号的所有添加好友任务记录,无论是否通过都展示 + * 包含:添加者头像、昵称、微信号、添加状态、添加时间、通过时间等信息 + * @return \think\response\Json + */ + public function getAddTaskList() + { + $page = $this->request->param('page', 1); + $limit = $this->request->param('limit', 10); + $status = $this->request->param('status', ''); // 可选:筛选状态 0执行中,1执行成功,2执行失败 + $accountId = $this->getUserInfo('s2_accountId'); + + if (empty($accountId)) { + return ResponseHelper::error('请先登录'); + } + + // 直接使用operatorAccountId查询添加好友任务记录 + $query = Db::table('s2_friend_task') + ->where('operatorAccountId', $accountId) + ->order('createTime desc'); + + // 如果指定了状态筛选 + if ($status !== '' && $status !== null) { + $query->where('status', $status); + } + + $total = $query->count(); + $tasks = $query->page($page, $limit)->select(); + + // 提取所有任务的phone和wechatId,用于查询好友信息(获取通过时间) + $taskPhones = []; + $taskWechatIds = []; + foreach ($tasks as $task) { + if (!empty($task['phone'])) { + $taskPhones[] = $task['phone']; + } + if (!empty($task['wechatId'])) { + $taskWechatIds[] = $task['wechatId']; + } + } + + // 查询好友信息,获取通过时间 + $friendPassTimeMap = []; + if (!empty($taskPhones) || !empty($taskWechatIds)) { + // 分别通过phone和wechatId查询,确保都能匹配到 + $friendsByPhone = []; + $friendsByWechatId = []; + + if (!empty($taskPhones)) { + $friendsByPhone = Db::table('s2_wechat_friend') + ->where('accountId', $accountId) + ->where('isDeleted', 0) + ->where('phone', 'in', $taskPhones) + ->field('phone,wechatId,passTime,nickname') + ->select(); + } + + if (!empty($taskWechatIds)) { + $friendsByWechatId = Db::table('s2_wechat_friend') + ->where('accountId', $accountId) + ->where('isDeleted', 0) + ->where('wechatId', 'in', $taskWechatIds) + ->field('phone,wechatId,passTime,nickname') + ->select(); + } + + // 合并结果并构建映射表(优先使用phone作为key) + $allFriends = array_merge($friendsByPhone, $friendsByWechatId); + foreach ($allFriends as $friend) { + // 使用phone作为key(如果存在) + if (!empty($friend['phone'])) { + $friendPassTimeMap[$friend['phone']] = [ + 'passTime' => $friend['passTime'] ?? 0, + 'nickname' => $friend['nickname'] ?? '', + ]; + } + // 同时使用wechatId作为key(如果存在且phone为空) + if (!empty($friend['wechatId']) && empty($friend['phone'])) { + $friendPassTimeMap[$friend['wechatId']] = [ + 'passTime' => $friend['passTime'] ?? 0, + 'nickname' => $friend['nickname'] ?? '', + ]; + } + } + } + + // 处理任务数据 + $list = []; + foreach ($tasks as $task) { + $taskKey = !empty($task['phone']) ? $task['phone'] : ($task['wechatId'] ?? ''); + $friendInfo = isset($friendPassTimeMap[$taskKey]) ? $friendPassTimeMap[$taskKey] : null; + + $item = [ + 'taskId' => $task['id'] ?? 0, + 'phone' => $task['phone'] ?? '', + 'wechatId' => $task['wechatId'] ?? '', + // 添加者信息 + 'adder' => [ + 'avatar' => $task['wechatAvatar'] ?? '', // 添加者头像 + 'nickname' => $task['wechatNickname'] ?? '', // 添加者昵称 + 'username' => $task['accountUsername'] ?? '', // 添加者微信号 + 'accountNickname' => $task['accountNickname'] ?? '', // 账号昵称 + 'accountRealName' => $task['accountRealName'] ?? '', // 账号真实姓名 + ], + // 添加状态 + 'status' => [ + 'code' => $task['status'] ?? 0, // 状态码:0执行中,1执行成功,2执行失败 + 'text' => $this->getTaskStatusText($task['status'] ?? 0), // 状态文本 + ], + // 时间信息 + 'time' => [ + 'addTime' => !empty($task['createTime']) ? date('Y-m-d H:i:s', $task['createTime']) : '', // 添加时间 + 'addTimeStamp' => $task['createTime'] ?? 0, // 添加时间戳 + 'updateTime' => !empty($task['updateTime']) ? date('Y-m-d H:i:s', $task['updateTime']) : '', // 更新时间 + 'updateTimeStamp' => $task['updateTime'] ?? 0, // 更新时间戳 + 'passTime' => !empty($friendInfo['passTime']) ? date('Y-m-d H:i:s', $friendInfo['passTime']) : '', // 通过时间 + 'passTimeStamp' => $friendInfo['passTime'] ?? 0, // 通过时间戳 + ], + // 好友信息(如果已通过) + 'friend' => [ + 'nickname' => $friendInfo['nickname'] ?? '', // 好友昵称 + 'isPassed' => !empty($friendInfo['passTime']), // 是否已通过 + ], + // 其他信息 + 'other' => [ + 'msgContent' => $task['msgContent'] ?? '', // 验证消息 + 'remark' => $task['remark'] ?? '', // 备注 + 'from' => $task['from'] ?? '', // 来源 + 'labels' => !empty($task['labels']) ? explode(',', $task['labels']) : [], // 标签 + ] + ]; + + $list[] = $item; + } + + return ResponseHelper::success(['list' => $list, 'total' => $total]); + } + + + /** + * 获取任务状态文本 + * @param int $status 状态码 + * @return string 状态文本 + */ + private function getTaskStatusText($status) + { + $statusMap = [ + 0 => '执行中', + 1 => '执行成功', + 2 => '执行失败', + ]; + + return isset($statusMap[$status]) ? $statusMap[$status] : '未知状态'; + } } \ No newline at end of file From 78de6c92b7debbd5473915d45a9b9492488f4b40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Tue, 25 Nov 2025 15:36:43 +0800 Subject: [PATCH 24/49] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=A5=BD=E5=8F=8B?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BB=BB=E5=8A=A1=E5=88=97=E8=A1=A8API?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96NavCommon=E7=BB=84=E4=BB=B6=EF=BC=8C?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E6=9C=AA=E4=BD=BF=E7=94=A8=E7=9A=84=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91=EF=BC=8C=E7=AE=80?= =?UTF-8?q?=E5=8C=96=E4=BB=A3=E7=A0=81=E7=BB=93=E6=9E=84=EF=BC=8C=E6=8F=90?= =?UTF-8?q?=E5=8D=87=E5=8F=AF=E8=AF=BB=E6=80=A7=E5=92=8C=E7=BB=B4=E6=8A=A4?= =?UTF-8?q?=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pc/ckbox/components/NavCommon/Notice.tsx | 431 ++++++++++++++++++ .../pc/ckbox/components/NavCommon/api.ts | 5 + .../pc/ckbox/components/NavCommon/index.tsx | 211 +-------- 3 files changed, 440 insertions(+), 207 deletions(-) create mode 100644 Touchkebao/src/pages/pc/ckbox/components/NavCommon/Notice.tsx 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 000000000..1249a6d7f --- /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 f814b6b94..dd665a02a 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 fcd3f7a45..dce087d40 100644 --- a/Touchkebao/src/pages/pc/ckbox/components/NavCommon/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/components/NavCommon/index.tsx @@ -1,29 +1,18 @@ -import React, { useState, useEffect } from "react"; -import { - Layout, - Drawer, - Avatar, - Space, - Button, - Badge, - Dropdown, - Empty, - message, -} from "antd"; +import React, { useState } from "react"; +import { Layout, Avatar, Space, Button, Dropdown, message } from "antd"; import { BarChartOutlined, UserOutlined, - BellOutlined, LogoutOutlined, ThunderboltOutlined, SettingOutlined, 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; @@ -32,40 +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")) { @@ -74,48 +35,6 @@ const NavCommon: React.FC = ({ title = "触客宝" }) => { navigate("/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和用户状态 @@ -215,61 +134,6 @@ const NavCommon: React.FC = ({ title = "触客宝" }) => { } }; - // 处理消息已读 - 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); - } - }; - - // 处理全部已读 - const handleReadAll = async () => { - try { - await readAll(); // 这里需要根据实际API调整参数 - // 更新本地状态 - setMessageList(prev => prev.map(item => ({ ...item, isRead: 1 }))); - setMessageCount(0); - } 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", - }); - } - }; - // 用户菜单项 const userMenuItems = [ { @@ -333,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)} - -
    - )} -
    -
    - )) - )} -
    -
    ); }; From cd27506388447f3331d91a091b18e739a32914fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Tue, 25 Nov 2025 15:37:51 +0800 Subject: [PATCH 25/49] cf --- Moncter/数据库/KR.js | 374 ++++++ Moncter/数据库/KR_KR.js | 509 ++++++++ Moncter/数据库/KR_Linkedln.js | 31 + Moncter/数据库/KR_京东.js | 31 + Moncter/数据库/KR_人才库.js | 37 + Moncter/数据库/KR_企业.js | 142 +++ Moncter/数据库/KR_企业名录.js | 115 ++ Moncter/数据库/KR_卡若私域.js | 228 ++++ Moncter/数据库/KR_商城.js | 122 ++ Moncter/数据库/KR_国外.js | 359 ++++++ Moncter/数据库/KR_存客宝.js | 618 +++++++++ Moncter/数据库/KR_存客宝_四表重构KR_KR版.js | 65 + Moncter/数据库/KR_微博.js | 36 + Moncter/数据库/KR_快递.js | 90 ++ Moncter/数据库/KR_户口.js | 46 + Moncter/数据库/KR_手机.js | 186 +++ Moncter/数据库/KR_投资.js | 223 ++++ Moncter/数据库/KR_淘宝.js | 223 ++++ Moncter/数据库/KR_游戏.js | 156 +++ Moncter/数据库/KR_腾讯.js | 1136 +++++++++++++++++ Moncter/数据库/KR_酒店.js | 79 ++ .../KR_销售额3000万元-5000万元企业名录.js | 85 ++ Moncter/数据库/KR_顺丰.js | 91 ++ Moncter/数据库/KR_香港在大陆投资企业名录.js | 217 ++++ Moncter/数据库/KR_魔兽世界.js | 103 ++ 25 files changed, 5302 insertions(+) create mode 100644 Moncter/数据库/KR.js create mode 100644 Moncter/数据库/KR_KR.js create mode 100644 Moncter/数据库/KR_Linkedln.js create mode 100644 Moncter/数据库/KR_京东.js create mode 100644 Moncter/数据库/KR_人才库.js create mode 100644 Moncter/数据库/KR_企业.js create mode 100644 Moncter/数据库/KR_企业名录.js create mode 100644 Moncter/数据库/KR_卡若私域.js create mode 100644 Moncter/数据库/KR_商城.js create mode 100644 Moncter/数据库/KR_国外.js create mode 100644 Moncter/数据库/KR_存客宝.js create mode 100644 Moncter/数据库/KR_存客宝_四表重构KR_KR版.js create mode 100644 Moncter/数据库/KR_微博.js create mode 100644 Moncter/数据库/KR_快递.js create mode 100644 Moncter/数据库/KR_户口.js create mode 100644 Moncter/数据库/KR_手机.js create mode 100644 Moncter/数据库/KR_投资.js create mode 100644 Moncter/数据库/KR_淘宝.js create mode 100644 Moncter/数据库/KR_游戏.js create mode 100644 Moncter/数据库/KR_腾讯.js create mode 100644 Moncter/数据库/KR_酒店.js create mode 100644 Moncter/数据库/KR_销售额3000万元-5000万元企业名录.js create mode 100644 Moncter/数据库/KR_顺丰.js create mode 100644 Moncter/数据库/KR_香港在大陆投资企业名录.js create mode 100644 Moncter/数据库/KR_魔兽世界.js diff --git a/Moncter/数据库/KR.js b/Moncter/数据库/KR.js new file mode 100644 index 000000000..00dabafaf --- /dev/null +++ b/Moncter/数据库/KR.js @@ -0,0 +1,374 @@ +/* + Navicat Premium Data Transfer + + Source Server : 标签数据库 + Source Server Type : MongoDB + Source Server Version : 60025 (6.0.25) + Source Host : 192.168.2.6:27017 + Source Schema : KR + + Target Server Type : MongoDB + Target Server Version : 60025 (6.0.25) + File Encoding : 65001 + + Date: 19/11/2025 15:18:11 +*/ + + +// ---------------------------- +// Collection structure for system.profile +// ---------------------------- +db.getCollection("system.profile").drop(); +db.createCollection("system.profile",{ + capped: true, + size: 1048576 +}); + +// ---------------------------- +// Collection structure for 厦门京东内 +// ---------------------------- +db.getCollection("厦门京东内").drop(); +db.createCollection("厦门京东内"); + +// ---------------------------- +// Collection structure for 厦门用户资产2025年9月 +// ---------------------------- +db.getCollection("厦门用户资产2025年9月").drop(); +db.createCollection("厦门用户资产2025年9月"); + +// ---------------------------- +// Collection structure for 厦门用户资产2025年9月_优化版 +// ---------------------------- +db.getCollection("厦门用户资产2025年9月_优化版").drop(); +db.createCollection("厦门用户资产2025年9月_优化版"); +db.getCollection("厦门用户资产2025年9月_优化版").createIndex({ + user_level: NumberInt("1") +}, { + name: "user_level_1" +}); +db.getCollection("厦门用户资产2025年9月_优化版").createIndex({ + total_score: NumberInt("-1") +}, { + name: "total_score_-1" +}); +db.getCollection("厦门用户资产2025年9月_优化版").createIndex({ + mobile: NumberInt("1") +}, { + name: "mobile_1" +}); +db.getCollection("厦门用户资产2025年9月_优化版").createIndex({ + city: NumberInt("1") +}, { + name: "city_1" +}); +db.getCollection("厦门用户资产2025年9月_优化版").createIndex({ + name: NumberInt("1") +}, { + name: "name_1" +}); + +// ---------------------------- +// Collection structure for 游戏_深度用户有过语音聊天、魔兽等 +// ---------------------------- +db.getCollection("游戏_深度用户有过语音聊天、魔兽等").drop(); +db.createCollection("游戏_深度用户有过语音聊天、魔兽等"); +db.getCollection("游戏_深度用户有过语音聊天、魔兽等").createIndex({ + user_id: NumberInt("1") +}, { + name: "user_id_1" +}); +db.getCollection("游戏_深度用户有过语音聊天、魔兽等").createIndex({ + user_value: NumberInt("1") +}, { + name: "user_value_1" +}); +db.getCollection("游戏_深度用户有过语音聊天、魔兽等").createIndex({ + overlap_count: NumberInt("1") +}, { + name: "overlap_count_1" +}); + +// ---------------------------- +// Collection structure for 用户估值 +// ---------------------------- +db.getCollection("用户估值").drop(); +db.createCollection("用户估值"); +db.getCollection("用户估值").createIndex({ + user_key: NumberInt("1") +}, { + name: "user_key_1", + unique: true +}); +db.getCollection("用户估值").createIndex({ + phone: NumberInt("1") +}, { + name: "phone_1" +}); +db.getCollection("用户估值").createIndex({ + phone_masked: NumberInt("1") +}, { + name: "phone_masked_1" +}); +db.getCollection("用户估值").createIndex({ + email: NumberInt("1") +}, { + name: "email_1" +}); +db.getCollection("用户估值").createIndex({ + name: NumberInt("1") +}, { + name: "name_1" +}); +db.getCollection("用户估值").createIndex({ + province: NumberInt("1"), + city: NumberInt("1") +}, { + name: "province_1_city_1" +}); +db.getCollection("用户估值").createIndex({ + computed_at: NumberInt("-1") +}, { + name: "computed_at_-1" +}); +db.getCollection("用户估值").createIndex({ + source_channels: NumberInt("1") +}, { + name: "source_channels_1" +}); +db.getCollection("用户估值").createIndex({ + "data_quality.completeness": NumberInt("-1") +}, { + name: "data_quality.completeness_-1" +}); +db.getCollection("用户估值").createIndex({ + user_evaluation_score: NumberInt("-1") +}, { + name: "user_evaluation_score_-1" +}); + +// ---------------------------- +// Collection structure for 用户资产整合 +// ---------------------------- +db.getCollection("用户资产整合").drop(); +db.createCollection("用户资产整合"); +db.getCollection("用户资产整合").createIndex({ + user_key: NumberInt("1") +}, { + name: "idx_user_key" +}); +db.getCollection("用户资产整合").createIndex({ + phone: NumberInt("1") +}, { + name: "idx_phone" +}); +db.getCollection("用户资产整合").createIndex({ + consumption_level: NumberInt("1") +}, { + name: "idx_consumption_level" +}); +db.getCollection("用户资产整合").createIndex({ + consumption_score: NumberInt("-1") +}, { + name: "idx_consumption_score_desc" +}); +db.getCollection("用户资产整合").createIndex({ + city: NumberInt("1") +}, { + name: "idx_city" +}); +db.getCollection("用户资产整合").createIndex({ + age_range: NumberInt("1") +}, { + name: "idx_age_range" +}); +db.getCollection("用户资产整合").createIndex({ + user_evaluation_score: NumberInt("-1") +}, { + name: "idx_user_evaluation_score_desc" +}); +db.getCollection("用户资产整合").createIndex({ + consumption_level: NumberInt("1"), + consumption_score: NumberInt("-1") +}, { + name: "idx_level_score" +}); + +// ---------------------------- +// Collection structure for 金融客户_厦门_A级用户 +// ---------------------------- +db.getCollection("金融客户_厦门_A级用户").drop(); +db.createCollection("金融客户_厦门_A级用户"); +db.getCollection("金融客户_厦门_A级用户").createIndex({ + total_score: NumberInt("1") +}, { + name: "total_score_1", + background: true +}); +db.getCollection("金融客户_厦门_A级用户").createIndex({ + "姓名": NumberInt("1") +}, { + name: "姓名_1", + background: true +}); +db.getCollection("金融客户_厦门_A级用户").createIndex({ + "手机": NumberInt("1") +}, { + name: "手机_1", + background: true +}); + +// ---------------------------- +// Collection structure for 金融客户_厦门_B级用户 +// ---------------------------- +db.getCollection("金融客户_厦门_B级用户").drop(); +db.createCollection("金融客户_厦门_B级用户"); +db.getCollection("金融客户_厦门_B级用户").createIndex({ + total_score: NumberInt("1") +}, { + name: "total_score_1", + background: true +}); +db.getCollection("金融客户_厦门_B级用户").createIndex({ + "姓名": NumberInt("1") +}, { + name: "姓名_1", + background: true +}); +db.getCollection("金融客户_厦门_B级用户").createIndex({ + "手机": NumberInt("1") +}, { + name: "手机_1", + background: true +}); + +// ---------------------------- +// Collection structure for 金融客户_厦门_C级用户 +// ---------------------------- +db.getCollection("金融客户_厦门_C级用户").drop(); +db.createCollection("金融客户_厦门_C级用户"); +db.getCollection("金融客户_厦门_C级用户").createIndex({ + total_score: NumberInt("1") +}, { + name: "total_score_1", + background: true +}); +db.getCollection("金融客户_厦门_C级用户").createIndex({ + "姓名": NumberInt("1") +}, { + name: "姓名_1", + background: true +}); +db.getCollection("金融客户_厦门_C级用户").createIndex({ + "手机": NumberInt("1") +}, { + name: "手机_1", + background: true +}); + +// ---------------------------- +// Collection structure for 金融客户_厦门_D级用户 +// ---------------------------- +db.getCollection("金融客户_厦门_D级用户").drop(); +db.createCollection("金融客户_厦门_D级用户"); +db.getCollection("金融客户_厦门_D级用户").createIndex({ + total_score: NumberInt("1") +}, { + name: "total_score_1", + background: true +}); +db.getCollection("金融客户_厦门_D级用户").createIndex({ + "姓名": NumberInt("1") +}, { + name: "姓名_1", + background: true +}); +db.getCollection("金融客户_厦门_D级用户").createIndex({ + "手机": NumberInt("1") +}, { + name: "手机_1", + background: true +}); + +// ---------------------------- +// Collection structure for 金融客户_厦门_E级用户 +// ---------------------------- +db.getCollection("金融客户_厦门_E级用户").drop(); +db.createCollection("金融客户_厦门_E级用户"); +db.getCollection("金融客户_厦门_E级用户").createIndex({ + total_score: NumberInt("1") +}, { + name: "total_score_1", + background: true +}); +db.getCollection("金融客户_厦门_E级用户").createIndex({ + "姓名": NumberInt("1") +}, { + name: "姓名_1", + background: true +}); +db.getCollection("金融客户_厦门_E级用户").createIndex({ + "手机": NumberInt("1") +}, { + name: "手机_1", + background: true +}); + +// ---------------------------- +// Collection structure for 金融客户_厦门_S级用户 +// ---------------------------- +db.getCollection("金融客户_厦门_S级用户").drop(); +db.createCollection("金融客户_厦门_S级用户"); +db.getCollection("金融客户_厦门_S级用户").createIndex({ + total_score: NumberInt("1") +}, { + name: "total_score_1", + background: true +}); +db.getCollection("金融客户_厦门_S级用户").createIndex({ + "姓名": NumberInt("1") +}, { + name: "姓名_1", + background: true +}); +db.getCollection("金融客户_厦门_S级用户").createIndex({ + "手机": NumberInt("1") +}, { + name: "手机_1", + background: true +}); + +// ---------------------------- +// Collection structure for 金融组02250905 +// ---------------------------- +db.getCollection("金融组02250905").drop(); +db.createCollection("金融组02250905"); +db.getCollection("金融组02250905").createIndex({ + "姓名": NumberInt("1") +}, { + name: "姓名_1" +}); +db.getCollection("金融组02250905").createIndex({ + "手机": NumberInt("1") +}, { + name: "手机_1" +}); +db.getCollection("金融组02250905").createIndex({ + "身份证": NumberInt("1") +}, { + name: "身份证_1" +}); +db.getCollection("金融组02250905").createIndex({ + "综合评分": NumberInt("-1") +}, { + name: "综合评分_-1" +}); +db.getCollection("金融组02250905").createIndex({ + "用户等级": NumberInt("1") +}, { + name: "用户等级_1" +}); +db.getCollection("金融组02250905").createIndex({ + "城市": NumberInt("1") +}, { + name: "城市_1" +}); diff --git a/Moncter/数据库/KR_KR.js b/Moncter/数据库/KR_KR.js new file mode 100644 index 000000000..9120b599e --- /dev/null +++ b/Moncter/数据库/KR_KR.js @@ -0,0 +1,509 @@ +/* + Navicat Premium Data Transfer + + Source Server : 标签数据库 + Source Server Type : MongoDB + Source Server Version : 60025 (6.0.25) + Source Host : 192.168.2.6:27017 + Source Schema : KR_KR + + Target Server Type : MongoDB + Target Server Version : 60025 (6.0.25) + File Encoding : 65001 + + Date: 19/11/2025 15:18:19 +*/ + + +// ---------------------------- +// Collection structure for 34万HR人力资源经理企业1 +// ---------------------------- +db.getCollection("34万HR人力资源经理企业1").drop(); +db.createCollection("34万HR人力资源经理企业1"); +db.getCollection("34万HR人力资源经理企业1").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); +db.getCollection("34万HR人力资源经理企业1").createIndex({ + "电话": NumberInt("1") +}, { + name: "电话_1" +}); + +// ---------------------------- +// Collection structure for 34万HR人力资源经理企业3 +// ---------------------------- +db.getCollection("34万HR人力资源经理企业3").drop(); +db.createCollection("34万HR人力资源经理企业3"); +db.getCollection("34万HR人力资源经理企业3").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for 34万HR人力资源经理企业4 +// ---------------------------- +db.getCollection("34万HR人力资源经理企业4").drop(); +db.createCollection("34万HR人力资源经理企业4"); +db.getCollection("34万HR人力资源经理企业4").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for 34万HR人力资源经理企业5 +// ---------------------------- +db.getCollection("34万HR人力资源经理企业5").drop(); +db.createCollection("34万HR人力资源经理企业5"); +db.getCollection("34万HR人力资源经理企业5").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for 51JOB +// ---------------------------- +db.getCollection("51JOB").drop(); +db.createCollection("51JOB"); + +// ---------------------------- +// Collection structure for 8万条借款数据 +// ---------------------------- +db.getCollection("8万条借款数据").drop(); +db.createCollection("8万条借款数据"); +db.getCollection("8万条借款数据").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for CSDN用户数据 +// ---------------------------- +db.getCollection("CSDN用户数据").drop(); +db.createCollection("CSDN用户数据"); +db.getCollection("CSDN用户数据").createIndex({ + username: NumberInt("1") +}, { + name: "username_1" +}); +db.getCollection("CSDN用户数据").createIndex({ + email: NumberInt("1") +}, { + name: "email_1" +}); +db.getCollection("CSDN用户数据").createIndex({ + domain: NumberInt("1") +}, { + name: "domain_1" +}); +db.getCollection("CSDN用户数据").createIndex({ + created_at: NumberInt("-1") +}, { + name: "created_at_-1" +}); +db.getCollection("CSDN用户数据").createIndex({ + data_source: NumberInt("1") +}, { + name: "data_source_1" +}); +db.getCollection("CSDN用户数据").createIndex({ + username: NumberInt("1"), + email: NumberInt("1") +}, { + name: "username_1_email_1", + unique: true +}); + +// ---------------------------- +// Collection structure for Sheet2 +// ---------------------------- +db.getCollection("Sheet2").drop(); +db.createCollection("Sheet2"); +db.getCollection("Sheet2").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for pw_members +// ---------------------------- +db.getCollection("pw_members").drop(); +db.createCollection("pw_members"); + +// ---------------------------- +// Collection structure for system.profile +// ---------------------------- +db.getCollection("system.profile").drop(); +db.createCollection("system.profile",{ + capped: true, + size: 1048576 +}); + +// ---------------------------- +// Collection structure for www.jfclub.com +// ---------------------------- +db.getCollection("www.jfclub.com").drop(); +db.createCollection("www.jfclub.com"); +db.getCollection("www.jfclub.com").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); +db.getCollection("www.jfclub.com").createIndex({ + u_id: NumberInt("1") +}, { + name: "u_id_1" +}); +db.getCollection("www.jfclub.com").createIndex({ + email: NumberInt("1") +}, { + name: "email_1" +}); +db.getCollection("www.jfclub.com").createIndex({ + phone: NumberInt("1") +}, { + name: "phone_1" +}); +db.getCollection("www.jfclub.com").createIndex({ + name: NumberInt("1") +}, { + name: "name_1" +}); +db.getCollection("www.jfclub.com").createIndex({ + email: NumberInt("1"), + name: NumberInt("1") +}, { + name: "email_1_name_1" +}); +db.getCollection("www.jfclub.com").createIndex({ + phone: NumberInt("1"), + name: NumberInt("1") +}, { + name: "phone_1_name_1" +}); + +// ---------------------------- +// Collection structure for 人人网用户数据 +// ---------------------------- +db.getCollection("人人网用户数据").drop(); +db.createCollection("人人网用户数据"); + +// ---------------------------- +// Collection structure for 借贷 +// ---------------------------- +db.getCollection("借贷").drop(); +db.createCollection("借贷"); + +// ---------------------------- +// Collection structure for 全国车主数据集_2020年 +// ---------------------------- +db.getCollection("全国车主数据集_2020年").drop(); +db.createCollection("全国车主数据集_2020年"); + +// ---------------------------- +// Collection structure for 北京本科 +// ---------------------------- +db.getCollection("北京本科").drop(); +db.createCollection("北京本科"); +db.getCollection("北京本科").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for 千脑云电脑_qiannao_com +// ---------------------------- +db.getCollection("千脑云电脑_qiannao_com").drop(); +db.createCollection("千脑云电脑_qiannao_com"); +db.getCollection("千脑云电脑_qiannao_com").createIndex({ + "用户名": NumberInt("1") +}, { + name: "用户名_1" +}); +db.getCollection("千脑云电脑_qiannao_com").createIndex({ + "邮箱": NumberInt("1") +}, { + name: "邮箱_1" +}); +db.getCollection("千脑云电脑_qiannao_com").createIndex({ + "用户ID": NumberInt("1") +}, { + name: "用户ID_1" +}); +db.getCollection("千脑云电脑_qiannao_com").createIndex({ + "创建时间": NumberInt("1") +}, { + name: "创建时间_1" +}); + +// ---------------------------- +// Collection structure for 天涯论坛tianya.cn +// ---------------------------- +db.getCollection("天涯论坛tianya.cn").drop(); +db.createCollection("天涯论坛tianya.cn"); + +// ---------------------------- +// Collection structure for 情感 +// ---------------------------- +db.getCollection("情感").drop(); +db.createCollection("情感"); +db.getCollection("情感").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); +db.getCollection("情感").createIndex({ + "手机": NumberInt("1") +}, { + name: "手机_1" +}); + +// ---------------------------- +// Collection structure for 慈溪人才网 +// ---------------------------- +db.getCollection("慈溪人才网").drop(); +db.createCollection("慈溪人才网"); + +// ---------------------------- +// Collection structure for 房产网 +// ---------------------------- +db.getCollection("房产网").drop(); +db.createCollection("房产网"); +db.getCollection("房产网").createIndex({ + uid: NumberInt("1") +}, { + name: "uid_1", + unique: true +}); +db.getCollection("房产网").createIndex({ + username: NumberInt("1") +}, { + name: "username_1" +}); +db.getCollection("房产网").createIndex({ + email: NumberInt("1") +}, { + name: "email_1" +}); +db.getCollection("房产网").createIndex({ + regdate: NumberInt("1") +}, { + name: "regdate_1" +}); + +// ---------------------------- +// Collection structure for 抖音直播粉 +// ---------------------------- +db.getCollection("抖音直播粉").drop(); +db.createCollection("抖音直播粉"); +db.getCollection("抖音直播粉").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); +db.getCollection("抖音直播粉").createIndex({ + Uid: NumberInt("1") +}, { + name: "Uid_1" +}); +db.getCollection("抖音直播粉").createIndex({ + sec_uid: NumberInt("1") +}, { + name: "sec_uid_1" +}); + +// ---------------------------- +// Collection structure for 收藏品微信好友 +// ---------------------------- +db.getCollection("收藏品微信好友").drop(); +db.createCollection("收藏品微信好友"); +db.getCollection("收藏品微信好友").createIndex({ + WechatId: NumberInt("1") +}, { + name: "IX_Kefure_WechatFriend_WechatId" +}); +db.getCollection("收藏品微信好友").createIndex({ + OwnerWechatId: NumberInt("1") +}, { + name: "IX_Kefure_WechatFriend_OwnerWechatId" +}); +db.getCollection("收藏品微信好友").createIndex({ + Phone: NumberInt("1") +}, { + name: "Phone_1" +}); + +// ---------------------------- +// Collection structure for 收藏品用户 +// ---------------------------- +db.getCollection("收藏品用户").drop(); +db.createCollection("收藏品用户"); +db.getCollection("收藏品用户").createIndex({ + Id: NumberInt("1") +}, { + name: "Id_1" +}); +db.getCollection("收藏品用户").createIndex({ + WechatAccountId: NumberInt("1") +}, { + name: "WechatAccountId_1" +}); +db.getCollection("收藏品用户").createIndex({ + WechatId: NumberInt("1") +}, { + name: "WechatId_1" +}); +db.getCollection("收藏品用户").createIndex({ + AccountId: NumberInt("1") +}, { + name: "AccountId_1" +}); +db.getCollection("收藏品用户").createIndex({ + OwnerWechatId: NumberInt("1") +}, { + name: "OwnerWechatId_1" +}); +db.getCollection("收藏品用户").createIndex({ + GroupId: NumberInt("1") +}, { + name: "GroupId_1" +}); +db.getCollection("收藏品用户").createIndex({ + Nickname: NumberInt("1") +}, { + name: "Nickname_1" +}); +db.getCollection("收藏品用户").createIndex({ + AccountNickname: NumberInt("1") +}, { + name: "AccountNickname_1" +}); +db.getCollection("收藏品用户").createIndex({ + OwnerNickname: NumberInt("1") +}, { + name: "OwnerNickname_1" +}); + +// ---------------------------- +// Collection structure for 木蚂蚁munayi_com +// ---------------------------- +db.getCollection("木蚂蚁munayi_com").drop(); +db.createCollection("木蚂蚁munayi_com"); +db.getCollection("木蚂蚁munayi_com").createIndex({ + username: NumberInt("1") +}, { + name: "username_1", + unique: true, + sparse: true +}); +db.getCollection("木蚂蚁munayi_com").createIndex({ + email: NumberInt("1") +}, { + name: "email_1", + sparse: true +}); +db.getCollection("木蚂蚁munayi_com").createIndex({ + uid: NumberInt("1") +}, { + name: "uid_1", + sparse: true +}); +db.getCollection("木蚂蚁munayi_com").createIndex({ + data_hash: NumberInt("1") +}, { + name: "data_hash_1", + unique: true, + sparse: true +}); + +// ---------------------------- +// Collection structure for 狂人影子库user +// ---------------------------- +db.getCollection("狂人影子库user").drop(); +db.createCollection("狂人影子库user"); + +// ---------------------------- +// Collection structure for 珍爱网 +// ---------------------------- +db.getCollection("珍爱网").drop(); +db.createCollection("珍爱网"); +db.getCollection("珍爱网").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for 用户身份凭证数据集 +// ---------------------------- +db.getCollection("用户身份凭证数据集").drop(); +db.createCollection("用户身份凭证数据集"); + +// ---------------------------- +// Collection structure for 网红库 +// ---------------------------- +db.getCollection("网红库").drop(); +db.createCollection("网红库"); +db.getCollection("网红库").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); +db.getCollection("网红库").createIndex({ + sec_uid: NumberInt("1") +}, { + name: "sec_uid_1" +}); +db.getCollection("网红库").createIndex({ + idcard: NumberInt("1") +}, { + name: "idcard_1" +}); +db.getCollection("网红库").createIndex({ + collectId: NumberInt("1") +}, { + name: "collectId_1" +}); +db.getCollection("网红库").createIndex({ + cateId: NumberInt("1") +}, { + name: "cateId_1" +}); +db.getCollection("网红库").createIndex({ + authorId: NumberInt("1") +}, { + name: "authorId_1" +}); +db.getCollection("网红库").createIndex({ + uniqueId: NumberInt("1") +}, { + name: "uniqueId_1" +}); +db.getCollection("网红库").createIndex({ + phoneNumber: NumberInt("1") +}, { + name: "phoneNumber_1" +}); +db.getCollection("网红库").createIndex({ + nickname: NumberInt("1") +}, { + name: "nickname_1" +}); +db.getCollection("网红库").createIndex({ + updatedAt: NumberInt("1") +}, { + name: "updatedAt_1" +}); + +// ---------------------------- +// Collection structure for 雅虎用户 +// ---------------------------- +db.getCollection("雅虎用户").drop(); +db.createCollection("雅虎用户"); diff --git a/Moncter/数据库/KR_Linkedln.js b/Moncter/数据库/KR_Linkedln.js new file mode 100644 index 000000000..dc2b230d2 --- /dev/null +++ b/Moncter/数据库/KR_Linkedln.js @@ -0,0 +1,31 @@ +/* + Navicat Premium Data Transfer + + Source Server : 标签数据库 + Source Server Type : MongoDB + Source Server Version : 60025 (6.0.25) + Source Host : 192.168.2.6:27017 + Source Schema : KR_Linkedln + + Target Server Type : MongoDB + Target Server Version : 60025 (6.0.25) + File Encoding : 65001 + + Date: 19/11/2025 15:18:28 +*/ + + +// ---------------------------- +// Collection structure for linkedin +// ---------------------------- +db.getCollection("linkedin").drop(); +db.createCollection("linkedin"); + +// ---------------------------- +// Collection structure for system.profile +// ---------------------------- +db.getCollection("system.profile").drop(); +db.createCollection("system.profile",{ + capped: true, + size: 1048576 +}); diff --git a/Moncter/数据库/KR_京东.js b/Moncter/数据库/KR_京东.js new file mode 100644 index 000000000..9d7683579 --- /dev/null +++ b/Moncter/数据库/KR_京东.js @@ -0,0 +1,31 @@ +/* + Navicat Premium Data Transfer + + Source Server : 标签数据库 + Source Server Type : MongoDB + Source Server Version : 60025 (6.0.25) + Source Host : 192.168.2.6:27017 + Source Schema : KR_京东 + + Target Server Type : MongoDB + Target Server Version : 60025 (6.0.25) + File Encoding : 65001 + + Date: 19/11/2025 15:19:11 +*/ + + +// ---------------------------- +// Collection structure for jd_com +// ---------------------------- +db.getCollection("jd_com").drop(); +db.createCollection("jd_com"); + +// ---------------------------- +// Collection structure for system.profile +// ---------------------------- +db.getCollection("system.profile").drop(); +db.createCollection("system.profile",{ + capped: true, + size: 1048576 +}); diff --git a/Moncter/数据库/KR_人才库.js b/Moncter/数据库/KR_人才库.js new file mode 100644 index 000000000..aba558720 --- /dev/null +++ b/Moncter/数据库/KR_人才库.js @@ -0,0 +1,37 @@ +/* + Navicat Premium Data Transfer + + Source Server : 标签数据库 + Source Server Type : MongoDB + Source Server Version : 60025 (6.0.25) + Source Host : 192.168.2.6:27017 + Source Schema : KR_人才库 + + Target Server Type : MongoDB + Target Server Version : 60025 (6.0.25) + File Encoding : 65001 + + Date: 19/11/2025 15:19:59 +*/ + + +// ---------------------------- +// Collection structure for 51JOB +// ---------------------------- +db.getCollection("51JOB").drop(); +db.createCollection("51JOB"); + +// ---------------------------- +// Collection structure for system.profile +// ---------------------------- +db.getCollection("system.profile").drop(); +db.createCollection("system.profile",{ + capped: true, + size: 1048576 +}); + +// ---------------------------- +// Collection structure for 慈溪人才网 +// ---------------------------- +db.getCollection("慈溪人才网").drop(); +db.createCollection("慈溪人才网"); diff --git a/Moncter/数据库/KR_企业.js b/Moncter/数据库/KR_企业.js new file mode 100644 index 000000000..f58889d3b --- /dev/null +++ b/Moncter/数据库/KR_企业.js @@ -0,0 +1,142 @@ +/* + Navicat Premium Data Transfer + + Source Server : 标签数据库 + Source Server Type : MongoDB + Source Server Version : 60025 (6.0.25) + Source Host : 192.168.2.6:27017 + Source Schema : KR_企业 + + Target Server Type : MongoDB + Target Server Version : 60025 (6.0.25) + File Encoding : 65001 + + Date: 19/11/2025 15:19:44 +*/ + + +// ---------------------------- +// Collection structure for system.profile +// ---------------------------- +db.getCollection("system.profile").drop(); +db.createCollection("system.profile",{ + capped: true, + size: 1048576 +}); + +// ---------------------------- +// Collection structure for 企业400号段数据 +// ---------------------------- +db.getCollection("企业400号段数据").drop(); +db.createCollection("企业400号段数据"); +db.getCollection("企业400号段数据").createIndex({ + ID: NumberInt("1") +}, { + name: "ID_1" +}); +db.getCollection("企业400号段数据").createIndex({ + "企业名称": NumberInt("1") +}, { + name: "企业名称_1" +}); +db.getCollection("企业400号段数据").createIndex({ + "400号码": NumberInt("1") +}, { + name: "400号码_1" +}); +db.getCollection("企业400号段数据").createIndex({ + "联系人": NumberInt("1") +}, { + name: "联系人_1" +}); +db.getCollection("企业400号段数据").createIndex({ + "手机号码": NumberInt("1") +}, { + name: "手机号码_1" +}); +db.getCollection("企业400号段数据").createIndex({ + "表名": NumberInt("1") +}, { + name: "表名_1" +}); +db.getCollection("企业400号段数据").createIndex({ + "导入时间": NumberInt("-1") +}, { + name: "导入时间_-1" +}); +db.getCollection("企业400号段数据").createIndex({ + nid: NumberInt("1") +}, { + name: "nid_1" +}); +db.getCollection("企业400号段数据").createIndex({ + EnterpriseName: NumberInt("1") +}, { + name: "EnterpriseName_1" +}); +db.getCollection("企业400号段数据").createIndex({ + Number400: NumberInt("1") +}, { + name: "Number400_1" +}); +db.getCollection("企业400号段数据").createIndex({ + LinkMan: NumberInt("1") +}, { + name: "LinkMan_1" +}); +db.getCollection("企业400号段数据").createIndex({ + Mobile: NumberInt("1") +}, { + name: "Mobile_1" +}); + +// ---------------------------- +// Collection structure for 统一用户资产表 +// ---------------------------- +db.getCollection("统一用户资产表").drop(); +db.createCollection("统一用户资产表"); +db.getCollection("统一用户资产表").createIndex({ + "用户唯一ID": NumberInt("1") +}, { + name: "用户唯一ID_1" +}); +db.getCollection("统一用户资产表").createIndex({ + "基础信息.手机号": NumberInt("1") +}, { + name: "基础信息.手机号_1" +}); +db.getCollection("统一用户资产表").createIndex({ + "基础信息.邮箱": NumberInt("1") +}, { + name: "基础信息.邮箱_1" +}); +db.getCollection("统一用户资产表").createIndex({ + "企业信息.企业ID": NumberInt("1") +}, { + name: "企业信息.企业ID_1" +}); +db.getCollection("统一用户资产表").createIndex({ + "企业信息.400号码": NumberInt("1") +}, { + name: "企业信息.400号码_1" +}); +db.getCollection("统一用户资产表").createIndex({ + "资产概览.总资产价值": NumberInt("-1") +}, { + name: "资产概览.总资产价值_-1" +}); +db.getCollection("统一用户资产表").createIndex({ + "资产概览.资产等级": NumberInt("1") +}, { + name: "资产概览.资产等级_1" +}); +db.getCollection("统一用户资产表").createIndex({ + "用户类型": NumberInt("1") +}, { + name: "用户类型_1" +}); +db.getCollection("统一用户资产表").createIndex({ + "创建时间": NumberInt("-1") +}, { + name: "创建时间_-1" +}); diff --git a/Moncter/数据库/KR_企业名录.js b/Moncter/数据库/KR_企业名录.js new file mode 100644 index 000000000..d5fe4d07f --- /dev/null +++ b/Moncter/数据库/KR_企业名录.js @@ -0,0 +1,115 @@ +/* + Navicat Premium Data Transfer + + Source Server : 标签数据库 + Source Server Type : MongoDB + Source Server Version : 60025 (6.0.25) + Source Host : 192.168.2.6:27017 + Source Schema : KR_企业名录 + + Target Server Type : MongoDB + Target Server Version : 60025 (6.0.25) + File Encoding : 65001 + + Date: 19/11/2025 15:19:51 +*/ + + +// ---------------------------- +// Collection structure for system.profile +// ---------------------------- +db.getCollection("system.profile").drop(); +db.createCollection("system.profile",{ + capped: true, + size: 1048576 +}); + +// ---------------------------- +// Collection structure for 中国医院名录_copy1 +// ---------------------------- +db.getCollection("中国医院名录_copy1").drop(); +db.createCollection("中国医院名录_copy1"); + +// ---------------------------- +// Collection structure for 企业及个体工商目录 +// ---------------------------- +db.getCollection("企业及个体工商目录").drop(); +db.createCollection("企业及个体工商目录"); + +// ---------------------------- +// Collection structure for 企业名录 +// ---------------------------- +db.getCollection("企业名录").drop(); +db.createCollection("企业名录"); + +// ---------------------------- +// Collection structure for 四川企业老板 +// ---------------------------- +db.getCollection("四川企业老板").drop(); +db.createCollection("四川企业老板"); + +// ---------------------------- +// Collection structure for 广东工商企业库1 +// ---------------------------- +db.getCollection("广东工商企业库1").drop(); +db.createCollection("广东工商企业库1"); + +// ---------------------------- +// Collection structure for 广东工商企业库2 +// ---------------------------- +db.getCollection("广东工商企业库2").drop(); +db.createCollection("广东工商企业库2"); + +// ---------------------------- +// Collection structure for 广东工商企业库3 +// ---------------------------- +db.getCollection("广东工商企业库3").drop(); +db.createCollection("广东工商企业库3"); + +// ---------------------------- +// Collection structure for 广东工商企业库4 +// ---------------------------- +db.getCollection("广东工商企业库4").drop(); +db.createCollection("广东工商企业库4"); + +// ---------------------------- +// Collection structure for 广东工商企业库5 +// ---------------------------- +db.getCollection("广东工商企业库5").drop(); +db.createCollection("广东工商企业库5"); + +// ---------------------------- +// Collection structure for 广东工商企业库6 +// ---------------------------- +db.getCollection("广东工商企业库6").drop(); +db.createCollection("广东工商企业库6"); + +// ---------------------------- +// Collection structure for 广东工商企业库7 +// ---------------------------- +db.getCollection("广东工商企业库7").drop(); +db.createCollection("广东工商企业库7"); + +// ---------------------------- +// Collection structure for 行政区划代码 +// ---------------------------- +db.getCollection("行政区划代码").drop(); +db.createCollection("行政区划代码"); + +// ---------------------------- +// Collection structure for 表1 +// ---------------------------- +db.getCollection("表1").drop(); +db.createCollection("表1"); + +// ---------------------------- +// Collection structure for 表2 +// ---------------------------- +db.getCollection("表2").drop(); +db.createCollection("表2"); + +// ---------------------------- +// Collection structure for 销售额3000万元-5000万元企业名录 +// ---------------------------- +db.getCollection("销售额3000万元-5000万元企业名录").drop(); +db.createCollection("销售额3000万元-5000万元企业名录"); diff --git a/Moncter/数据库/KR_卡若私域.js b/Moncter/数据库/KR_卡若私域.js new file mode 100644 index 000000000..b942a6183 --- /dev/null +++ b/Moncter/数据库/KR_卡若私域.js @@ -0,0 +1,228 @@ +/* + Navicat Premium Data Transfer + + Source Server : 标签数据库 + Source Server Type : MongoDB + Source Server Version : 60025 (6.0.25) + Source Host : 192.168.2.6:27017 + Source Schema : KR_卡若私域 + + Target Server Type : MongoDB + Target Server Version : 60025 (6.0.25) + File Encoding : 65001 + + Date: 19/11/2025 15:19:24 +*/ + + +// ---------------------------- +// Collection structure for system.profile +// ---------------------------- +db.getCollection("system.profile").drop(); +db.createCollection("system.profile",{ + capped: true, + size: 1048576 +}); + +// ---------------------------- +// Collection structure for 存客宝流量池分类 +// ---------------------------- +db.getCollection("存客宝流量池分类").drop(); +db.createCollection("存客宝流量池分类"); +db.getCollection("存客宝流量池分类").createIndex({ + province: NumberInt("1"), + wechat: NumberInt("1"), + phoneNumber: NumberInt("1"), + createdAt: NumberInt("1") +}, { + name: "user_province_wechat_phone_number_created_at" +}); +db.getCollection("存客宝流量池分类").createIndex({ + province: NumberInt("1"), + wechat: NumberInt("1"), + phoneNumber: NumberInt("1"), + wechatIsOpen: NumberInt("1"), + createdAt: NumberInt("1") +}, { + name: "user_province_wechat_phone_number_wechat_is_open_created_at" +}); + +// ---------------------------- +// Collection structure for 存客宝流量池分类2 +// ---------------------------- +db.getCollection("存客宝流量池分类2").drop(); +db.createCollection("存客宝流量池分类2"); +db.getCollection("存客宝流量池分类2").createIndex({ + source: NumberInt("1"), + wechat: NumberInt("1") +}, { + name: "user_source_wechat", + unique: true +}); +db.getCollection("存客宝流量池分类2").createIndex({ + source: NumberInt("1"), + phoneNumber: NumberInt("1") +}, { + name: "user_source_phone_number", + unique: true +}); +db.getCollection("存客宝流量池分类2").createIndex({ + province: NumberInt("1") +}, { + name: "user_province" +}); +db.getCollection("存客宝流量池分类2").createIndex({ + wechat: NumberInt("1") +}, { + name: "user_wechat" +}); +db.getCollection("存客宝流量池分类2").createIndex({ + phoneNumber: NumberInt("1") +}, { + name: "user_phone_number" +}); +db.getCollection("存客宝流量池分类2").createIndex({ + wechatIsOpen: NumberInt("1") +}, { + name: "user_wechat_is_open" +}); +db.getCollection("存客宝流量池分类2").createIndex({ + createdAt: NumberInt("1") +}, { + name: "user_created_at" +}); +db.getCollection("存客宝流量池分类2").createIndex({ + jdIsOpen: NumberInt("1") +}, { + name: "user_jd_is_open" +}); + +// ---------------------------- +// Collection structure for 存客宝采集网红 +// ---------------------------- +db.getCollection("存客宝采集网红").drop(); +db.createCollection("存客宝采集网红"); +db.getCollection("存客宝采集网红").createIndex({ + OwnerWechatId: NumberInt("1"), + Phone: NumberInt("1"), + Alias: NumberInt("1") +}, { + name: "IX_Kefure_WechatFriend_OwnerWechatId" +}); +db.getCollection("存客宝采集网红").createIndex({ + WechatId: NumberInt("1"), + IsSend: NumberInt("1"), + IsDeleted: NumberInt("1"), + Gender: NumberInt("1"), + Nickname: NumberInt("1") +}, { + name: "IX_Kefure_WechatFriend_WechatId_IsSend" +}); +db.getCollection("存客宝采集网红").createIndex({ + WechatAccountId: NumberInt("1") +}, { + name: "WechatAccountId" +}); + +// ---------------------------- +// Collection structure for 存客宝采集网红有微信 +// ---------------------------- +db.getCollection("存客宝采集网红有微信").drop(); +db.createCollection("存客宝采集网红有微信"); + +// ---------------------------- +// Collection structure for 老坑爹商店 shop.lkdie.com +// ---------------------------- +db.getCollection("老坑爹商店 shop.lkdie.com").drop(); +db.createCollection("老坑爹商店 shop.lkdie.com"); +db.getCollection("老坑爹商店 shop.lkdie.com").createIndex({ + email: NumberInt("1") +}, { + name: "email", + unique: true +}); +db.getCollection("老坑爹商店 shop.lkdie.com").createIndex({ + nickname: NumberInt("1") +}, { + name: "nickname", + unique: true +}); +db.getCollection("老坑爹商店 shop.lkdie.com").createIndex({ + updatedTime: NumberInt("1") +}, { + name: "updatedTime" +}); + +// ---------------------------- +// Collection structure for 老坑爹论坛www.lkdie.com 会员 +// ---------------------------- +db.getCollection("老坑爹论坛www.lkdie.com 会员").drop(); +db.createCollection("老坑爹论坛www.lkdie.com 会员"); +db.getCollection("老坑爹论坛www.lkdie.com 会员").createIndex({ + username: NumberInt("1") +}, { + name: "username", + unique: true +}); +db.getCollection("老坑爹论坛www.lkdie.com 会员").createIndex({ + email: NumberInt("1") +}, { + name: "email" +}); + +// ---------------------------- +// Collection structure for 黑科技www.quwanzhi.com 付款邮箱 +// ---------------------------- +db.getCollection("黑科技www.quwanzhi.com 付款邮箱").drop(); +db.createCollection("黑科技www.quwanzhi.com 付款邮箱"); +db.getCollection("黑科技www.quwanzhi.com 付款邮箱").createIndex({ + trade_no: NumberInt("1") +}, { + name: "tradeNo" +}); +db.getCollection("黑科技www.quwanzhi.com 付款邮箱").createIndex({ + zid: NumberInt("1") +}, { + name: "zid" +}); +db.getCollection("黑科技www.quwanzhi.com 付款邮箱").createIndex({ + tid: NumberInt("1") +}, { + name: "tid" +}); + +// ---------------------------- +// Collection structure for 黑科技www.quwanzhi.com 发卡邮箱 +// ---------------------------- +db.getCollection("黑科技www.quwanzhi.com 发卡邮箱").drop(); +db.createCollection("黑科技www.quwanzhi.com 发卡邮箱"); +db.getCollection("黑科技www.quwanzhi.com 发卡邮箱").createIndex({ + zid: NumberInt("1") +}, { + name: "zid" +}); +db.getCollection("黑科技www.quwanzhi.com 发卡邮箱").createIndex({ + input: NumberInt("1") +}, { + name: "input" +}); +db.getCollection("黑科技www.quwanzhi.com 发卡邮箱").createIndex({ + userid: NumberInt("1") +}, { + name: "userid" +}); +db.getCollection("黑科技www.quwanzhi.com 发卡邮箱").createIndex({ + tradeno: NumberInt("1") +}, { + name: "tradeno" +}); +db.getCollection("黑科技www.quwanzhi.com 发卡邮箱").createIndex({ + id: NumberInt("1") +}, { + name: "id" +}); +db.getCollection("黑科技www.quwanzhi.com 发卡邮箱").createIndex({ + tid: NumberInt("1") +}, { + name: "tid" +}); diff --git a/Moncter/数据库/KR_商城.js b/Moncter/数据库/KR_商城.js new file mode 100644 index 000000000..3a784f1cd --- /dev/null +++ b/Moncter/数据库/KR_商城.js @@ -0,0 +1,122 @@ +/* + Navicat Premium Data Transfer + + Source Server : 标签数据库 + Source Server Type : MongoDB + Source Server Version : 60025 (6.0.25) + Source Host : 192.168.2.6:27017 + Source Schema : KR_商城 + + Target Server Type : MongoDB + Target Server Version : 60025 (6.0.25) + File Encoding : 65001 + + Date: 19/11/2025 15:20:13 +*/ + + +// ---------------------------- +// Collection structure for 21年贝蒂喜订单整合 +// ---------------------------- +db.getCollection("21年贝蒂喜订单整合").drop(); +db.createCollection("21年贝蒂喜订单整合"); +db.getCollection("21年贝蒂喜订单整合").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); +db.getCollection("21年贝蒂喜订单整合").createIndex({ + "特权订金订单id": NumberInt("1") +}, { + name: "特权订金订单id_1" +}); +db.getCollection("21年贝蒂喜订单整合").createIndex({ + "新零售导购门店id": NumberInt("1") +}, { + name: "新零售导购门店id_1" +}); +db.getCollection("21年贝蒂喜订单整合").createIndex({ + "新零售发货门店id": NumberInt("1") +}, { + name: "新零售发货门店id_1" +}); +db.getCollection("21年贝蒂喜订单整合").createIndex({ + "新零售成交门店id": NumberInt("1") +}, { + name: "新零售成交门店id_1" +}); +db.getCollection("21年贝蒂喜订单整合").createIndex({ + "新零售成交经销商id": NumberInt("1") +}, { + name: "新零售成交经销商id_1" +}); +db.getCollection("21年贝蒂喜订单整合").createIndex({ + "店铺Id": NumberInt("1") +}, { + name: "店铺Id_1" +}); + +// ---------------------------- +// Collection structure for system.profile +// ---------------------------- +db.getCollection("system.profile").drop(); +db.createCollection("system.profile",{ + capped: true, + size: 1048576 +}); + +// ---------------------------- +// Collection structure for 凡客诚品_vancl.com +// ---------------------------- +db.getCollection("凡客诚品_vancl.com").drop(); +db.createCollection("凡客诚品_vancl.com"); +db.getCollection("凡客诚品_vancl.com").createIndex({ + name: NumberInt("1") +}, { + name: "name_1" +}); +db.getCollection("凡客诚品_vancl.com").createIndex({ + "数据来源": NumberInt("1"), + "导入时间": NumberInt("-1") +}, { + name: "数据来源_1_导入时间_-1" +}); + +// ---------------------------- +// Collection structure for 嘟嘟牛 +// ---------------------------- +db.getCollection("嘟嘟牛").drop(); +db.createCollection("嘟嘟牛"); + +// ---------------------------- +// Collection structure for 嘟嘟牛 本地同城 +// ---------------------------- +db.getCollection("嘟嘟牛 本地同城").drop(); +db.createCollection("嘟嘟牛 本地同城"); +db.getCollection("嘟嘟牛 本地同城").createIndex({ + username: NumberInt("1") +}, { + name: "username_1" +}); +db.getCollection("嘟嘟牛 本地同城").createIndex({ + email: NumberInt("1") +}, { + name: "email_1" +}); + +// ---------------------------- +// Collection structure for 小米 xiaomi_com +// ---------------------------- +db.getCollection("小米 xiaomi_com").drop(); +db.createCollection("小米 xiaomi_com"); + +// ---------------------------- +// Collection structure for 购物-北京一电购公司2月整理版30万 +// ---------------------------- +db.getCollection("购物-北京一电购公司2月整理版30万").drop(); +db.createCollection("购物-北京一电购公司2月整理版30万"); +db.getCollection("购物-北京一电购公司2月整理版30万").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); diff --git a/Moncter/数据库/KR_国外.js b/Moncter/数据库/KR_国外.js new file mode 100644 index 000000000..2498e82bf --- /dev/null +++ b/Moncter/数据库/KR_国外.js @@ -0,0 +1,359 @@ +/* + Navicat Premium Data Transfer + + Source Server : 标签数据库 + Source Server Type : MongoDB + Source Server Version : 60025 (6.0.25) + Source Host : 192.168.2.6:27017 + Source Schema : KR_国外 + + Target Server Type : MongoDB + Target Server Version : 60025 (6.0.25) + File Encoding : 65001 + + Date: 19/11/2025 15:18:56 +*/ + + +// ---------------------------- +// Collection structure for Yandex俄罗斯百度用户数据 +// ---------------------------- +db.getCollection("Yandex俄罗斯百度用户数据").drop(); +db.createCollection("Yandex俄罗斯百度用户数据"); +db.getCollection("Yandex俄罗斯百度用户数据").createIndex({ + email: NumberInt("1") +}, { + name: "email_1" +}); +db.getCollection("Yandex俄罗斯百度用户数据").createIndex({ + username: NumberInt("1") +}, { + name: "username_1" +}); +db.getCollection("Yandex俄罗斯百度用户数据").createIndex({ + domain: NumberInt("1") +}, { + name: "domain_1" +}); +db.getCollection("Yandex俄罗斯百度用户数据").createIndex({ + password_strength: NumberInt("1") +}, { + name: "password_strength_1" +}); +db.getCollection("Yandex俄罗斯百度用户数据").createIndex({ + country: NumberInt("1") +}, { + name: "country_1" +}); +db.getCollection("Yandex俄罗斯百度用户数据").createIndex({ + service_provider: NumberInt("1") +}, { + name: "service_provider_1" +}); +db.getCollection("Yandex俄罗斯百度用户数据").createIndex({ + created_at: NumberInt("-1") +}, { + name: "created_at_-1" +}); +db.getCollection("Yandex俄罗斯百度用户数据").createIndex({ + data_source: NumberInt("1") +}, { + name: "data_source_1" +}); +db.getCollection("Yandex俄罗斯百度用户数据").createIndex({ + email: NumberInt("1"), + password: NumberInt("1") +}, { + name: "email_password_unique", + unique: true +}); +db.getCollection("Yandex俄罗斯百度用户数据").createIndex({ + "$**": "text" +}, { + name: "text_search", + weights: { + email: NumberInt("1"), + username: NumberInt("1") + }, + default_language: "english", + language_override: "language", + textIndexVersion: NumberInt("3") +}); + +// ---------------------------- +// Collection structure for Yandex俄罗斯谷歌用户数据 +// ---------------------------- +db.getCollection("Yandex俄罗斯谷歌用户数据").drop(); +db.createCollection("Yandex俄罗斯谷歌用户数据"); +db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({ + email: NumberInt("1") +}, { + name: "email_1" +}); +db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({ + username: NumberInt("1") +}, { + name: "username_1" +}); +db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({ + domain: NumberInt("1") +}, { + name: "domain_1" +}); +db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({ + password_strength: NumberInt("1") +}, { + name: "password_strength_1" +}); +db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({ + country: NumberInt("1") +}, { + name: "country_1" +}); +db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({ + service_provider: NumberInt("1") +}, { + name: "service_provider_1" +}); +db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({ + created_at: NumberInt("-1") +}, { + name: "created_at_-1" +}); +db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({ + data_source: NumberInt("1") +}, { + name: "data_source_1" +}); +db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({ + email: NumberInt("1"), + password: NumberInt("1") +}, { + name: "email_password_unique", + unique: true +}); +db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({ + "$**": "text" +}, { + name: "text_search", + weights: { + email: NumberInt("1"), + username: NumberInt("1") + }, + default_language: "english", + language_override: "language", + textIndexVersion: NumberInt("3") +}); + +// ---------------------------- +// Collection structure for system.profile +// ---------------------------- +db.getCollection("system.profile").drop(); +db.createCollection("system.profile",{ + capped: true, + size: 1048576 +}); + +// ---------------------------- +// Collection structure for www.angeldiet.co.kr 1 +// ---------------------------- +db.getCollection("www.angeldiet.co.kr 1").drop(); +db.createCollection("www.angeldiet.co.kr 1"); +db.getCollection("www.angeldiet.co.kr 1").createIndex({ + ID: NumberInt("1") +}, { + name: "ID_1" +}); + +// ---------------------------- +// Collection structure for www.angeldiet.co.kr 2 +// ---------------------------- +db.getCollection("www.angeldiet.co.kr 2").drop(); +db.createCollection("www.angeldiet.co.kr 2"); +db.getCollection("www.angeldiet.co.kr 2").createIndex({ + ID: NumberInt("1") +}, { + name: "ID_1" +}); + +// ---------------------------- +// Collection structure for www.bookoa.com(韩国学术出版) +// ---------------------------- +db.getCollection("www.bookoa.com(韩国学术出版)").drop(); +db.createCollection("www.bookoa.com(韩国学术出版)"); +db.getCollection("www.bookoa.com(韩国学术出版)").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); +db.getCollection("www.bookoa.com(韩国学术出版)").createIndex({ + email: NumberInt("1") +}, { + name: "email_1" +}); +db.getCollection("www.bookoa.com(韩国学术出版)").createIndex({ + hphone: NumberInt("1") +}, { + name: "hphone_1" +}); +db.getCollection("www.bookoa.com(韩国学术出版)").createIndex({ + name: NumberInt("1") +}, { + name: "name_1" +}); +db.getCollection("www.bookoa.com(韩国学术出版)").createIndex({ + email: NumberInt("1"), + name: NumberInt("1") +}, { + name: "email_1_name_1" +}); + +// ---------------------------- +// Collection structure for 卡塔卡银行_APP数据 +// ---------------------------- +db.getCollection("卡塔卡银行_APP数据").drop(); +db.createCollection("卡塔卡银行_APP数据"); + +// ---------------------------- +// Collection structure for 卡塔卡银行_交易明细 +// ---------------------------- +db.getCollection("卡塔卡银行_交易明细").drop(); +db.createCollection("卡塔卡银行_交易明细"); + +// ---------------------------- +// Collection structure for 卡塔卡银行_交易明细临时 +// ---------------------------- +db.getCollection("卡塔卡银行_交易明细临时").drop(); +db.createCollection("卡塔卡银行_交易明细临时"); + +// ---------------------------- +// Collection structure for 卡塔卡银行_卡主表 +// ---------------------------- +db.getCollection("卡塔卡银行_卡主表").drop(); +db.createCollection("卡塔卡银行_卡主表"); + +// ---------------------------- +// Collection structure for 卡塔卡银行_卡列表 +// ---------------------------- +db.getCollection("卡塔卡银行_卡列表").drop(); +db.createCollection("卡塔卡银行_卡列表"); + +// ---------------------------- +// Collection structure for 卡塔卡银行_审计主表 +// ---------------------------- +db.getCollection("卡塔卡银行_审计主表").drop(); +db.createCollection("卡塔卡银行_审计主表"); + +// ---------------------------- +// Collection structure for 卡塔卡银行_客户 +// ---------------------------- +db.getCollection("卡塔卡银行_客户").drop(); +db.createCollection("卡塔卡银行_客户"); + +// ---------------------------- +// Collection structure for 卡塔卡银行_客户主表 +// ---------------------------- +db.getCollection("卡塔卡银行_客户主表").drop(); +db.createCollection("卡塔卡银行_客户主表"); + +// ---------------------------- +// Collection structure for 卡塔卡银行_收款人主表 +// ---------------------------- +db.getCollection("卡塔卡银行_收款人主表").drop(); +db.createCollection("卡塔卡银行_收款人主表"); + +// ---------------------------- +// Collection structure for 卡塔卡银行_用户档案 +// ---------------------------- +db.getCollection("卡塔卡银行_用户档案").drop(); +db.createCollection("卡塔卡银行_用户档案"); + +// ---------------------------- +// Collection structure for 卡塔卡银行_申请记录 +// ---------------------------- +db.getCollection("卡塔卡银行_申请记录").drop(); +db.createCollection("卡塔卡银行_申请记录"); + +// ---------------------------- +// Collection structure for 卡塔卡银行_电子对账单邮箱 +// ---------------------------- +db.getCollection("卡塔卡银行_电子对账单邮箱").drop(); +db.createCollection("卡塔卡银行_电子对账单邮箱"); + +// ---------------------------- +// Collection structure for 卡塔卡银行_订单客户明细 +// ---------------------------- +db.getCollection("卡塔卡银行_订单客户明细").drop(); +db.createCollection("卡塔卡银行_订单客户明细"); + +// ---------------------------- +// Collection structure for 卡塔卡银行_账户主表 +// ---------------------------- +db.getCollection("卡塔卡银行_账户主表").drop(); +db.createCollection("卡塔卡银行_账户主表"); + +// ---------------------------- +// Collection structure for 卡塔卡银行_身份证变更审计 +// ---------------------------- +db.getCollection("卡塔卡银行_身份证变更审计").drop(); +db.createCollection("卡塔卡银行_身份证变更审计"); + +// ---------------------------- +// Collection structure for 卡塔卡银行_黑名单用户 +// ---------------------------- +db.getCollection("卡塔卡银行_黑名单用户").drop(); +db.createCollection("卡塔卡银行_黑名单用户"); + +// ---------------------------- +// Collection structure for 台湾财经电视台 +// ---------------------------- +db.getCollection("台湾财经电视台").drop(); +db.createCollection("台湾财经电视台"); +db.getCollection("台湾财经电视台").createIndex({ + email: NumberInt("1") +}, { + name: "email_1" +}); +db.getCollection("台湾财经电视台").createIndex({ + name: NumberInt("1") +}, { + name: "name_1" +}); +db.getCollection("台湾财经电视台").createIndex({ + phone: NumberInt("1") +}, { + name: "phone_1" +}); +db.getCollection("台湾财经电视台").createIndex({ + source: NumberInt("1") +}, { + name: "source_1" +}); + +// ---------------------------- +// Collection structure for 土耳其公民 +// ---------------------------- +db.getCollection("土耳其公民").drop(); +db.createCollection("土耳其公民"); + +// ---------------------------- +// Collection structure for 社工库_1.7G_www.comicstorm.co.kr 85w_1 +// ---------------------------- +db.getCollection("社工库_1.7G_www.comicstorm.co.kr 85w_1").drop(); +db.createCollection("社工库_1.7G_www.comicstorm.co.kr 85w_1"); +db.getCollection("社工库_1.7G_www.comicstorm.co.kr 85w_1").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for 社工库_1.7G_www.hacker.co.kr 140W_140W +// ---------------------------- +db.getCollection("社工库_1.7G_www.hacker.co.kr 140W_140W").drop(); +db.createCollection("社工库_1.7G_www.hacker.co.kr 140W_140W"); +db.getCollection("社工库_1.7G_www.hacker.co.kr 140W_140W").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); diff --git a/Moncter/数据库/KR_存客宝.js b/Moncter/数据库/KR_存客宝.js new file mode 100644 index 000000000..c6e03cf17 --- /dev/null +++ b/Moncter/数据库/KR_存客宝.js @@ -0,0 +1,618 @@ +/* + Navicat Premium Data Transfer + + Source Server : 标签数据库 + Source Server Type : MongoDB + Source Server Version : 60025 (6.0.25) + Source Host : 192.168.2.6:27017 + Source Schema : KR_存客宝 + + Target Server Type : MongoDB + Target Server Version : 60025 (6.0.25) + File Encoding : 65001 + + Date: 19/11/2025 15:18:41 +*/ + + +// ---------------------------- +// Collection structure for cunkebao_AccountWechat +// ---------------------------- +db.getCollection("cunkebao_AccountWechat").drop(); +db.createCollection("cunkebao_AccountWechat"); +db.getCollection("cunkebao_AccountWechat").createIndex({ + lId: NumberInt("1") +}, { + name: "lId_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_DownloadVideoSchedule +// ---------------------------- +db.getCollection("cunkebao_DownloadVideoSchedule").drop(); +db.createCollection("cunkebao_DownloadVideoSchedule"); +db.getCollection("cunkebao_DownloadVideoSchedule").createIndex({ + lId: NumberInt("1") +}, { + name: "lId_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_FriendMessageTask +// ---------------------------- +db.getCollection("cunkebao_FriendMessageTask").drop(); +db.createCollection("cunkebao_FriendMessageTask"); +db.getCollection("cunkebao_FriendMessageTask").createIndex({ + lId: NumberInt("1") +}, { + name: "lId_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_FriendMessageTpl +// ---------------------------- +db.getCollection("cunkebao_FriendMessageTpl").drop(); +db.createCollection("cunkebao_FriendMessageTpl"); +db.getCollection("cunkebao_FriendMessageTpl").createIndex({ + lId: NumberInt("1") +}, { + name: "lId_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_FriendRequestConfig +// ---------------------------- +db.getCollection("cunkebao_FriendRequestConfig").drop(); +db.createCollection("cunkebao_FriendRequestConfig"); + +// ---------------------------- +// Collection structure for cunkebao_FriendRequestTaskScan +// ---------------------------- +db.getCollection("cunkebao_FriendRequestTaskScan").drop(); +db.createCollection("cunkebao_FriendRequestTaskScan"); +db.getCollection("cunkebao_FriendRequestTaskScan").createIndex({ + lId: NumberInt("1") +}, { + name: "lId_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_FrontSession +// ---------------------------- +db.getCollection("cunkebao_FrontSession").drop(); +db.createCollection("cunkebao_FrontSession"); + +// ---------------------------- +// Collection structure for cunkebao_JdPromotionSite +// ---------------------------- +db.getCollection("cunkebao_JdPromotionSite").drop(); +db.createCollection("cunkebao_JdPromotionSite"); +db.getCollection("cunkebao_JdPromotionSite").createIndex({ + lId: NumberInt("1") +}, { + name: "lId_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_JdSocialMedia +// ---------------------------- +db.getCollection("cunkebao_JdSocialMedia").drop(); +db.createCollection("cunkebao_JdSocialMedia"); +db.getCollection("cunkebao_JdSocialMedia").createIndex({ + lId: NumberInt("1") +}, { + name: "lId_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_MaterialLib +// ---------------------------- +db.getCollection("cunkebao_MaterialLib").drop(); +db.createCollection("cunkebao_MaterialLib"); +db.getCollection("cunkebao_MaterialLib").createIndex({ + lId: NumberInt("1") +}, { + name: "lId_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_MaterialPushLog +// ---------------------------- +db.getCollection("cunkebao_MaterialPushLog").drop(); +db.createCollection("cunkebao_MaterialPushLog"); +db.getCollection("cunkebao_MaterialPushLog").createIndex({ + lId: NumberInt("1") +}, { + name: "lId_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_MiniProgrameLib +// ---------------------------- +db.getCollection("cunkebao_MiniProgrameLib").drop(); +db.createCollection("cunkebao_MiniProgrameLib"); +db.getCollection("cunkebao_MiniProgrameLib").createIndex({ + lId: NumberInt("1") +}, { + name: "lId_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_PartTimer +// ---------------------------- +db.getCollection("cunkebao_PartTimer").drop(); +db.createCollection("cunkebao_PartTimer"); +db.getCollection("cunkebao_PartTimer").createIndex({ + lId: NumberInt("1") +}, { + name: "lId_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_Poster +// ---------------------------- +db.getCollection("cunkebao_Poster").drop(); +db.createCollection("cunkebao_Poster"); +db.getCollection("cunkebao_Poster").createIndex({ + lId: NumberInt("1") +}, { + name: "lId_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_SysAttach +// ---------------------------- +db.getCollection("cunkebao_SysAttach").drop(); +db.createCollection("cunkebao_SysAttach"); + +// ---------------------------- +// Collection structure for cunkebao_SysAttach_20250228 +// ---------------------------- +db.getCollection("cunkebao_SysAttach_20250228").drop(); +db.createCollection("cunkebao_SysAttach_20250228"); + +// ---------------------------- +// Collection structure for cunkebao_SysAttach_copy +// ---------------------------- +db.getCollection("cunkebao_SysAttach_copy").drop(); +db.createCollection("cunkebao_SysAttach_copy"); + +// ---------------------------- +// Collection structure for cunkebao_SysDataSource +// ---------------------------- +db.getCollection("cunkebao_SysDataSource").drop(); +db.createCollection("cunkebao_SysDataSource"); + +// ---------------------------- +// Collection structure for cunkebao_SysModule +// ---------------------------- +db.getCollection("cunkebao_SysModule").drop(); +db.createCollection("cunkebao_SysModule"); + +// ---------------------------- +// Collection structure for cunkebao_SysNavItem +// ---------------------------- +db.getCollection("cunkebao_SysNavItem").drop(); +db.createCollection("cunkebao_SysNavItem"); + +// ---------------------------- +// Collection structure for cunkebao_SysObject +// ---------------------------- +db.getCollection("cunkebao_SysObject").drop(); +db.createCollection("cunkebao_SysObject"); + +// ---------------------------- +// Collection structure for cunkebao_SysOperatorPermission +// ---------------------------- +db.getCollection("cunkebao_SysOperatorPermission").drop(); +db.createCollection("cunkebao_SysOperatorPermission"); + +// ---------------------------- +// Collection structure for cunkebao_SysRelatedList +// ---------------------------- +db.getCollection("cunkebao_SysRelatedList").drop(); +db.createCollection("cunkebao_SysRelatedList"); + +// ---------------------------- +// Collection structure for cunkebao_SysShare +// ---------------------------- +db.getCollection("cunkebao_SysShare").drop(); +db.createCollection("cunkebao_SysShare"); + +// ---------------------------- +// Collection structure for cunkebao_SysSolution +// ---------------------------- +db.getCollection("cunkebao_SysSolution").drop(); +db.createCollection("cunkebao_SysSolution"); + +// ---------------------------- +// Collection structure for cunkebao_SysUI +// ---------------------------- +db.getCollection("cunkebao_SysUI").drop(); +db.createCollection("cunkebao_SysUI"); + +// ---------------------------- +// Collection structure for cunkebao_UserIncome +// ---------------------------- +db.getCollection("cunkebao_UserIncome").drop(); +db.createCollection("cunkebao_UserIncome"); +db.getCollection("cunkebao_UserIncome").createIndex({ + lId: NumberInt("1") +}, { + name: "lId_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_ck_administrator_permissions +// ---------------------------- +db.getCollection("cunkebao_v3_ck_administrator_permissions").drop(); +db.createCollection("cunkebao_v3_ck_administrator_permissions"); +db.getCollection("cunkebao_v3_ck_administrator_permissions").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_ck_administrators +// ---------------------------- +db.getCollection("cunkebao_v3_ck_administrators").drop(); +db.createCollection("cunkebao_v3_ck_administrators"); +db.getCollection("cunkebao_v3_ck_administrators").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_ck_call_recording +// ---------------------------- +db.getCollection("cunkebao_v3_ck_call_recording").drop(); +db.createCollection("cunkebao_v3_ck_call_recording"); +db.getCollection("cunkebao_v3_ck_call_recording").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_ck_company +// ---------------------------- +db.getCollection("cunkebao_v3_ck_company").drop(); +db.createCollection("cunkebao_v3_ck_company"); +db.getCollection("cunkebao_v3_ck_company").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_ck_customer_acquisition_task +// ---------------------------- +db.getCollection("cunkebao_v3_ck_customer_acquisition_task").drop(); +db.createCollection("cunkebao_v3_ck_customer_acquisition_task"); +db.getCollection("cunkebao_v3_ck_customer_acquisition_task").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_ck_flow_package_order +// ---------------------------- +db.getCollection("cunkebao_v3_ck_flow_package_order").drop(); +db.createCollection("cunkebao_v3_ck_flow_package_order"); +db.getCollection("cunkebao_v3_ck_flow_package_order").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_ck_flow_usage_record +// ---------------------------- +db.getCollection("cunkebao_v3_ck_flow_usage_record").drop(); +db.createCollection("cunkebao_v3_ck_flow_usage_record"); +db.getCollection("cunkebao_v3_ck_flow_usage_record").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_ck_jd_promotion_site +// ---------------------------- +db.getCollection("cunkebao_v3_ck_jd_promotion_site").drop(); +db.createCollection("cunkebao_v3_ck_jd_promotion_site"); +db.getCollection("cunkebao_v3_ck_jd_promotion_site").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_ck_jd_social_media +// ---------------------------- +db.getCollection("cunkebao_v3_ck_jd_social_media").drop(); +db.createCollection("cunkebao_v3_ck_jd_social_media"); +db.getCollection("cunkebao_v3_ck_jd_social_media").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_ck_menus +// ---------------------------- +db.getCollection("cunkebao_v3_ck_menus").drop(); +db.createCollection("cunkebao_v3_ck_menus"); +db.getCollection("cunkebao_v3_ck_menus").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_ck_plan_scene +// ---------------------------- +db.getCollection("cunkebao_v3_ck_plan_scene").drop(); +db.createCollection("cunkebao_v3_ck_plan_scene"); +db.getCollection("cunkebao_v3_ck_plan_scene").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_ck_plan_tags +// ---------------------------- +db.getCollection("cunkebao_v3_ck_plan_tags").drop(); +db.createCollection("cunkebao_v3_ck_plan_tags"); +db.getCollection("cunkebao_v3_ck_plan_tags").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_ck_task_customer +// ---------------------------- +db.getCollection("cunkebao_v3_ck_task_customer").drop(); +db.createCollection("cunkebao_v3_ck_task_customer"); +db.getCollection("cunkebao_v3_ck_task_customer").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_ck_traffic_pool +// ---------------------------- +db.getCollection("cunkebao_v3_ck_traffic_pool").drop(); +db.createCollection("cunkebao_v3_ck_traffic_pool"); +db.getCollection("cunkebao_v3_ck_traffic_pool").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_ck_traffic_source +// ---------------------------- +db.getCollection("cunkebao_v3_ck_traffic_source").drop(); +db.createCollection("cunkebao_v3_ck_traffic_source"); +db.getCollection("cunkebao_v3_ck_traffic_source").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_ck_traffic_source_package +// ---------------------------- +db.getCollection("cunkebao_v3_ck_traffic_source_package").drop(); +db.createCollection("cunkebao_v3_ck_traffic_source_package"); +db.getCollection("cunkebao_v3_ck_traffic_source_package").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_ck_traffic_source_package_item +// ---------------------------- +db.getCollection("cunkebao_v3_ck_traffic_source_package_item").drop(); +db.createCollection("cunkebao_v3_ck_traffic_source_package_item"); +db.getCollection("cunkebao_v3_ck_traffic_source_package_item").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_ck_traffic_tag +// ---------------------------- +db.getCollection("cunkebao_v3_ck_traffic_tag").drop(); +db.createCollection("cunkebao_v3_ck_traffic_tag"); +db.getCollection("cunkebao_v3_ck_traffic_tag").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_ck_user_flow_package +// ---------------------------- +db.getCollection("cunkebao_v3_ck_user_flow_package").drop(); +db.createCollection("cunkebao_v3_ck_user_flow_package"); +db.getCollection("cunkebao_v3_ck_user_flow_package").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_ck_user_portrait +// ---------------------------- +db.getCollection("cunkebao_v3_ck_user_portrait").drop(); +db.createCollection("cunkebao_v3_ck_user_portrait"); +db.getCollection("cunkebao_v3_ck_user_portrait").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_ck_vendor_project +// ---------------------------- +db.getCollection("cunkebao_v3_ck_vendor_project").drop(); +db.createCollection("cunkebao_v3_ck_vendor_project"); +db.getCollection("cunkebao_v3_ck_vendor_project").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_ck_wechat_friendship +// ---------------------------- +db.getCollection("cunkebao_v3_ck_wechat_friendship").drop(); +db.createCollection("cunkebao_v3_ck_wechat_friendship"); +db.getCollection("cunkebao_v3_ck_wechat_friendship").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_ck_wechat_group_member +// ---------------------------- +db.getCollection("cunkebao_v3_ck_wechat_group_member").drop(); +db.createCollection("cunkebao_v3_ck_wechat_group_member"); +db.getCollection("cunkebao_v3_ck_wechat_group_member").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_ck_wechat_tag +// ---------------------------- +db.getCollection("cunkebao_v3_ck_wechat_tag").drop(); +db.createCollection("cunkebao_v3_ck_wechat_tag"); +db.getCollection("cunkebao_v3_ck_wechat_tag").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_ck_workbench +// ---------------------------- +db.getCollection("cunkebao_v3_ck_workbench").drop(); +db.createCollection("cunkebao_v3_ck_workbench"); +db.getCollection("cunkebao_v3_ck_workbench").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_ck_workbench_group_create_item +// ---------------------------- +db.getCollection("cunkebao_v3_ck_workbench_group_create_item").drop(); +db.createCollection("cunkebao_v3_ck_workbench_group_create_item"); +db.getCollection("cunkebao_v3_ck_workbench_group_create_item").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_ck_workbench_group_push +// ---------------------------- +db.getCollection("cunkebao_v3_ck_workbench_group_push").drop(); +db.createCollection("cunkebao_v3_ck_workbench_group_push"); +db.getCollection("cunkebao_v3_ck_workbench_group_push").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_ck_workbench_group_push_item +// ---------------------------- +db.getCollection("cunkebao_v3_ck_workbench_group_push_item").drop(); +db.createCollection("cunkebao_v3_ck_workbench_group_push_item"); +db.getCollection("cunkebao_v3_ck_workbench_group_push_item").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_s2_allot_rule +// ---------------------------- +db.getCollection("cunkebao_v3_s2_allot_rule").drop(); +db.createCollection("cunkebao_v3_s2_allot_rule"); +db.getCollection("cunkebao_v3_s2_allot_rule").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for cunkebao_v3_s2_device_group +// ---------------------------- +db.getCollection("cunkebao_v3_s2_device_group").drop(); +db.createCollection("cunkebao_v3_s2_device_group"); +db.getCollection("cunkebao_v3_s2_device_group").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); + +// ---------------------------- +// Collection structure for system.profile +// ---------------------------- +db.getCollection("system.profile").drop(); +db.createCollection("system.profile",{ + capped: true, + size: 1048576 +}); + +// ---------------------------- +// Collection structure for 好友关系网络汇总表 +// ---------------------------- +db.getCollection("好友关系网络汇总表").drop(); +db.createCollection("好友关系网络汇总表"); +db.getCollection("好友关系网络汇总表").createIndex({ + "用户微信ID": NumberInt("1") +}, { + name: "用户微信ID_1" +}); +db.getCollection("好友关系网络汇总表").createIndex({ + "好友微信ID": NumberInt("1") +}, { + name: "好友微信ID_1" +}); +db.getCollection("好友关系网络汇总表").createIndex({ + "关系等级": NumberInt("1") +}, { + name: "关系等级_1" +}); +db.getCollection("好友关系网络汇总表").createIndex({ + "RFM评分.总分": NumberInt("1") +}, { + name: "RFM评分.总分_1" +}); +db.getCollection("好友关系网络汇总表").createIndex({ + "关系强度评分": NumberInt("1") +}, { + name: "关系强度评分_1" +}); +db.getCollection("好友关系网络汇总表").createIndex({ + "公司ID": NumberInt("1") +}, { + name: "公司ID_1" +}); diff --git a/Moncter/数据库/KR_存客宝_四表重构KR_KR版.js b/Moncter/数据库/KR_存客宝_四表重构KR_KR版.js new file mode 100644 index 000000000..06b262137 --- /dev/null +++ b/Moncter/数据库/KR_存客宝_四表重构KR_KR版.js @@ -0,0 +1,65 @@ +/* + Navicat Premium Data Transfer + + Source Server : 标签数据库 + Source Server Type : MongoDB + Source Server Version : 60025 (6.0.25) + Source Host : 192.168.2.6:27017 + Source Schema : KR_存客宝_四表重构KR_KR版 + + Target Server Type : MongoDB + Target Server Version : 60025 (6.0.25) + File Encoding : 65001 + + Date: 19/11/2025 15:18:48 +*/ + + +// ---------------------------- +// Collection structure for system.profile +// ---------------------------- +db.getCollection("system.profile").drop(); +db.createCollection("system.profile",{ + capped: true, + size: 1048576 +}); + +// ---------------------------- +// Collection structure for 估值模式表 +// ---------------------------- +db.getCollection("估值模式表").drop(); +db.createCollection("估值模式表"); + +// ---------------------------- +// Collection structure for 微信号管理表 +// ---------------------------- +db.getCollection("微信号管理表").drop(); +db.createCollection("微信号管理表"); + +// ---------------------------- +// Collection structure for 微信好友表 +// ---------------------------- +db.getCollection("微信好友表").drop(); +db.createCollection("微信好友表"); + +// ---------------------------- +// Collection structure for 设备管理表 +// ---------------------------- +db.getCollection("设备管理表").drop(); +db.createCollection("设备管理表"); +db.getCollection("设备管理表").createIndex({ + "设备ID": NumberInt("1") +}, { + name: "设备ID_1", + unique: true +}); +db.getCollection("设备管理表").createIndex({ + "用户ID": NumberInt("1") +}, { + name: "用户ID_1" +}); +db.getCollection("设备管理表").createIndex({ + "设备类型": NumberInt("1") +}, { + name: "设备类型_1" +}); diff --git a/Moncter/数据库/KR_微博.js b/Moncter/数据库/KR_微博.js new file mode 100644 index 000000000..1b5c053a1 --- /dev/null +++ b/Moncter/数据库/KR_微博.js @@ -0,0 +1,36 @@ +/* + Navicat Premium Data Transfer + + Source Server : 标签数据库 + Source Server Type : MongoDB + Source Server Version : 60025 (6.0.25) + Source Host : 192.168.2.6:27017 + Source Schema : KR_微博 + + Target Server Type : MongoDB + Target Server Version : 60025 (6.0.25) + File Encoding : 65001 + + Date: 19/11/2025 15:20:57 +*/ + + +// ---------------------------- +// Collection structure for system.profile +// ---------------------------- +db.getCollection("system.profile").drop(); +db.createCollection("system.profile",{ + capped: true, + size: 1048576 +}); + +// ---------------------------- +// Collection structure for 微博uid+手机 +// ---------------------------- +db.getCollection("微博uid+手机").drop(); +db.createCollection("微博uid+手机"); +db.getCollection("微博uid+手机").createIndex({ + phone: NumberInt("1") +}, { + name: "phone_1" +}); diff --git a/Moncter/数据库/KR_快递.js b/Moncter/数据库/KR_快递.js new file mode 100644 index 000000000..de92805d6 --- /dev/null +++ b/Moncter/数据库/KR_快递.js @@ -0,0 +1,90 @@ +/* + Navicat Premium Data Transfer + + Source Server : 标签数据库 + Source Server Type : MongoDB + Source Server Version : 60025 (6.0.25) + Source Host : 192.168.2.6:27017 + Source Schema : KR_快递 + + Target Server Type : MongoDB + Target Server Version : 60025 (6.0.25) + File Encoding : 65001 + + Date: 19/11/2025 15:19:31 +*/ + + +// ---------------------------- +// Collection structure for system.profile +// ---------------------------- +db.getCollection("system.profile").drop(); +db.createCollection("system.profile",{ + capped: true, + size: 1048576 +}); + +// ---------------------------- +// Collection structure for wlzhwu_jiezhang +// ---------------------------- +db.getCollection("wlzhwu_jiezhang").drop(); +db.createCollection("wlzhwu_jiezhang"); + +// ---------------------------- +// Collection structure for wlzhwu_wlzhangwu +// ---------------------------- +db.getCollection("wlzhwu_wlzhangwu").drop(); +db.createCollection("wlzhwu_wlzhangwu"); + +// ---------------------------- +// Collection structure for 可行路由 +// ---------------------------- +db.getCollection("可行路由").drop(); +db.createCollection("可行路由"); +db.getCollection("可行路由").createIndex({ + _import_time: NumberInt("1") +}, { + name: "_import_time_1" +}); + +// ---------------------------- +// Collection structure for 圆通_普通客户 +// ---------------------------- +db.getCollection("圆通_普通客户").drop(); +db.createCollection("圆通_普通客户"); +db.getCollection("圆通_普通客户").createIndex({ + username: NumberInt("1") +}, { + name: "username_1" +}); +db.getCollection("圆通_普通客户").createIndex({ + phone: NumberInt("1") +}, { + name: "phone_1" +}); +db.getCollection("圆通_普通客户").createIndex({ + user_score: NumberInt("-1") +}, { + name: "user_score_-1" +}); + +// ---------------------------- +// Collection structure for 圆通_潜在客户 +// ---------------------------- +db.getCollection("圆通_潜在客户").drop(); +db.createCollection("圆通_潜在客户"); +db.getCollection("圆通_潜在客户").createIndex({ + username: NumberInt("1") +}, { + name: "username_1" +}); +db.getCollection("圆通_潜在客户").createIndex({ + phone: NumberInt("1") +}, { + name: "phone_1" +}); +db.getCollection("圆通_潜在客户").createIndex({ + user_score: NumberInt("-1") +}, { + name: "user_score_-1" +}); diff --git a/Moncter/数据库/KR_户口.js b/Moncter/数据库/KR_户口.js new file mode 100644 index 000000000..e8ea0de3b --- /dev/null +++ b/Moncter/数据库/KR_户口.js @@ -0,0 +1,46 @@ +/* + Navicat Premium Data Transfer + + Source Server : 标签数据库 + Source Server Type : MongoDB + Source Server Version : 60025 (6.0.25) + Source Host : 192.168.2.6:27017 + Source Schema : KR_户口 + + Target Server Type : MongoDB + Target Server Version : 60025 (6.0.25) + File Encoding : 65001 + + Date: 19/11/2025 15:19:03 +*/ + + +// ---------------------------- +// Collection structure for system.profile +// ---------------------------- +db.getCollection("system.profile").drop(); +db.createCollection("system.profile",{ + capped: true, + size: 1048576 +}); + +// ---------------------------- +// Collection structure for 户籍数据 +// ---------------------------- +db.getCollection("户籍数据").drop(); +db.createCollection("户籍数据"); +db.getCollection("户籍数据").createIndex({ + "姓名": NumberInt("1") +}, { + name: "姓名_1" +}); +db.getCollection("户籍数据").createIndex({ + "身份证号": NumberInt("1") +}, { + name: "身份证号_1" +}); +db.getCollection("户籍数据").createIndex({ + "户籍地址": NumberInt("1") +}, { + name: "户籍地址_1" +}); diff --git a/Moncter/数据库/KR_手机.js b/Moncter/数据库/KR_手机.js new file mode 100644 index 000000000..b1edf8ca5 --- /dev/null +++ b/Moncter/数据库/KR_手机.js @@ -0,0 +1,186 @@ +/* + Navicat Premium Data Transfer + + Source Server : 标签数据库 + Source Server Type : MongoDB + Source Server Version : 60025 (6.0.25) + Source Host : 192.168.2.6:27017 + Source Schema : KR_手机 + + Target Server Type : MongoDB + Target Server Version : 60025 (6.0.25) + File Encoding : 65001 + + Date: 19/11/2025 15:20:20 +*/ + + +// ---------------------------- +// Collection structure for system.profile +// ---------------------------- +db.getCollection("system.profile").drop(); +db.createCollection("system.profile",{ + capped: true, + size: 1048576 +}); + +// ---------------------------- +// Collection structure for tmpgSyh4.renameCollection +// ---------------------------- +db.getCollection("tmpgSyh4.renameCollection").drop(); +db.createCollection("tmpgSyh4.renameCollection"); + +// ---------------------------- +// Collection structure for 广东gd5800万神州行动感地带=广州GZBran2_copy2 +// ---------------------------- +db.getCollection("广东gd5800万神州行动感地带=广州GZBran2_copy2").drop(); +db.createCollection("广东gd5800万神州行动感地带=广州GZBran2_copy2"); + +// ---------------------------- +// Collection structure for 广东广州航空数据1 +// ---------------------------- +db.getCollection("广东广州航空数据1").drop(); +db.createCollection("广东广州航空数据1"); + +// ---------------------------- +// Collection structure for 广东广州航空数据2 +// ---------------------------- +db.getCollection("广东广州航空数据2").drop(); +db.createCollection("广东广州航空数据2"); + +// ---------------------------- +// Collection structure for 广东广州航空数据3 +// ---------------------------- +db.getCollection("广东广州航空数据3").drop(); +db.createCollection("广东广州航空数据3"); + +// ---------------------------- +// Collection structure for 广东广州航空数据4 +// ---------------------------- +db.getCollection("广东广州航空数据4").drop(); +db.createCollection("广东广州航空数据4"); +db.getCollection("广东广州航空数据4").createIndex({ + "电话号码": NumberInt("1") +}, { + name: "电话号码_1" +}); + +// ---------------------------- +// Collection structure for 广东深圳全球通女 +// ---------------------------- +db.getCollection("广东深圳全球通女").drop(); +db.createCollection("广东深圳全球通女"); + +// ---------------------------- +// Collection structure for 江西移动_701鹰潭 +// ---------------------------- +db.getCollection("江西移动_701鹰潭").drop(); +db.createCollection("江西移动_701鹰潭"); + +// ---------------------------- +// Collection structure for 江西移动_792九江 +// ---------------------------- +db.getCollection("江西移动_792九江").drop(); +db.createCollection("江西移动_792九江"); + +// ---------------------------- +// Collection structure for 江西移动_793上饶 +// ---------------------------- +db.getCollection("江西移动_793上饶").drop(); +db.createCollection("江西移动_793上饶"); + +// ---------------------------- +// Collection structure for 江西移动_798景德镇 +// ---------------------------- +db.getCollection("江西移动_798景德镇").drop(); +db.createCollection("江西移动_798景德镇"); + +// ---------------------------- +// Collection structure for 江西移动_799萍乡 +// ---------------------------- +db.getCollection("江西移动_799萍乡").drop(); +db.createCollection("江西移动_799萍乡"); + +// ---------------------------- +// Collection structure for 江西移动_上绕262万 +// ---------------------------- +db.getCollection("江西移动_上绕262万").drop(); +db.createCollection("江西移动_上绕262万"); + +// ---------------------------- +// Collection structure for 江西移动_九江222万 +// ---------------------------- +db.getCollection("江西移动_九江222万").drop(); +db.createCollection("江西移动_九江222万"); + +// ---------------------------- +// Collection structure for 江西移动_南昌368万 +// ---------------------------- +db.getCollection("江西移动_南昌368万").drop(); +db.createCollection("江西移动_南昌368万"); + +// ---------------------------- +// Collection structure for 江西移动_吉安206万 +// ---------------------------- +db.getCollection("江西移动_吉安206万").drop(); +db.createCollection("江西移动_吉安206万"); + +// ---------------------------- +// Collection structure for 江西移动_宜春230万 +// ---------------------------- +db.getCollection("江西移动_宜春230万").drop(); +db.createCollection("江西移动_宜春230万"); + +// ---------------------------- +// Collection structure for 江西移动_抚州143万 +// ---------------------------- +db.getCollection("江西移动_抚州143万").drop(); +db.createCollection("江西移动_抚州143万"); + +// ---------------------------- +// Collection structure for 江西移动_新余67万 +// ---------------------------- +db.getCollection("江西移动_新余67万").drop(); +db.createCollection("江西移动_新余67万"); + +// ---------------------------- +// Collection structure for 江西移动_景德镇76万 +// ---------------------------- +db.getCollection("江西移动_景德镇76万").drop(); +db.createCollection("江西移动_景德镇76万"); + +// ---------------------------- +// Collection structure for 江西移动_江西全球通带身份证 +// ---------------------------- +db.getCollection("江西移动_江西全球通带身份证").drop(); +db.createCollection("江西移动_江西全球通带身份证"); + +// ---------------------------- +// Collection structure for 江西移动_江西全球通身份证 +// ---------------------------- +db.getCollection("江西移动_江西全球通身份证").drop(); +db.createCollection("江西移动_江西全球通身份证"); + +// ---------------------------- +// Collection structure for 江西移动_江西全球通身份证姓名1226056 +// ---------------------------- +db.getCollection("江西移动_江西全球通身份证姓名1226056").drop(); +db.createCollection("江西移动_江西全球通身份证姓名1226056"); + +// ---------------------------- +// Collection structure for 江西移动_萍乡86万 +// ---------------------------- +db.getCollection("江西移动_萍乡86万").drop(); +db.createCollection("江西移动_萍乡86万"); + +// ---------------------------- +// Collection structure for 江西移动_赣州352万 +// ---------------------------- +db.getCollection("江西移动_赣州352万").drop(); +db.createCollection("江西移动_赣州352万"); + +// ---------------------------- +// Collection structure for 江西移动_鹰潭53万 +// ---------------------------- +db.getCollection("江西移动_鹰潭53万").drop(); +db.createCollection("江西移动_鹰潭53万"); diff --git a/Moncter/数据库/KR_投资.js b/Moncter/数据库/KR_投资.js new file mode 100644 index 000000000..250de32bc --- /dev/null +++ b/Moncter/数据库/KR_投资.js @@ -0,0 +1,223 @@ +/* + Navicat Premium Data Transfer + + Source Server : 标签数据库 + Source Server Type : MongoDB + Source Server Version : 60025 (6.0.25) + Source Host : 192.168.2.6:27017 + Source Schema : KR_投资 + + Target Server Type : MongoDB + Target Server Version : 60025 (6.0.25) + File Encoding : 65001 + + Date: 19/11/2025 15:20:51 +*/ + + +// ---------------------------- +// Collection structure for system.profile +// ---------------------------- +db.getCollection("system.profile").drop(); +db.createCollection("system.profile",{ + capped: true, + size: 1048576 +}); + +// ---------------------------- +// Collection structure for 上海 +// ---------------------------- +db.getCollection("上海").drop(); +db.createCollection("上海"); + +// ---------------------------- +// Collection structure for 云南 +// ---------------------------- +db.getCollection("云南").drop(); +db.createCollection("云南"); + +// ---------------------------- +// Collection structure for 内蒙古 +// ---------------------------- +db.getCollection("内蒙古").drop(); +db.createCollection("内蒙古"); + +// ---------------------------- +// Collection structure for 北京 +// ---------------------------- +db.getCollection("北京").drop(); +db.createCollection("北京"); + +// ---------------------------- +// Collection structure for 吉林 +// ---------------------------- +db.getCollection("吉林").drop(); +db.createCollection("吉林"); + +// ---------------------------- +// Collection structure for 商务部世界买家数据库 此数据由www_10195_com提供 +// ---------------------------- +db.getCollection("商务部世界买家数据库 此数据由www_10195_com提供").drop(); +db.createCollection("商务部世界买家数据库 此数据由www_10195_com提供"); + +// ---------------------------- +// Collection structure for 四川 +// ---------------------------- +db.getCollection("四川").drop(); +db.createCollection("四川"); + +// ---------------------------- +// Collection structure for 天津 +// ---------------------------- +db.getCollection("天津").drop(); +db.createCollection("天津"); + +// ---------------------------- +// Collection structure for 宁夏 +// ---------------------------- +db.getCollection("宁夏").drop(); +db.createCollection("宁夏"); + +// ---------------------------- +// Collection structure for 安徽 +// ---------------------------- +db.getCollection("安徽").drop(); +db.createCollection("安徽"); + +// ---------------------------- +// Collection structure for 山东 +// ---------------------------- +db.getCollection("山东").drop(); +db.createCollection("山东"); + +// ---------------------------- +// Collection structure for 山西 +// ---------------------------- +db.getCollection("山西").drop(); +db.createCollection("山西"); + +// ---------------------------- +// Collection structure for 广东 +// ---------------------------- +db.getCollection("广东").drop(); +db.createCollection("广东"); + +// ---------------------------- +// Collection structure for 广西 +// ---------------------------- +db.getCollection("广西").drop(); +db.createCollection("广西"); + +// ---------------------------- +// Collection structure for 新疆 +// ---------------------------- +db.getCollection("新疆").drop(); +db.createCollection("新疆"); + +// ---------------------------- +// Collection structure for 江苏 +// ---------------------------- +db.getCollection("江苏").drop(); +db.createCollection("江苏"); + +// ---------------------------- +// Collection structure for 江西 +// ---------------------------- +db.getCollection("江西").drop(); +db.createCollection("江西"); + +// ---------------------------- +// Collection structure for 河北 +// ---------------------------- +db.getCollection("河北").drop(); +db.createCollection("河北"); + +// ---------------------------- +// Collection structure for 河南 +// ---------------------------- +db.getCollection("河南").drop(); +db.createCollection("河南"); + +// ---------------------------- +// Collection structure for 浙江 +// ---------------------------- +db.getCollection("浙江").drop(); +db.createCollection("浙江"); + +// ---------------------------- +// Collection structure for 海南 +// ---------------------------- +db.getCollection("海南").drop(); +db.createCollection("海南"); + +// ---------------------------- +// Collection structure for 湖北 +// ---------------------------- +db.getCollection("湖北").drop(); +db.createCollection("湖北"); + +// ---------------------------- +// Collection structure for 湖南 +// ---------------------------- +db.getCollection("湖南").drop(); +db.createCollection("湖南"); + +// ---------------------------- +// Collection structure for 甘肃 +// ---------------------------- +db.getCollection("甘肃").drop(); +db.createCollection("甘肃"); + +// ---------------------------- +// Collection structure for 省份 +// ---------------------------- +db.getCollection("省份").drop(); +db.createCollection("省份"); + +// ---------------------------- +// Collection structure for 福建 +// ---------------------------- +db.getCollection("福建").drop(); +db.createCollection("福建"); + +// ---------------------------- +// Collection structure for 西藏 +// ---------------------------- +db.getCollection("西藏").drop(); +db.createCollection("西藏"); + +// ---------------------------- +// Collection structure for 贵州 +// ---------------------------- +db.getCollection("贵州").drop(); +db.createCollection("贵州"); + +// ---------------------------- +// Collection structure for 辽宁 +// ---------------------------- +db.getCollection("辽宁").drop(); +db.createCollection("辽宁"); + +// ---------------------------- +// Collection structure for 重庆 +// ---------------------------- +db.getCollection("重庆").drop(); +db.createCollection("重庆"); + +// ---------------------------- +// Collection structure for 陕西 +// ---------------------------- +db.getCollection("陕西").drop(); +db.createCollection("陕西"); + +// ---------------------------- +// Collection structure for 青海 +// ---------------------------- +db.getCollection("青海").drop(); +db.createCollection("青海"); + +// ---------------------------- +// Collection structure for 黑龙江 +// ---------------------------- +db.getCollection("黑龙江").drop(); +db.createCollection("黑龙江"); diff --git a/Moncter/数据库/KR_淘宝.js b/Moncter/数据库/KR_淘宝.js new file mode 100644 index 000000000..d5df6b276 --- /dev/null +++ b/Moncter/数据库/KR_淘宝.js @@ -0,0 +1,223 @@ +/* + Navicat Premium Data Transfer + + Source Server : 标签数据库 + Source Server Type : MongoDB + Source Server Version : 60025 (6.0.25) + Source Host : 192.168.2.6:27017 + Source Schema : KR_淘宝 + + Target Server Type : MongoDB + Target Server Version : 60025 (6.0.25) + File Encoding : 65001 + + Date: 19/11/2025 15:20:35 +*/ + + +// ---------------------------- +// Collection structure for system.profile +// ---------------------------- +db.getCollection("system.profile").drop(); +db.createCollection("system.profile",{ + capped: true, + size: 1048576 +}); + +// ---------------------------- +// Collection structure for zippo1(去重复后33万) +// ---------------------------- +db.getCollection("zippo1(去重复后33万)").drop(); +db.createCollection("zippo1(去重复后33万)"); + +// ---------------------------- +// Collection structure for zippo2 +// ---------------------------- +db.getCollection("zippo2").drop(); +db.createCollection("zippo2"); + +// ---------------------------- +// Collection structure for zippo3 +// ---------------------------- +db.getCollection("zippo3").drop(); +db.createCollection("zippo3"); + +// ---------------------------- +// Collection structure for zippo4 +// ---------------------------- +db.getCollection("zippo4").drop(); +db.createCollection("zippo4"); + +// ---------------------------- +// Collection structure for zippo5 +// ---------------------------- +db.getCollection("zippo5").drop(); +db.createCollection("zippo5"); + +// ---------------------------- +// Collection structure for 办公设备文具(去重复64.5万) +// ---------------------------- +db.getCollection("办公设备文具(去重复64.5万)").drop(); +db.createCollection("办公设备文具(去重复64.5万)"); + +// ---------------------------- +// Collection structure for 包(去重复后71万) +// ---------------------------- +db.getCollection("包(去重复后71万)").drop(); +db.createCollection("包(去重复后71万)"); + +// ---------------------------- +// Collection structure for 卖家邮箱(去重复后300万) +// ---------------------------- +db.getCollection("卖家邮箱(去重复后300万)").drop(); +db.createCollection("卖家邮箱(去重复后300万)"); + +// ---------------------------- +// Collection structure for 女士内衣(去重复后132.6万) +// ---------------------------- +db.getCollection("女士内衣(去重复后132.6万)").drop(); +db.createCollection("女士内衣(去重复后132.6万)"); + +// ---------------------------- +// Collection structure for 女士箱包配件(去重复后34.8万) +// ---------------------------- +db.getCollection("女士箱包配件(去重复后34.8万)").drop(); +db.createCollection("女士箱包配件(去重复后34.8万)"); + +// ---------------------------- +// Collection structure for 女装1(去重复后388.6万) +// ---------------------------- +db.getCollection("女装1(去重复后388.6万)").drop(); +db.createCollection("女装1(去重复后388.6万)"); + +// ---------------------------- +// Collection structure for 女装2(去重复后83万,带旺旺号及支付宝) +// ---------------------------- +db.getCollection("女装2(去重复后83万,带旺旺号及支付宝)").drop(); +db.createCollection("女装2(去重复后83万,带旺旺号及支付宝)"); + +// ---------------------------- +// Collection structure for 女鞋(去重复后118.6万) +// ---------------------------- +db.getCollection("女鞋(去重复后118.6万)").drop(); +db.createCollection("女鞋(去重复后118.6万)"); + +// ---------------------------- +// Collection structure for 家具定制代购(去重复后20万) +// ---------------------------- +db.getCollection("家具定制代购(去重复后20万)").drop(); +db.createCollection("家具定制代购(去重复后20万)"); + +// ---------------------------- +// Collection structure for 家用电器(去重复后93.5万) +// ---------------------------- +db.getCollection("家用电器(去重复后93.5万)").drop(); +db.createCollection("家用电器(去重复后93.5万)"); + +// ---------------------------- +// Collection structure for 床上用品(去重复后19万) +// ---------------------------- +db.getCollection("床上用品(去重复后19万)").drop(); +db.createCollection("床上用品(去重复后19万)"); + +// ---------------------------- +// Collection structure for 彩妆护肤(去重复后138万) +// ---------------------------- +db.getCollection("彩妆护肤(去重复后138万)").drop(); +db.createCollection("彩妆护肤(去重复后138万)"); + +// ---------------------------- +// Collection structure for 成人用品避孕用品情趣内衣(去重复后8万) +// ---------------------------- +db.getCollection("成人用品避孕用品情趣内衣(去重复后8万)").drop(); +db.createCollection("成人用品避孕用品情趣内衣(去重复后8万)"); + +// ---------------------------- +// Collection structure for 户外军品旅游机票(去重复后64万) +// ---------------------------- +db.getCollection("户外军品旅游机票(去重复后64万)").drop(); +db.createCollection("户外军品旅游机票(去重复后64万)"); + +// ---------------------------- +// Collection structure for 手机(去重复后80万) +// ---------------------------- +db.getCollection("手机(去重复后80万)").drop(); +db.createCollection("手机(去重复后80万)"); + +// ---------------------------- +// Collection structure for 数码相机图形冲印(去重复后23.8万) +// ---------------------------- +db.getCollection("数码相机图形冲印(去重复后23.8万)").drop(); +db.createCollection("数码相机图形冲印(去重复后23.8万)"); + +// ---------------------------- +// Collection structure for 数码配件电子元件(去重复后51.6万) +// ---------------------------- +db.getCollection("数码配件电子元件(去重复后51.6万)").drop(); +db.createCollection("数码配件电子元件(去重复后51.6万)"); + +// ---------------------------- +// Collection structure for 时尚家饰工艺品(去重复后41万) +// ---------------------------- +db.getCollection("时尚家饰工艺品(去重复后41万)").drop(); +db.createCollection("时尚家饰工艺品(去重复后41万)"); + +// ---------------------------- +// Collection structure for 汽车配件(去重复后38.6万) +// ---------------------------- +db.getCollection("汽车配件(去重复后38.6万)").drop(); +db.createCollection("汽车配件(去重复后38.6万)"); + +// ---------------------------- +// Collection structure for 珠宝钻石翡翠(去重复后44.5万) +// ---------------------------- +db.getCollection("珠宝钻石翡翠(去重复后44.5万)").drop(); +db.createCollection("珠宝钻石翡翠(去重复后44.5万)"); + +// ---------------------------- +// Collection structure for 电脑硬件台式机网络设备(去重复后20万) +// ---------------------------- +db.getCollection("电脑硬件台式机网络设备(去重复后20万)").drop(); +db.createCollection("电脑硬件台式机网络设备(去重复后20万)"); + +// ---------------------------- +// Collection structure for 男装(去重复后216万) +// ---------------------------- +db.getCollection("男装(去重复后216万)").drop(); +db.createCollection("男装(去重复后216万)"); + +// ---------------------------- +// Collection structure for 童装婴儿服鞋帽(去重复后52万) +// ---------------------------- +db.getCollection("童装婴儿服鞋帽(去重复后52万)").drop(); +db.createCollection("童装婴儿服鞋帽(去重复后52万)"); + +// ---------------------------- +// Collection structure for 笔记本(去重复后32万) +// ---------------------------- +db.getCollection("笔记本(去重复后32万)").drop(); +db.createCollection("笔记本(去重复后32万)"); + +// ---------------------------- +// Collection structure for 网络服务电脑软件(去重复后17万) +// ---------------------------- +db.getCollection("网络服务电脑软件(去重复后17万)").drop(); +db.createCollection("网络服务电脑软件(去重复后17万)"); + +// ---------------------------- +// Collection structure for 装潢灯具五金(去重复后107万) +// ---------------------------- +db.getCollection("装潢灯具五金(去重复后107万)").drop(); +db.createCollection("装潢灯具五金(去重复后107万)"); + +// ---------------------------- +// Collection structure for 食品保健(去重复后84.5万) +// ---------------------------- +db.getCollection("食品保健(去重复后84.5万)").drop(); +db.createCollection("食品保健(去重复后84.5万)"); + +// ---------------------------- +// Collection structure for 饰品(去重复14.7万) +// ---------------------------- +db.getCollection("饰品(去重复14.7万)").drop(); +db.createCollection("饰品(去重复14.7万)"); diff --git a/Moncter/数据库/KR_游戏.js b/Moncter/数据库/KR_游戏.js new file mode 100644 index 000000000..23b1759c0 --- /dev/null +++ b/Moncter/数据库/KR_游戏.js @@ -0,0 +1,156 @@ +/* + Navicat Premium Data Transfer + + Source Server : 标签数据库 + Source Server Type : MongoDB + Source Server Version : 60025 (6.0.25) + Source Host : 192.168.2.6:27017 + Source Schema : KR_游戏 + + Target Server Type : MongoDB + Target Server Version : 60025 (6.0.25) + File Encoding : 65001 + + Date: 19/11/2025 15:21:24 +*/ + + +// ---------------------------- +// Collection structure for 178游戏网 +// ---------------------------- +db.getCollection("178游戏网").drop(); +db.createCollection("178游戏网"); +db.getCollection("178游戏网").createIndex({ + username: NumberInt("1") +}, { + name: "username_1" +}); + +// ---------------------------- +// Collection structure for 766游戏网 +// ---------------------------- +db.getCollection("766游戏网").drop(); +db.createCollection("766游戏网"); +db.getCollection("766游戏网").createIndex({ + "data.0": NumberInt("1") +}, { + name: "data.0_1" +}); + +// ---------------------------- +// Collection structure for 7k7k小游戏 +// ---------------------------- +db.getCollection("7k7k小游戏").drop(); +db.createCollection("7k7k小游戏"); +db.getCollection("7k7k小游戏").createIndex({ + account: NumberInt("1") +}, { + name: "account_1" +}); +db.getCollection("7k7k小游戏").createIndex({ + domain: NumberInt("1") +}, { + name: "domain_1" +}); +db.getCollection("7k7k小游戏").createIndex({ + account_type: NumberInt("1") +}, { + name: "account_type_1" +}); +db.getCollection("7k7k小游戏").createIndex({ + is_email: NumberInt("1") +}, { + name: "is_email_1" +}); + +// ---------------------------- +// Collection structure for DNF +// ---------------------------- +db.getCollection("DNF").drop(); +db.createCollection("DNF"); +db.getCollection("DNF").createIndex({ + "data.0": NumberInt("1") +}, { + name: "data.0_1" +}); +db.getCollection("DNF").createIndex({ + id: NumberInt("1") +}, { + name: "id_1" +}); +db.getCollection("DNF").createIndex({ + username: NumberInt("1") +}, { + name: "username_1" +}); +db.getCollection("DNF").createIndex({ + email: NumberInt("1") +}, { + name: "email_1" +}); +db.getCollection("DNF").createIndex({ + phone: NumberInt("1") +}, { + name: "phone_1" +}); +db.getCollection("DNF").createIndex({ + character_name: NumberInt("1") +}, { + name: "character_name_1" +}); +db.getCollection("DNF").createIndex({ + server_name: NumberInt("1") +}, { + name: "server_name_1" +}); +db.getCollection("DNF").createIndex({ + level: NumberInt("-1") +}, { + name: "level_-1" +}); + +// ---------------------------- +// Collection structure for KR_游戏_重叠用户 +// ---------------------------- +db.getCollection("KR_游戏_重叠用户").drop(); +db.createCollection("KR_游戏_重叠用户"); +db.getCollection("KR_游戏_重叠用户").createIndex({ + user_id: NumberInt("1") +}, { + name: "user_id_1" +}); +db.getCollection("KR_游戏_重叠用户").createIndex({ + user_value: NumberInt("1") +}, { + name: "user_value_1" +}); +db.getCollection("KR_游戏_重叠用户").createIndex({ + overlap_count: NumberInt("1") +}, { + name: "overlap_count_1" +}); + +// ---------------------------- +// Collection structure for system.profile +// ---------------------------- +db.getCollection("system.profile").drop(); +db.createCollection("system.profile",{ + capped: true, + size: 1048576 +}); + +// ---------------------------- +// Collection structure for 游戏语音ispeak_cn +// ---------------------------- +db.getCollection("游戏语音ispeak_cn").drop(); +db.createCollection("游戏语音ispeak_cn"); +db.getCollection("游戏语音ispeak_cn").createIndex({ + username: NumberInt("1") +}, { + name: "username_1" +}); +db.getCollection("游戏语音ispeak_cn").createIndex({ + email: NumberInt("1") +}, { + name: "email_1" +}); diff --git a/Moncter/数据库/KR_腾讯.js b/Moncter/数据库/KR_腾讯.js new file mode 100644 index 000000000..7514a11dd --- /dev/null +++ b/Moncter/数据库/KR_腾讯.js @@ -0,0 +1,1136 @@ +/* + Navicat Premium Data Transfer + + Source Server : 标签数据库 + Source Server Type : MongoDB + Source Server Version : 60025 (6.0.25) + Source Host : 192.168.2.6:27017 + Source Schema : KR_腾讯 + + Target Server Type : MongoDB + Target Server Version : 60025 (6.0.25) + File Encoding : 65001 + + Date: 19/11/2025 15:20:44 +*/ + + +// ---------------------------- +// Collection structure for IT族qq号(435999个群成员) +// ---------------------------- +db.getCollection("IT族qq号(435999个群成员)").drop(); +db.createCollection("IT族qq号(435999个群成员)"); + +// ---------------------------- +// Collection structure for IT族qq邮箱(435999个群成员) +// ---------------------------- +db.getCollection("IT族qq邮箱(435999个群成员)").drop(); +db.createCollection("IT族qq邮箱(435999个群成员)"); +db.getCollection("IT族qq邮箱(435999个群成员)").createIndex({ + "邮箱": NumberInt("1") +}, { + name: "idx_邮箱" +}); + +// ---------------------------- +// Collection structure for MBAqq号(48191个群成员) +// ---------------------------- +db.getCollection("MBAqq号(48191个群成员)").drop(); +db.createCollection("MBAqq号(48191个群成员)"); + +// ---------------------------- +// Collection structure for MBAqq邮箱(48191个群成员) +// ---------------------------- +db.getCollection("MBAqq邮箱(48191个群成员)").drop(); +db.createCollection("MBAqq邮箱(48191个群成员)"); +db.getCollection("MBAqq邮箱(48191个群成员)").createIndex({ + "邮箱": NumberInt("1") +}, { + name: "idx_邮箱" +}); + +// ---------------------------- +// Collection structure for QQ+手机 +// ---------------------------- +db.getCollection("QQ+手机").drop(); +db.createCollection("QQ+手机"); +db.getCollection("QQ+手机").createIndex({ + "省份": NumberInt("1") +}, { + name: "省份_1" +}); +db.getCollection("QQ+手机").createIndex({ + "手机号评分": NumberInt("1") +}, { + name: "手机号评分_1" +}); +db.getCollection("QQ+手机").createIndex({ + phone: NumberInt("1") +}, { + name: "phone_1" +}); + +// ---------------------------- +// Collection structure for QQ手机号数据 +// ---------------------------- +db.getCollection("QQ手机号数据").drop(); +db.createCollection("QQ手机号数据"); +db.getCollection("QQ手机号数据").createIndex({ + qq_number: NumberInt("1") +}, { + name: "qq_number_1", + background: true, + unique: true +}); + +// ---------------------------- +// Collection structure for it认证考试qq号(24416个群成员) +// ---------------------------- +db.getCollection("it认证考试qq号(24416个群成员)").drop(); +db.createCollection("it认证考试qq号(24416个群成员)"); + +// ---------------------------- +// Collection structure for it认证考试qq邮箱(24416个群成员) +// ---------------------------- +db.getCollection("it认证考试qq邮箱(24416个群成员)").drop(); +db.createCollection("it认证考试qq邮箱(24416个群成员)"); +db.getCollection("it认证考试qq邮箱(24416个群成员)").createIndex({ + "邮箱": NumberInt("1") +}, { + name: "idx_邮箱" +}); + +// ---------------------------- +// Collection structure for qq邮箱 +// ---------------------------- +db.getCollection("qq邮箱").drop(); +db.createCollection("qq邮箱"); + +// ---------------------------- +// Collection structure for shopping族qq号(16966个群成员) +// ---------------------------- +db.getCollection("shopping族qq号(16966个群成员)").drop(); +db.createCollection("shopping族qq号(16966个群成员)"); + +// ---------------------------- +// Collection structure for shopping族qq邮箱(16966个群成员) +// ---------------------------- +db.getCollection("shopping族qq邮箱(16966个群成员)").drop(); +db.createCollection("shopping族qq邮箱(16966个群成员)"); + +// ---------------------------- +// Collection structure for system.profile +// ---------------------------- +db.getCollection("system.profile").drop(); +db.createCollection("system.profile",{ + capped: true, + size: 1048576 +}); + +// ---------------------------- +// Collection structure for 东风车主qq号(13605个群成员) +// ---------------------------- +db.getCollection("东风车主qq号(13605个群成员)").drop(); +db.createCollection("东风车主qq号(13605个群成员)"); + +// ---------------------------- +// Collection structure for 东风车主qq邮箱(13605个群成员) +// ---------------------------- +db.getCollection("东风车主qq邮箱(13605个群成员)").drop(); +db.createCollection("东风车主qq邮箱(13605个群成员)"); + +// ---------------------------- +// Collection structure for 中华车主qq号(11322个群成员) +// ---------------------------- +db.getCollection("中华车主qq号(11322个群成员)").drop(); +db.createCollection("中华车主qq号(11322个群成员)"); + +// ---------------------------- +// Collection structure for 中华车主qq邮箱(11322个群成员) +// ---------------------------- +db.getCollection("中华车主qq邮箱(11322个群成员)").drop(); +db.createCollection("中华车主qq邮箱(11322个群成员)"); + +// ---------------------------- +// Collection structure for 丽江旅游自助群 +// ---------------------------- +db.getCollection("丽江旅游自助群").drop(); +db.createCollection("丽江旅游自助群"); + +// ---------------------------- +// Collection structure for 会计考试qq号(146885个群成员) +// ---------------------------- +db.getCollection("会计考试qq号(146885个群成员)").drop(); +db.createCollection("会计考试qq号(146885个群成员)"); + +// ---------------------------- +// Collection structure for 会计考试qq邮箱(146885个群成员) +// ---------------------------- +db.getCollection("会计考试qq邮箱(146885个群成员)").drop(); +db.createCollection("会计考试qq邮箱(146885个群成员)"); + +// ---------------------------- +// Collection structure for 儿童教育qq号码(6847个群成员) +// ---------------------------- +db.getCollection("儿童教育qq号码(6847个群成员)").drop(); +db.createCollection("儿童教育qq号码(6847个群成员)"); + +// ---------------------------- +// Collection structure for 儿童教育qq邮箱(6847个群成员) +// ---------------------------- +db.getCollection("儿童教育qq邮箱(6847个群成员)").drop(); +db.createCollection("儿童教育qq邮箱(6847个群成员)"); + +// ---------------------------- +// Collection structure for 公务员考试qq号(225647个群成员) +// ---------------------------- +db.getCollection("公务员考试qq号(225647个群成员)").drop(); +db.createCollection("公务员考试qq号(225647个群成员)"); + +// ---------------------------- +// Collection structure for 公务员考试qq邮箱(225647个群成员) +// ---------------------------- +db.getCollection("公务员考试qq邮箱(225647个群成员)").drop(); +db.createCollection("公务员考试qq邮箱(225647个群成员)"); + +// ---------------------------- +// Collection structure for 公司销售、培训、内部群qq号(404335个群成员) +// ---------------------------- +db.getCollection("公司销售、培训、内部群qq号(404335个群成员)").drop(); +db.createCollection("公司销售、培训、内部群qq号(404335个群成员)"); + +// ---------------------------- +// Collection structure for 公司销售、培训、内部群qq邮箱(404335个群成员) +// ---------------------------- +db.getCollection("公司销售、培训、内部群qq邮箱(404335个群成员)").drop(); +db.createCollection("公司销售、培训、内部群qq邮箱(404335个群成员)"); + +// ---------------------------- +// Collection structure for 其他旅游qq号(467863个群成员) +// ---------------------------- +db.getCollection("其他旅游qq号(467863个群成员)").drop(); +db.createCollection("其他旅游qq号(467863个群成员)"); + +// ---------------------------- +// Collection structure for 其他旅游qq邮箱(467863个群成员) +// ---------------------------- +db.getCollection("其他旅游qq邮箱(467863个群成员)").drop(); +db.createCollection("其他旅游qq邮箱(467863个群成员)"); + +// ---------------------------- +// Collection structure for 减肥qq +// ---------------------------- +db.getCollection("减肥qq").drop(); +db.createCollection("减肥qq"); + +// ---------------------------- +// Collection structure for 别克车主qq号(22192个群成员) +// ---------------------------- +db.getCollection("别克车主qq号(22192个群成员)").drop(); +db.createCollection("别克车主qq号(22192个群成员)"); + +// ---------------------------- +// Collection structure for 别克车主qq邮箱(22192个群成员) +// ---------------------------- +db.getCollection("别克车主qq邮箱(22192个群成员)").drop(); +db.createCollection("别克车主qq邮箱(22192个群成员)"); + +// ---------------------------- +// Collection structure for 动漫qq号(586505个群成员) +// ---------------------------- +db.getCollection("动漫qq号(586505个群成员)").drop(); +db.createCollection("动漫qq号(586505个群成员)"); + +// ---------------------------- +// Collection structure for 动漫qq邮箱(586505个群成员) +// ---------------------------- +db.getCollection("动漫qq邮箱(586505个群成员)").drop(); +db.createCollection("动漫qq邮箱(586505个群成员)"); + +// ---------------------------- +// Collection structure for 动漫游戏qq号(4677个群成员) +// ---------------------------- +db.getCollection("动漫游戏qq号(4677个群成员)").drop(); +db.createCollection("动漫游戏qq号(4677个群成员)"); + +// ---------------------------- +// Collection structure for 动漫游戏qq邮箱(4677个群成员) +// ---------------------------- +db.getCollection("动漫游戏qq邮箱(4677个群成员)").drop(); +db.createCollection("动漫游戏qq邮箱(4677个群成员)"); + +// ---------------------------- +// Collection structure for 化妆qq号码(172823个群成员) +// ---------------------------- +db.getCollection("化妆qq号码(172823个群成员)").drop(); +db.createCollection("化妆qq号码(172823个群成员)"); + +// ---------------------------- +// Collection structure for 化妆qq邮箱(172823个群成员) +// ---------------------------- +db.getCollection("化妆qq邮箱(172823个群成员)").drop(); +db.createCollection("化妆qq邮箱(172823个群成员)"); + +// ---------------------------- +// Collection structure for 化妆品qq号(80714个群成员) +// ---------------------------- +db.getCollection("化妆品qq号(80714个群成员)").drop(); +db.createCollection("化妆品qq号(80714个群成员)"); + +// ---------------------------- +// Collection structure for 化妆品qq邮箱(80714个群成员) +// ---------------------------- +db.getCollection("化妆品qq邮箱(80714个群成员)").drop(); +db.createCollection("化妆品qq邮箱(80714个群成员)"); + +// ---------------------------- +// Collection structure for 单机游戏qq号码(167004个群成员) +// ---------------------------- +db.getCollection("单机游戏qq号码(167004个群成员)").drop(); +db.createCollection("单机游戏qq号码(167004个群成员)"); + +// ---------------------------- +// Collection structure for 单机游戏qq邮箱(167004个群成员) +// ---------------------------- +db.getCollection("单机游戏qq邮箱(167004个群成员)").drop(); +db.createCollection("单机游戏qq邮箱(167004个群成员)"); + +// ---------------------------- +// Collection structure for 古董收藏qq号(2617个群成员) +// ---------------------------- +db.getCollection("古董收藏qq号(2617个群成员)").drop(); +db.createCollection("古董收藏qq号(2617个群成员)"); + +// ---------------------------- +// Collection structure for 古董收藏qq邮箱(2617个群成员) +// ---------------------------- +db.getCollection("古董收藏qq邮箱(2617个群成员)").drop(); +db.createCollection("古董收藏qq邮箱(2617个群成员)"); + +// ---------------------------- +// Collection structure for 司法考试qq号(90796个群成员) +// ---------------------------- +db.getCollection("司法考试qq号(90796个群成员)").drop(); +db.createCollection("司法考试qq号(90796个群成员)"); + +// ---------------------------- +// Collection structure for 司法考试qq邮箱(90796个群成员) +// ---------------------------- +db.getCollection("司法考试qq邮箱(90796个群成员)").drop(); +db.createCollection("司法考试qq邮箱(90796个群成员)"); + +// ---------------------------- +// Collection structure for 吉利车主qq号(14246个群成员) +// ---------------------------- +db.getCollection("吉利车主qq号(14246个群成员)").drop(); +db.createCollection("吉利车主qq号(14246个群成员)"); + +// ---------------------------- +// Collection structure for 吉利车主qq邮箱(14246个群成员) +// ---------------------------- +db.getCollection("吉利车主qq邮箱(14246个群成员)").drop(); +db.createCollection("吉利车主qq邮箱(14246个群成员)"); + +// ---------------------------- +// Collection structure for 同城交友qq号码(2391578个群成员) +// ---------------------------- +db.getCollection("同城交友qq号码(2391578个群成员)").drop(); +db.createCollection("同城交友qq号码(2391578个群成员)"); + +// ---------------------------- +// Collection structure for 同城交友qq邮箱(2391578个群成员) +// ---------------------------- +db.getCollection("同城交友qq邮箱(2391578个群成员)").drop(); +db.createCollection("同城交友qq邮箱(2391578个群成员)"); + +// ---------------------------- +// Collection structure for 周六登山协会9群 +// ---------------------------- +db.getCollection("周六登山协会9群").drop(); +db.createCollection("周六登山协会9群"); + +// ---------------------------- +// Collection structure for 在路上户外-休闲-旅游 +// ---------------------------- +db.getCollection("在路上户外-休闲-旅游").drop(); +db.createCollection("在路上户外-休闲-旅游"); + +// ---------------------------- +// Collection structure for 天津778单车俱乐部 +// ---------------------------- +db.getCollection("天津778单车俱乐部").drop(); +db.createCollection("天津778单车俱乐部"); + +// ---------------------------- +// Collection structure for 天津e游人-远离喧嚣群 +// ---------------------------- +db.getCollection("天津e游人-远离喧嚣群").drop(); +db.createCollection("天津e游人-远离喧嚣群"); + +// ---------------------------- +// Collection structure for 天津快乐自驾1 +// ---------------------------- +db.getCollection("天津快乐自驾1").drop(); +db.createCollection("天津快乐自驾1"); + +// ---------------------------- +// Collection structure for 天津户外开心驴群 +// ---------------------------- +db.getCollection("天津户外开心驴群").drop(); +db.createCollection("天津户外开心驴群"); + +// ---------------------------- +// Collection structure for 天津户外旅游安途社 +// ---------------------------- +db.getCollection("天津户外旅游安途社").drop(); +db.createCollection("天津户外旅游安途社"); + +// ---------------------------- +// Collection structure for 天津旅游 +// ---------------------------- +db.getCollection("天津旅游").drop(); +db.createCollection("天津旅游"); + +// ---------------------------- +// Collection structure for 天津滨海驴友快乐营 +// ---------------------------- +db.getCollection("天津滨海驴友快乐营").drop(); +db.createCollection("天津滨海驴友快乐营"); + +// ---------------------------- +// Collection structure for 天津驴友小窝 +// ---------------------------- +db.getCollection("天津驴友小窝").drop(); +db.createCollection("天津驴友小窝"); + +// ---------------------------- +// Collection structure for 天津驴社车友俱乐部 +// ---------------------------- +db.getCollection("天津驴社车友俱乐部").drop(); +db.createCollection("天津驴社车友俱乐部"); + +// ---------------------------- +// Collection structure for 天津骑行 +// ---------------------------- +db.getCollection("天津骑行").drop(); +db.createCollection("天津骑行"); + +// ---------------------------- +// Collection structure for 奇瑞车主qq号(33309个群成员) +// ---------------------------- +db.getCollection("奇瑞车主qq号(33309个群成员)").drop(); +db.createCollection("奇瑞车主qq号(33309个群成员)"); + +// ---------------------------- +// Collection structure for 奇瑞车主qq邮箱(33309个群成员) +// ---------------------------- +db.getCollection("奇瑞车主qq邮箱(33309个群成员)").drop(); +db.createCollection("奇瑞车主qq邮箱(33309个群成员)"); + +// ---------------------------- +// Collection structure for 奔腾车主qq号(5401个群成员) +// ---------------------------- +db.getCollection("奔腾车主qq号(5401个群成员)").drop(); +db.createCollection("奔腾车主qq号(5401个群成员)"); + +// ---------------------------- +// Collection structure for 奔腾车主qq邮箱(5401个群成员) +// ---------------------------- +db.getCollection("奔腾车主qq邮箱(5401个群成员)").drop(); +db.createCollection("奔腾车主qq邮箱(5401个群成员)"); + +// ---------------------------- +// Collection structure for 奔驰车主qq号(3782个群成员) +// ---------------------------- +db.getCollection("奔驰车主qq号(3782个群成员)").drop(); +db.createCollection("奔驰车主qq号(3782个群成员)"); + +// ---------------------------- +// Collection structure for 奔驰车主qq邮箱(3782个群成员) +// ---------------------------- +db.getCollection("奔驰车主qq邮箱(3782个群成员)").drop(); +db.createCollection("奔驰车主qq邮箱(3782个群成员)"); + +// ---------------------------- +// Collection structure for 奥迪车主qq号(28670个群成员) +// ---------------------------- +db.getCollection("奥迪车主qq号(28670个群成员)").drop(); +db.createCollection("奥迪车主qq号(28670个群成员)"); + +// ---------------------------- +// Collection structure for 奥迪车主qq邮箱(28670个群成员) +// ---------------------------- +db.getCollection("奥迪车主qq邮箱(28670个群成员)").drop(); +db.createCollection("奥迪车主qq邮箱(28670个群成员)"); + +// ---------------------------- +// Collection structure for 女装qq号码(58223个群成员) +// ---------------------------- +db.getCollection("女装qq号码(58223个群成员)").drop(); +db.createCollection("女装qq号码(58223个群成员)"); + +// ---------------------------- +// Collection structure for 女装qq邮箱(58223个群成员) +// ---------------------------- +db.getCollection("女装qq邮箱(58223个群成员)").drop(); +db.createCollection("女装qq邮箱(58223个群成员)"); + +// ---------------------------- +// Collection structure for 婴幼宝贝qq号(52111个群成员) +// ---------------------------- +db.getCollection("婴幼宝贝qq号(52111个群成员)").drop(); +db.createCollection("婴幼宝贝qq号(52111个群成员)"); + +// ---------------------------- +// Collection structure for 婴幼宝贝qq邮箱(52111个群成员) +// ---------------------------- +db.getCollection("婴幼宝贝qq邮箱(52111个群成员)").drop(); +db.createCollection("婴幼宝贝qq邮箱(52111个群成员)"); + +// ---------------------------- +// Collection structure for 宝马车主qq号(22860个群成员) +// ---------------------------- +db.getCollection("宝马车主qq号(22860个群成员)").drop(); +db.createCollection("宝马车主qq号(22860个群成员)"); + +// ---------------------------- +// Collection structure for 宝马车主qq邮箱(22860个群成员) +// ---------------------------- +db.getCollection("宝马车主qq邮箱(22860个群成员)").drop(); +db.createCollection("宝马车主qq邮箱(22860个群成员)"); + +// ---------------------------- +// Collection structure for 宠物qq号(210501个群成员) +// ---------------------------- +db.getCollection("宠物qq号(210501个群成员)").drop(); +db.createCollection("宠物qq号(210501个群成员)"); + +// ---------------------------- +// Collection structure for 宠物qq邮箱(210501个群成员) +// ---------------------------- +db.getCollection("宠物qq邮箱(210501个群成员)").drop(); +db.createCollection("宠物qq邮箱(210501个群成员)"); + +// ---------------------------- +// Collection structure for 家具、日用品qq号(21324个群成员) +// ---------------------------- +db.getCollection("家具、日用品qq号(21324个群成员)").drop(); +db.createCollection("家具、日用品qq号(21324个群成员)"); + +// ---------------------------- +// Collection structure for 家具、日用品qq邮箱(21324个群成员) +// ---------------------------- +db.getCollection("家具、日用品qq邮箱(21324个群成员)").drop(); +db.createCollection("家具、日用品qq邮箱(21324个群成员)"); + +// ---------------------------- +// Collection structure for 家用电子qq号(56548个群成员) +// ---------------------------- +db.getCollection("家用电子qq号(56548个群成员)").drop(); +db.createCollection("家用电子qq号(56548个群成员)"); + +// ---------------------------- +// Collection structure for 家用电子qq邮箱(56548个群成员) +// ---------------------------- +db.getCollection("家用电子qq邮箱(56548个群成员)").drop(); +db.createCollection("家用电子qq邮箱(56548个群成员)"); + +// ---------------------------- +// Collection structure for 工艺品qq号(1664个群成员) +// ---------------------------- +db.getCollection("工艺品qq号(1664个群成员)").drop(); +db.createCollection("工艺品qq号(1664个群成员)"); + +// ---------------------------- +// Collection structure for 工艺品qq邮箱(1664个群成员) +// ---------------------------- +db.getCollection("工艺品qq邮箱(1664个群成员)").drop(); +db.createCollection("工艺品qq邮箱(1664个群成员)"); + +// ---------------------------- +// Collection structure for 市场营销qq号(283622个群成员) +// ---------------------------- +db.getCollection("市场营销qq号(283622个群成员)").drop(); +db.createCollection("市场营销qq号(283622个群成员)"); + +// ---------------------------- +// Collection structure for 市场营销qq邮箱(283622个群成员) +// ---------------------------- +db.getCollection("市场营销qq邮箱(283622个群成员)").drop(); +db.createCollection("市场营销qq邮箱(283622个群成员)"); + +// ---------------------------- +// Collection structure for 庞大车友俱乐部2群 +// ---------------------------- +db.getCollection("庞大车友俱乐部2群").drop(); +db.createCollection("庞大车友俱乐部2群"); + +// ---------------------------- +// Collection structure for 律师qq号(87181个群成员) +// ---------------------------- +db.getCollection("律师qq号(87181个群成员)").drop(); +db.createCollection("律师qq号(87181个群成员)"); + +// ---------------------------- +// Collection structure for 律师qq邮箱(87181个群成员) +// ---------------------------- +db.getCollection("律师qq邮箱(87181个群成员)").drop(); +db.createCollection("律师qq邮箱(87181个群成员)"); + +// ---------------------------- +// Collection structure for 怪兽美食-程度自驾游 +// ---------------------------- +db.getCollection("怪兽美食-程度自驾游").drop(); +db.createCollection("怪兽美食-程度自驾游"); + +// ---------------------------- +// Collection structure for 房地产qq号(96087个群成员) +// ---------------------------- +db.getCollection("房地产qq号(96087个群成员)").drop(); +db.createCollection("房地产qq号(96087个群成员)"); + +// ---------------------------- +// Collection structure for 房地产qq邮箱(96087个群成员) +// ---------------------------- +db.getCollection("房地产qq邮箱(96087个群成员)").drop(); +db.createCollection("房地产qq邮箱(96087个群成员)"); + +// ---------------------------- +// Collection structure for 手机qq号码(226868个群成员) +// ---------------------------- +db.getCollection("手机qq号码(226868个群成员)").drop(); +db.createCollection("手机qq号码(226868个群成员)"); + +// ---------------------------- +// Collection structure for 手机qq邮箱(226868个群成员) +// ---------------------------- +db.getCollection("手机qq邮箱(226868个群成员)").drop(); +db.createCollection("手机qq邮箱(226868个群成员)"); + +// ---------------------------- +// Collection structure for 打工一族qq号(110146个群成员) +// ---------------------------- +db.getCollection("打工一族qq号(110146个群成员)").drop(); +db.createCollection("打工一族qq号(110146个群成员)"); + +// ---------------------------- +// Collection structure for 打工一族qq邮箱(110146个群成员) +// ---------------------------- +db.getCollection("打工一族qq邮箱(110146个群成员)").drop(); +db.createCollection("打工一族qq邮箱(110146个群成员)"); + +// ---------------------------- +// Collection structure for 探险猎奇qq号(55838个群成员) +// ---------------------------- +db.getCollection("探险猎奇qq号(55838个群成员)").drop(); +db.createCollection("探险猎奇qq号(55838个群成员)"); + +// ---------------------------- +// Collection structure for 探险猎奇qq邮箱(55838个群成员) +// ---------------------------- +db.getCollection("探险猎奇qq邮箱(55838个群成员)").drop(); +db.createCollection("探险猎奇qq邮箱(55838个群成员)"); + +// ---------------------------- +// Collection structure for 摄影qq邮箱 +// ---------------------------- +db.getCollection("摄影qq邮箱").drop(); +db.createCollection("摄影qq邮箱"); + +// ---------------------------- +// Collection structure for 摄影qq邮箱(14618个群成员) +// ---------------------------- +db.getCollection("摄影qq邮箱(14618个群成员)").drop(); +db.createCollection("摄影qq邮箱(14618个群成员)"); + +// ---------------------------- +// Collection structure for 摄影玩家qq号(125333个群成员) +// ---------------------------- +db.getCollection("摄影玩家qq号(125333个群成员)").drop(); +db.createCollection("摄影玩家qq号(125333个群成员)"); + +// ---------------------------- +// Collection structure for 摄影玩家qq邮箱(125333个群成员) +// ---------------------------- +db.getCollection("摄影玩家qq邮箱(125333个群成员)").drop(); +db.createCollection("摄影玩家qq邮箱(125333个群成员)"); + +// ---------------------------- +// Collection structure for 新东方考试qq号(27781个群成员) +// ---------------------------- +db.getCollection("新东方考试qq号(27781个群成员)").drop(); +db.createCollection("新东方考试qq号(27781个群成员)"); + +// ---------------------------- +// Collection structure for 新东方考试qq邮箱(27781个群成员) +// ---------------------------- +db.getCollection("新东方考试qq邮箱(27781个群成员)").drop(); +db.createCollection("新东方考试qq邮箱(27781个群成员)"); + +// ---------------------------- +// Collection structure for 旅游 +// ---------------------------- +db.getCollection("旅游").drop(); +db.createCollection("旅游"); + +// ---------------------------- +// Collection structure for 旅游qq邮箱比较全的 +// ---------------------------- +db.getCollection("旅游qq邮箱比较全的").drop(); +db.createCollection("旅游qq邮箱比较全的"); + +// ---------------------------- +// Collection structure for 旅游美食 +// ---------------------------- +db.getCollection("旅游美食").drop(); +db.createCollection("旅游美食"); + +// ---------------------------- +// Collection structure for 早教qq号码(33956个群成员) +// ---------------------------- +db.getCollection("早教qq号码(33956个群成员)").drop(); +db.createCollection("早教qq号码(33956个群成员)"); + +// ---------------------------- +// Collection structure for 早教qq邮箱(33956个群成员) +// ---------------------------- +db.getCollection("早教qq邮箱(33956个群成员)").drop(); +db.createCollection("早教qq邮箱(33956个群成员)"); + +// ---------------------------- +// Collection structure for 景点qq号(241890个群成员) +// ---------------------------- +db.getCollection("景点qq号(241890个群成员)").drop(); +db.createCollection("景点qq号(241890个群成员)"); + +// ---------------------------- +// Collection structure for 景点qq邮箱(241890个群成员) +// ---------------------------- +db.getCollection("景点qq邮箱(241890个群成员)").drop(); +db.createCollection("景点qq邮箱(241890个群成员)"); + +// ---------------------------- +// Collection structure for 朋友圈-天津自助游 +// ---------------------------- +db.getCollection("朋友圈-天津自助游").drop(); +db.createCollection("朋友圈-天津自助游"); + +// ---------------------------- +// Collection structure for 本田车主qq号(19147个群成员) +// ---------------------------- +db.getCollection("本田车主qq号(19147个群成员)").drop(); +db.createCollection("本田车主qq号(19147个群成员)"); + +// ---------------------------- +// Collection structure for 本田车主qq邮箱(19147个群成员) +// ---------------------------- +db.getCollection("本田车主qq邮箱(19147个群成员)").drop(); +db.createCollection("本田车主qq邮箱(19147个群成员)"); + +// ---------------------------- +// Collection structure for 求职qq号(107317个群成员) +// ---------------------------- +db.getCollection("求职qq号(107317个群成员)").drop(); +db.createCollection("求职qq号(107317个群成员)"); + +// ---------------------------- +// Collection structure for 求职qq邮箱(107317个群成员) +// ---------------------------- +db.getCollection("求职qq邮箱(107317个群成员)").drop(); +db.createCollection("求职qq邮箱(107317个群成员)"); + +// ---------------------------- +// Collection structure for 汽车交易群qq号(76463个群成员) +// ---------------------------- +db.getCollection("汽车交易群qq号(76463个群成员)").drop(); +db.createCollection("汽车交易群qq号(76463个群成员)"); + +// ---------------------------- +// Collection structure for 汽车交易群qq邮箱(76463个群成员) +// ---------------------------- +db.getCollection("汽车交易群qq邮箱(76463个群成员)").drop(); +db.createCollection("汽车交易群qq邮箱(76463个群成员)"); + +// ---------------------------- +// Collection structure for 海马车主qq号(7782个群成员) +// ---------------------------- +db.getCollection("海马车主qq号(7782个群成员)").drop(); +db.createCollection("海马车主qq号(7782个群成员)"); + +// ---------------------------- +// Collection structure for 海马车主qq邮箱(7782个群成员) +// ---------------------------- +db.getCollection("海马车主qq邮箱(7782个群成员)").drop(); +db.createCollection("海马车主qq邮箱(7782个群成员)"); + +// ---------------------------- +// Collection structure for 游多多天津群 +// ---------------------------- +db.getCollection("游多多天津群").drop(); +db.createCollection("游多多天津群"); + +// ---------------------------- +// Collection structure for 游戏交易qq号码(17328个群成员) +// ---------------------------- +db.getCollection("游戏交易qq号码(17328个群成员)").drop(); +db.createCollection("游戏交易qq号码(17328个群成员)"); + +// ---------------------------- +// Collection structure for 游戏交易qq邮箱(17328个群成员) +// ---------------------------- +db.getCollection("游戏交易qq邮箱(17328个群成员)").drop(); +db.createCollection("游戏交易qq邮箱(17328个群成员)"); + +// ---------------------------- +// Collection structure for 激情旅游 +// ---------------------------- +db.getCollection("激情旅游").drop(); +db.createCollection("激情旅游"); + +// ---------------------------- +// Collection structure for 玩转旅游 +// ---------------------------- +db.getCollection("玩转旅游").drop(); +db.createCollection("玩转旅游"); + +// ---------------------------- +// Collection structure for 珠宝饰品qq号码(35829个群成员) +// ---------------------------- +db.getCollection("珠宝饰品qq号码(35829个群成员)").drop(); +db.createCollection("珠宝饰品qq号码(35829个群成员)"); + +// ---------------------------- +// Collection structure for 珠宝饰品qq邮箱(35829个群成员) +// ---------------------------- +db.getCollection("珠宝饰品qq邮箱(35829个群成员)").drop(); +db.createCollection("珠宝饰品qq邮箱(35829个群成员)"); + +// ---------------------------- +// Collection structure for 电脑qq号码(213718个群成员) +// ---------------------------- +db.getCollection("电脑qq号码(213718个群成员)").drop(); +db.createCollection("电脑qq号码(213718个群成员)"); + +// ---------------------------- +// Collection structure for 电脑qq邮箱(213718个群成员) +// ---------------------------- +db.getCollection("电脑qq邮箱(213718个群成员)").drop(); +db.createCollection("电脑qq邮箱(213718个群成员)"); + +// ---------------------------- +// Collection structure for 电脑技术交流qq号(679419个群成员) +// ---------------------------- +db.getCollection("电脑技术交流qq号(679419个群成员)").drop(); +db.createCollection("电脑技术交流qq号(679419个群成员)"); + +// ---------------------------- +// Collection structure for 电脑技术交流qq邮箱(679419个群成员) +// ---------------------------- +db.getCollection("电脑技术交流qq邮箱(679419个群成员)").drop(); +db.createCollection("电脑技术交流qq邮箱(679419个群成员)"); + +// ---------------------------- +// Collection structure for 男士产品qq号(29580个群成员) +// ---------------------------- +db.getCollection("男士产品qq号(29580个群成员)").drop(); +db.createCollection("男士产品qq号(29580个群成员)"); + +// ---------------------------- +// Collection structure for 男士产品qq邮箱(29580个群成员) +// ---------------------------- +db.getCollection("男士产品qq邮箱(29580个群成员)").drop(); +db.createCollection("男士产品qq邮箱(29580个群成员)"); + +// ---------------------------- +// Collection structure for 直销qq号码(114392个群成员) +// ---------------------------- +db.getCollection("直销qq号码(114392个群成员)").drop(); +db.createCollection("直销qq号码(114392个群成员)"); + +// ---------------------------- +// Collection structure for 直销qq邮箱(114392个群成员) +// ---------------------------- +db.getCollection("直销qq邮箱(114392个群成员)").drop(); +db.createCollection("直销qq邮箱(114392个群成员)"); + +// ---------------------------- +// Collection structure for 硬件交流qq号(66270个群成员) +// ---------------------------- +db.getCollection("硬件交流qq号(66270个群成员)").drop(); +db.createCollection("硬件交流qq号(66270个群成员)"); + +// ---------------------------- +// Collection structure for 硬件交流qq邮箱(66270个群成员) +// ---------------------------- +db.getCollection("硬件交流qq邮箱(66270个群成员)").drop(); +db.createCollection("硬件交流qq邮箱(66270个群成员)"); + +// ---------------------------- +// Collection structure for 站长qq号(305613个群成员) +// ---------------------------- +db.getCollection("站长qq号(305613个群成员)").drop(); +db.createCollection("站长qq号(305613个群成员)"); + +// ---------------------------- +// Collection structure for 站长qq邮箱(305613个群成员) +// ---------------------------- +db.getCollection("站长qq邮箱(305613个群成员)").drop(); +db.createCollection("站长qq邮箱(305613个群成员)"); + +// ---------------------------- +// Collection structure for 红旗车主qq号(1070个群成员) +// ---------------------------- +db.getCollection("红旗车主qq号(1070个群成员)").drop(); +db.createCollection("红旗车主qq号(1070个群成员)"); + +// ---------------------------- +// Collection structure for 红旗车主qq邮箱(1070个群成员) +// ---------------------------- +db.getCollection("红旗车主qq邮箱(1070个群成员)").drop(); +db.createCollection("红旗车主qq邮箱(1070个群成员)"); + +// ---------------------------- +// Collection structure for 网赚qq号码(229030个群成员) +// ---------------------------- +db.getCollection("网赚qq号码(229030个群成员)").drop(); +db.createCollection("网赚qq号码(229030个群成员)"); + +// ---------------------------- +// Collection structure for 网赚qq邮箱(229030个群成员) +// ---------------------------- +db.getCollection("网赚qq邮箱(229030个群成员)").drop(); +db.createCollection("网赚qq邮箱(229030个群成员)"); + +// ---------------------------- +// Collection structure for 美容护肤类qq群成员 +// ---------------------------- +db.getCollection("美容护肤类qq群成员").drop(); +db.createCollection("美容护肤类qq群成员"); + +// ---------------------------- +// Collection structure for 美食特产qq号码(131001个群成员) +// ---------------------------- +db.getCollection("美食特产qq号码(131001个群成员)").drop(); +db.createCollection("美食特产qq号码(131001个群成员)"); + +// ---------------------------- +// Collection structure for 翱翔天津户外摄影群 +// ---------------------------- +db.getCollection("翱翔天津户外摄影群").drop(); +db.createCollection("翱翔天津户外摄影群"); + +// ---------------------------- +// Collection structure for 考研qq号(335617个群成员) +// ---------------------------- +db.getCollection("考研qq号(335617个群成员)").drop(); +db.createCollection("考研qq号(335617个群成员)"); + +// ---------------------------- +// Collection structure for 考研qq邮箱(335617个群成员) +// ---------------------------- +db.getCollection("考研qq邮箱(335617个群成员)").drop(); +db.createCollection("考研qq邮箱(335617个群成员)"); + +// ---------------------------- +// Collection structure for 职业广告人qq号(503382个群成员) +// ---------------------------- +db.getCollection("职业广告人qq号(503382个群成员)").drop(); +db.createCollection("职业广告人qq号(503382个群成员)"); + +// ---------------------------- +// Collection structure for 职业广告人qq邮箱(503382个群成员) +// ---------------------------- +db.getCollection("职业广告人qq邮箱(503382个群成员)").drop(); +db.createCollection("职业广告人qq邮箱(503382个群成员)"); + +// ---------------------------- +// Collection structure for 股票qq号(103489个群成员) +// ---------------------------- +db.getCollection("股票qq号(103489个群成员)").drop(); +db.createCollection("股票qq号(103489个群成员)"); + +// ---------------------------- +// Collection structure for 股票qq邮箱(103489个群成员) +// ---------------------------- +db.getCollection("股票qq邮箱(103489个群成员)").drop(); +db.createCollection("股票qq邮箱(103489个群成员)"); + +// ---------------------------- +// Collection structure for 自助游qq号(112553个群成员) +// ---------------------------- +db.getCollection("自助游qq号(112553个群成员)").drop(); +db.createCollection("自助游qq号(112553个群成员)"); + +// ---------------------------- +// Collection structure for 自助游qq邮箱(112553个群成员) +// ---------------------------- +db.getCollection("自助游qq邮箱(112553个群成员)").drop(); +db.createCollection("自助游qq邮箱(112553个群成员)"); + +// ---------------------------- +// Collection structure for 自驾游qq号(71983个群成员) +// ---------------------------- +db.getCollection("自驾游qq号(71983个群成员)").drop(); +db.createCollection("自驾游qq号(71983个群成员)"); + +// ---------------------------- +// Collection structure for 自驾游qq邮箱(71983个群成员) +// ---------------------------- +db.getCollection("自驾游qq邮箱(71983个群成员)").drop(); +db.createCollection("自驾游qq邮箱(71983个群成员)"); + +// ---------------------------- +// Collection structure for 英语四六级qq号(48901个群成员) +// ---------------------------- +db.getCollection("英语四六级qq号(48901个群成员)").drop(); +db.createCollection("英语四六级qq号(48901个群成员)"); + +// ---------------------------- +// Collection structure for 英语四六级qq邮箱(48901个群成员) +// ---------------------------- +db.getCollection("英语四六级qq邮箱(48901个群成员)").drop(); +db.createCollection("英语四六级qq邮箱(48901个群成员)"); + +// ---------------------------- +// Collection structure for 营销广告群qq号(92355个群成员) +// ---------------------------- +db.getCollection("营销广告群qq号(92355个群成员)").drop(); +db.createCollection("营销广告群qq号(92355个群成员)"); + +// ---------------------------- +// Collection structure for 装修qq号(256532个群成员) +// ---------------------------- +db.getCollection("装修qq号(256532个群成员)").drop(); +db.createCollection("装修qq号(256532个群成员)"); + +// ---------------------------- +// Collection structure for 装修qq邮箱(256532个群成员) +// ---------------------------- +db.getCollection("装修qq邮箱(256532个群成员)").drop(); +db.createCollection("装修qq邮箱(256532个群成员)"); + +// ---------------------------- +// Collection structure for 起亚车主qq号(7260个群成员) +// ---------------------------- +db.getCollection("起亚车主qq号(7260个群成员)").drop(); +db.createCollection("起亚车主qq号(7260个群成员)"); + +// ---------------------------- +// Collection structure for 起亚车主qq邮箱(7260个群成员) +// ---------------------------- +db.getCollection("起亚车主qq邮箱(7260个群成员)").drop(); +db.createCollection("起亚车主qq邮箱(7260个群成员)"); + +// ---------------------------- +// Collection structure for 越野车qq号码(48938个群成员) +// ---------------------------- +db.getCollection("越野车qq号码(48938个群成员)").drop(); +db.createCollection("越野车qq号码(48938个群成员)"); + +// ---------------------------- +// Collection structure for 越野车qq邮箱(48938个群成员) +// ---------------------------- +db.getCollection("越野车qq邮箱(48938个群成员)").drop(); +db.createCollection("越野车qq邮箱(48938个群成员)"); + +// ---------------------------- +// Collection structure for 运动鞋、运动服qq号(20662个群成员) +// ---------------------------- +db.getCollection("运动鞋、运动服qq号(20662个群成员)").drop(); +db.createCollection("运动鞋、运动服qq号(20662个群成员)"); + +// ---------------------------- +// Collection structure for 运动鞋、运动服qq邮箱(20662个群成员) +// ---------------------------- +db.getCollection("运动鞋、运动服qq邮箱(20662个群成员)").drop(); +db.createCollection("运动鞋、运动服qq邮箱(20662个群成员)"); + +// ---------------------------- +// Collection structure for 邮箱 +// ---------------------------- +db.getCollection("邮箱").drop(); +db.createCollection("邮箱"); + +// ---------------------------- +// Collection structure for 重庆旅游交流群(驴) +// ---------------------------- +db.getCollection("重庆旅游交流群(驴)").drop(); +db.createCollection("重庆旅游交流群(驴)"); + +// ---------------------------- +// Collection structure for 重庆渴乐户外自驾游 +// ---------------------------- +db.getCollection("重庆渴乐户外自驾游").drop(); +db.createCollection("重庆渴乐户外自驾游"); + +// ---------------------------- +// Collection structure for 金融专才qq号(114497个群成员) +// ---------------------------- +db.getCollection("金融专才qq号(114497个群成员)").drop(); +db.createCollection("金融专才qq号(114497个群成员)"); + +// ---------------------------- +// Collection structure for 金融专才qq邮箱(114497个群成员) +// ---------------------------- +db.getCollection("金融专才qq邮箱(114497个群成员)").drop(); +db.createCollection("金融专才qq邮箱(114497个群成员)"); + +// ---------------------------- +// Collection structure for 金融投资qq号码(191295个群成员) +// ---------------------------- +db.getCollection("金融投资qq号码(191295个群成员)").drop(); +db.createCollection("金融投资qq号码(191295个群成员)"); + +// ---------------------------- +// Collection structure for 金融投资qq邮箱(191295个群成员) +// ---------------------------- +db.getCollection("金融投资qq邮箱(191295个群成员)").drop(); +db.createCollection("金融投资qq邮箱(191295个群成员)"); + +// ---------------------------- +// Collection structure for 长丰车主qq号(1347个群成员) +// ---------------------------- +db.getCollection("长丰车主qq号(1347个群成员)").drop(); +db.createCollection("长丰车主qq号(1347个群成员)"); + +// ---------------------------- +// Collection structure for 长丰车主qq邮箱(1347个群成员) +// ---------------------------- +db.getCollection("长丰车主qq邮箱(1347个群成员)").drop(); +db.createCollection("长丰车主qq邮箱(1347个群成员)"); + +// ---------------------------- +// Collection structure for 雪铁龙车主qq号(7345个群成员) +// ---------------------------- +db.getCollection("雪铁龙车主qq号(7345个群成员)").drop(); +db.createCollection("雪铁龙车主qq号(7345个群成员)"); + +// ---------------------------- +// Collection structure for 雪铁龙车主qq邮箱(7345个群成员) +// ---------------------------- +db.getCollection("雪铁龙车主qq邮箱(7345个群成员)").drop(); +db.createCollection("雪铁龙车主qq邮箱(7345个群成员)"); + +// ---------------------------- +// Collection structure for 音响qq号码(12601个群成员) +// ---------------------------- +db.getCollection("音响qq号码(12601个群成员)").drop(); +db.createCollection("音响qq号码(12601个群成员)"); + +// ---------------------------- +// Collection structure for 音响qq邮箱(12601个群成员) +// ---------------------------- +db.getCollection("音响qq邮箱(12601个群成员)").drop(); +db.createCollection("音响qq邮箱(12601个群成员)"); + +// ---------------------------- +// Collection structure for 驴友qq号(258290个群成员) +// ---------------------------- +db.getCollection("驴友qq号(258290个群成员)").drop(); +db.createCollection("驴友qq号(258290个群成员)"); + +// ---------------------------- +// Collection structure for 驴友qq邮箱(258290个群成员) +// ---------------------------- +db.getCollection("驴友qq邮箱(258290个群成员)").drop(); +db.createCollection("驴友qq邮箱(258290个群成员)"); diff --git a/Moncter/数据库/KR_酒店.js b/Moncter/数据库/KR_酒店.js new file mode 100644 index 000000000..015af9b87 --- /dev/null +++ b/Moncter/数据库/KR_酒店.js @@ -0,0 +1,79 @@ +/* + Navicat Premium Data Transfer + + Source Server : 标签数据库 + Source Server Type : MongoDB + Source Server Version : 60025 (6.0.25) + Source Host : 192.168.2.6:27017 + Source Schema : KR_酒店 + + Target Server Type : MongoDB + Target Server Version : 60025 (6.0.25) + File Encoding : 65001 + + Date: 19/11/2025 15:19:17 +*/ + + +// ---------------------------- +// Collection structure for system.profile +// ---------------------------- +db.getCollection("system.profile").drop(); +db.createCollection("system.profile",{ + capped: true, + size: 1048576 +}); + +// ---------------------------- +// Collection structure for 如家汉庭 +// ---------------------------- +db.getCollection("如家汉庭").drop(); +db.createCollection("如家汉庭"); +db.getCollection("如家汉庭").createIndex({ + Mobile: NumberInt("1") +}, { + name: "Mobile_1" +}); + +// ---------------------------- +// Collection structure for 酒店开房记录_2013年8月_2000万 +// ---------------------------- +db.getCollection("酒店开房记录_2013年8月_2000万").drop(); +db.createCollection("酒店开房记录_2013年8月_2000万"); +db.getCollection("酒店开房记录_2013年8月_2000万").createIndex({ + name: NumberInt("1") +}, { + name: "name_1" +}); +db.getCollection("酒店开房记录_2013年8月_2000万").createIndex({ + phone: NumberInt("1") +}, { + name: "phone_1" +}); +db.getCollection("酒店开房记录_2013年8月_2000万").createIndex({ + id_card: NumberInt("1") +}, { + name: "id_card_1" +}); +db.getCollection("酒店开房记录_2013年8月_2000万").createIndex({ + mobile: NumberInt("1") +}, { + name: "mobile_1" +}); + +// ---------------------------- +// Collection structure for 酒店开房记录_2013年8月_2000万内 +// ---------------------------- +db.getCollection("酒店开房记录_2013年8月_2000万内").drop(); +db.createCollection("酒店开房记录_2013年8月_2000万内"); +db.getCollection("酒店开房记录_2013年8月_2000万内").createIndex({ + mobile: NumberInt("1") +}, { + name: "mobile_1" +}); + +// ---------------------------- +// Collection structure for 酒店开房记录_2013年8月_2000万内_备份 +// ---------------------------- +db.getCollection("酒店开房记录_2013年8月_2000万内_备份").drop(); +db.createCollection("酒店开房记录_2013年8月_2000万内_备份"); diff --git a/Moncter/数据库/KR_销售额3000万元-5000万元企业名录.js b/Moncter/数据库/KR_销售额3000万元-5000万元企业名录.js new file mode 100644 index 000000000..412c808b5 --- /dev/null +++ b/Moncter/数据库/KR_销售额3000万元-5000万元企业名录.js @@ -0,0 +1,85 @@ +/* + Navicat Premium Data Transfer + + Source Server : 标签数据库 + Source Server Type : MongoDB + Source Server Version : 60025 (6.0.25) + Source Host : 192.168.2.6:27017 + Source Schema : KR_销售额3000万元-5000万元企业名录 + + Target Server Type : MongoDB + Target Server Version : 60025 (6.0.25) + File Encoding : 65001 + + Date: 19/11/2025 15:21:17 +*/ + + +// ---------------------------- +// Collection structure for system.profile +// ---------------------------- +db.getCollection("system.profile").drop(); +db.createCollection("system.profile",{ + capped: true, + size: 1048576 +}); + +// ---------------------------- +// Collection structure for 企业名录 的副本 +// ---------------------------- +db.getCollection("企业名录 的副本").drop(); +db.createCollection("企业名录 的副本"); + +// ---------------------------- +// Collection structure for 企业规模代码 +// ---------------------------- +db.getCollection("企业规模代码").drop(); +db.createCollection("企业规模代码"); + +// ---------------------------- +// Collection structure for 控股情况代码 +// ---------------------------- +db.getCollection("控股情况代码").drop(); +db.createCollection("控股情况代码"); + +// ---------------------------- +// Collection structure for 机构类型代码 +// ---------------------------- +db.getCollection("机构类型代码").drop(); +db.createCollection("机构类型代码"); + +// ---------------------------- +// Collection structure for 经济类型代码 +// ---------------------------- +db.getCollection("经济类型代码").drop(); +db.createCollection("经济类型代码"); + +// ---------------------------- +// Collection structure for 营业收入代码 +// ---------------------------- +db.getCollection("营业收入代码").drop(); +db.createCollection("营业收入代码"); + +// ---------------------------- +// Collection structure for 行业代码 +// ---------------------------- +db.getCollection("行业代码").drop(); +db.createCollection("行业代码"); + +// ---------------------------- +// Collection structure for 行政区划代码 +// ---------------------------- +db.getCollection("行政区划代码").drop(); +db.createCollection("行政区划代码"); + +// ---------------------------- +// Collection structure for 销售额3000万元-5000万元企业名录 +// ---------------------------- +db.getCollection("销售额3000万元-5000万元企业名录").drop(); +db.createCollection("销售额3000万元-5000万元企业名录"); + +// ---------------------------- +// Collection structure for 隶属关系代码 +// ---------------------------- +db.getCollection("隶属关系代码").drop(); +db.createCollection("隶属关系代码"); diff --git a/Moncter/数据库/KR_顺丰.js b/Moncter/数据库/KR_顺丰.js new file mode 100644 index 000000000..532479c72 --- /dev/null +++ b/Moncter/数据库/KR_顺丰.js @@ -0,0 +1,91 @@ +/* + Navicat Premium Data Transfer + + Source Server : 标签数据库 + Source Server Type : MongoDB + Source Server Version : 60025 (6.0.25) + Source Host : 192.168.2.6:27017 + Source Schema : KR_顺丰 + + Target Server Type : MongoDB + Target Server Version : 60025 (6.0.25) + File Encoding : 65001 + + Date: 19/11/2025 15:20:28 +*/ + + +// ---------------------------- +// Collection structure for system.profile +// ---------------------------- +db.getCollection("system.profile").drop(); +db.createCollection("system.profile",{ + capped: true, + size: 1048576 +}); + +// ---------------------------- +// Collection structure for 顺丰快递数据 +// ---------------------------- +db.getCollection("顺丰快递数据").drop(); +db.createCollection("顺丰快递数据"); +db.getCollection("顺丰快递数据").createIndex({ + phone: NumberInt("1") +}, { + name: "phone_1" +}); +db.getCollection("顺丰快递数据").createIndex({ + province: NumberInt("1") +}, { + name: "province_1" +}); +db.getCollection("顺丰快递数据").createIndex({ + city: NumberInt("1") +}, { + name: "city_1" +}); +db.getCollection("顺丰快递数据").createIndex({ + province: NumberInt("1"), + city: NumberInt("1") +}, { + name: "province_1_city_1" +}); +db.getCollection("顺丰快递数据").createIndex({ + phone: NumberInt("1"), + province: NumberInt("1") +}, { + name: "phone_1_province_1" +}); + +// ---------------------------- +// Collection structure for 顺丰快递数据_tmpForCopy1 +// ---------------------------- +db.getCollection("顺丰快递数据_tmpForCopy1").drop(); +db.createCollection("顺丰快递数据_tmpForCopy1"); +db.getCollection("顺丰快递数据_tmpForCopy1").createIndex({ + phone: NumberInt("1") +}, { + name: "phone_1" +}); +db.getCollection("顺丰快递数据_tmpForCopy1").createIndex({ + province: NumberInt("1") +}, { + name: "province_1" +}); +db.getCollection("顺丰快递数据_tmpForCopy1").createIndex({ + city: NumberInt("1") +}, { + name: "city_1" +}); +db.getCollection("顺丰快递数据_tmpForCopy1").createIndex({ + province: NumberInt("1"), + city: NumberInt("1") +}, { + name: "province_1_city_1" +}); +db.getCollection("顺丰快递数据_tmpForCopy1").createIndex({ + phone: NumberInt("1"), + province: NumberInt("1") +}, { + name: "phone_1_province_1" +}); diff --git a/Moncter/数据库/KR_香港在大陆投资企业名录.js b/Moncter/数据库/KR_香港在大陆投资企业名录.js new file mode 100644 index 000000000..8e4c297aa --- /dev/null +++ b/Moncter/数据库/KR_香港在大陆投资企业名录.js @@ -0,0 +1,217 @@ +/* + Navicat Premium Data Transfer + + Source Server : 标签数据库 + Source Server Type : MongoDB + Source Server Version : 60025 (6.0.25) + Source Host : 192.168.2.6:27017 + Source Schema : KR_香港在大陆投资企业名录 + + Target Server Type : MongoDB + Target Server Version : 60025 (6.0.25) + File Encoding : 65001 + + Date: 19/11/2025 15:21:07 +*/ + + +// ---------------------------- +// Collection structure for system.profile +// ---------------------------- +db.getCollection("system.profile").drop(); +db.createCollection("system.profile",{ + capped: true, + size: 1048576 +}); + +// ---------------------------- +// Collection structure for 上海 +// ---------------------------- +db.getCollection("上海").drop(); +db.createCollection("上海"); + +// ---------------------------- +// Collection structure for 云南 +// ---------------------------- +db.getCollection("云南").drop(); +db.createCollection("云南"); + +// ---------------------------- +// Collection structure for 内蒙 +// ---------------------------- +db.getCollection("内蒙").drop(); +db.createCollection("内蒙"); + +// ---------------------------- +// Collection structure for 北京 +// ---------------------------- +db.getCollection("北京").drop(); +db.createCollection("北京"); + +// ---------------------------- +// Collection structure for 吉林 +// ---------------------------- +db.getCollection("吉林").drop(); +db.createCollection("吉林"); + +// ---------------------------- +// Collection structure for 四川 +// ---------------------------- +db.getCollection("四川").drop(); +db.createCollection("四川"); + +// ---------------------------- +// Collection structure for 天津 +// ---------------------------- +db.getCollection("天津").drop(); +db.createCollection("天津"); + +// ---------------------------- +// Collection structure for 宁夏 +// ---------------------------- +db.getCollection("宁夏").drop(); +db.createCollection("宁夏"); + +// ---------------------------- +// Collection structure for 安徽 +// ---------------------------- +db.getCollection("安徽").drop(); +db.createCollection("安徽"); + +// ---------------------------- +// Collection structure for 山东 +// ---------------------------- +db.getCollection("山东").drop(); +db.createCollection("山东"); + +// ---------------------------- +// Collection structure for 山西 +// ---------------------------- +db.getCollection("山西").drop(); +db.createCollection("山西"); + +// ---------------------------- +// Collection structure for 广东 +// ---------------------------- +db.getCollection("广东").drop(); +db.createCollection("广东"); + +// ---------------------------- +// Collection structure for 广西 +// ---------------------------- +db.getCollection("广西").drop(); +db.createCollection("广西"); + +// ---------------------------- +// Collection structure for 新疆 +// ---------------------------- +db.getCollection("新疆").drop(); +db.createCollection("新疆"); + +// ---------------------------- +// Collection structure for 江苏 +// ---------------------------- +db.getCollection("江苏").drop(); +db.createCollection("江苏"); + +// ---------------------------- +// Collection structure for 江西 +// ---------------------------- +db.getCollection("江西").drop(); +db.createCollection("江西"); + +// ---------------------------- +// Collection structure for 河北 +// ---------------------------- +db.getCollection("河北").drop(); +db.createCollection("河北"); + +// ---------------------------- +// Collection structure for 河南 +// ---------------------------- +db.getCollection("河南").drop(); +db.createCollection("河南"); + +// ---------------------------- +// Collection structure for 浙江 +// ---------------------------- +db.getCollection("浙江").drop(); +db.createCollection("浙江"); + +// ---------------------------- +// Collection structure for 海南 +// ---------------------------- +db.getCollection("海南").drop(); +db.createCollection("海南"); + +// ---------------------------- +// Collection structure for 湖北 +// ---------------------------- +db.getCollection("湖北").drop(); +db.createCollection("湖北"); + +// ---------------------------- +// Collection structure for 湖南 +// ---------------------------- +db.getCollection("湖南").drop(); +db.createCollection("湖南"); + +// ---------------------------- +// Collection structure for 甘肃 +// ---------------------------- +db.getCollection("甘肃").drop(); +db.createCollection("甘肃"); + +// ---------------------------- +// Collection structure for 福建 +// ---------------------------- +db.getCollection("福建").drop(); +db.createCollection("福建"); + +// ---------------------------- +// Collection structure for 西藏 +// ---------------------------- +db.getCollection("西藏").drop(); +db.createCollection("西藏"); + +// ---------------------------- +// Collection structure for 贵州 +// ---------------------------- +db.getCollection("贵州").drop(); +db.createCollection("贵州"); + +// ---------------------------- +// Collection structure for 辽宁 +// ---------------------------- +db.getCollection("辽宁").drop(); +db.createCollection("辽宁"); + +// ---------------------------- +// Collection structure for 重庆 +// ---------------------------- +db.getCollection("重庆").drop(); +db.createCollection("重庆"); + +// ---------------------------- +// Collection structure for 陕西 +// ---------------------------- +db.getCollection("陕西").drop(); +db.createCollection("陕西"); + +// ---------------------------- +// Collection structure for 青海 +// ---------------------------- +db.getCollection("青海").drop(); +db.createCollection("青海"); + +// ---------------------------- +// Collection structure for 香港投资企业名录 +// ---------------------------- +db.getCollection("香港投资企业名录").drop(); +db.createCollection("香港投资企业名录"); + +// ---------------------------- +// Collection structure for 黑龙江 +// ---------------------------- +db.getCollection("黑龙江").drop(); +db.createCollection("黑龙江"); diff --git a/Moncter/数据库/KR_魔兽世界.js b/Moncter/数据库/KR_魔兽世界.js new file mode 100644 index 000000000..ff5dfe8d7 --- /dev/null +++ b/Moncter/数据库/KR_魔兽世界.js @@ -0,0 +1,103 @@ +/* + Navicat Premium Data Transfer + + Source Server : 标签数据库 + Source Server Type : MongoDB + Source Server Version : 60025 (6.0.25) + Source Host : 192.168.2.6:27017 + Source Schema : KR_魔兽世界 + + Target Server Type : MongoDB + Target Server Version : 60025 (6.0.25) + File Encoding : 65001 + + Date: 19/11/2025 15:19:37 +*/ + + +// ---------------------------- +// Collection structure for elysn欧美_魔兽世界主题库 +// ---------------------------- +db.getCollection("elysn欧美_魔兽世界主题库").drop(); +db.createCollection("elysn欧美_魔兽世界主题库"); +db.getCollection("elysn欧美_魔兽世界主题库").createIndex({ + "账号": NumberInt("1") +}, { + name: "账号_1" +}); +db.getCollection("elysn欧美_魔兽世界主题库").createIndex({ + "密码": NumberInt("1") +}, { + name: "密码_1" +}); + +// ---------------------------- +// Collection structure for system.profile +// ---------------------------- +db.getCollection("system.profile").drop(); +db.createCollection("system.profile",{ + capped: true, + size: 1048576 +}); + +// ---------------------------- +// Collection structure for 网易2500W密正数据 +// ---------------------------- +db.getCollection("网易2500W密正数据").drop(); +db.createCollection("网易2500W密正数据"); +db.getCollection("网易2500W密正数据").createIndex({ + "账号": NumberInt("1") +}, { + name: "账号_1", + unique: true +}); + +// ---------------------------- +// Collection structure for 网易IS_game.sohu.com邮箱占90% +// ---------------------------- +db.getCollection("网易IS_game.sohu.com邮箱占90%").drop(); +db.createCollection("网易IS_game.sohu.com邮箱占90%"); + +// ---------------------------- +// Collection structure for 网易正确数据_已确认 +// ---------------------------- +db.getCollection("网易正确数据_已确认").drop(); +db.createCollection("网易正确数据_已确认"); +db.getCollection("网易正确数据_已确认").createIndex({ + "账号": NumberInt("1") +}, { + name: "账号_1", + background: true, + unique: true +}); + +// ---------------------------- +// Collection structure for 网易正确数据_空白数据 +// ---------------------------- +db.getCollection("网易正确数据_空白数据").drop(); +db.createCollection("网易正确数据_空白数据"); +db.getCollection("网易正确数据_空白数据").createIndex({ + "账号": NumberInt("1") +}, { + name: "idx_account", + background: true +}); +db.getCollection("网易正确数据_空白数据").createIndex({ + "密码": NumberInt("1") +}, { + name: "idx_password", + background: true +}); +db.getCollection("网易正确数据_空白数据").createIndex({ + "账号": NumberInt("1"), + "密码": NumberInt("1") +}, { + name: "idx_account_password", + background: true +}); + +// ---------------------------- +// Collection structure for 魔兽世界_3月库 +// ---------------------------- +db.getCollection("魔兽世界_3月库").drop(); +db.createCollection("魔兽世界_3月库"); From 644dd8912d0140cadb5f215e25cc9a5955fec91e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Tue, 25 Nov 2025 16:05:30 +0800 Subject: [PATCH 26/49] =?UTF-8?q?=E9=87=8D=E6=9E=84CustomerList=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E6=A0=B7=E5=BC=8F=E5=92=8C=E7=BB=93=E6=9E=84=E3=80=82?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86=E7=94=A8=E6=88=B7=E5=A4=B4=E5=83=8F?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F=EF=BC=8C=E4=BB=A5=E8=8E=B7=E5=BE=97=E5=9C=A8?= =?UTF-8?q?=E7=BA=BF/=E7=A6=BB=E7=BA=BF=E7=8A=B6=E6=80=81=E7=9A=84?= =?UTF-8?q?=E6=9B=B4=E5=A5=BD=E8=A7=86=E8=A7=89=E5=8F=8D=E9=A6=88=EF=BC=8C?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E5=A4=B4=E5=83=8F=E5=8C=85=E8=A3=85?= =?UTF-8?q?=E4=BB=A5=E4=BF=9D=E6=8C=81=E5=B8=83=E5=B1=80=E4=B8=80=E8=87=B4?= =?UTF-8?q?=E6=80=A7=EF=BC=8C=E5=B9=B6=E8=B0=83=E6=95=B4=E4=BA=86=E5=9C=A8?= =?UTF-8?q?=E7=BA=BF=E6=8C=87=E7=A4=BA=E5=99=A8=E7=9A=84=E5=B0=BA=E5=AF=B8?= =?UTF-8?q?=E5=92=8C=E9=A2=9C=E8=89=B2=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/CustomerList/com.module.scss | 44 ++++++++++--------- .../weChat/components/CustomerList/index.tsx | 29 +++++++----- 2 files changed, 42 insertions(+), 31 deletions(-) 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 29e92bf8e..3decb640c 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 @@ -42,16 +42,25 @@ &.active { .userAvatar { - border: 4px solid #1890ff; + border-color: #1890ff; + } + } - .active & { - border-color: #1890ff; - } + .avatarWrapper { + position: relative; + } - &.offline { - filter: grayscale(100%); - opacity: 0.6; - } + .userAvatar { + border: 4px solid transparent; + border-radius: 50%; + transition: + filter 0.2s ease, + opacity 0.2s ease, + border-color 0.2s ease; + + &.offline { + filter: grayscale(100%); + opacity: 0.75; } } .allUser { @@ -81,20 +90,13 @@ .onlineIndicator { position: absolute; - bottom: 10px; - right: 10px; - width: 8px; - height: 8px; + bottom: 4px; + right: 4px; + width: 12px; + height: 12px; border-radius: 50%; - border: 1px solid #2e2e2e; - - &.online { - background-color: #52c41a; // 绿色表示在线 - } - - &.offline { - background-color: #8c8c8c; // 灰色表示离线 - } + border: 2px solid #ffffff; + background-color: #52c41a; } // 骨架屏样式 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 55ba297f8..6aa58c2bb 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/CustomerList/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/CustomerList/index.tsx @@ -101,16 +101,25 @@ const CustomerList: React.FC = () => { overflowCount={99} className={styles.messageBadge} > - - {!customer.avatar && customer.name.charAt(0)} - +
    + + {!customer.avatar && customer.name.charAt(0)} + + {customer.isOnline && ( + + )} +
    ))} From 7e2dd2914d38fbf8aae18f4bc811b9ce27734d63 Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Wed, 26 Nov 2025 11:17:23 +0800 Subject: [PATCH 27/49] =?UTF-8?q?=E5=81=A5=E5=BA=B7=E5=88=86=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E6=8F=90=E4=BA=A4=20+=20=E5=BE=AE=E4=BF=A1=E5=AE=A2?= =?UTF-8?q?=E6=9C=8D=E9=A1=B5=E9=9D=A2=E6=94=B9=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mobile/mine/wechat-accounts/detail/api.ts | 14 + .../mine/wechat-accounts/detail/data.ts | 98 ++ .../wechat-accounts/detail/detail.module.scss | 838 +++++++++++++++--- .../mine/wechat-accounts/detail/index.tsx | 637 ++++++++++--- .../wechat-accounts/list/index.module.scss | 33 + .../mine/wechat-accounts/list/index.tsx | 52 +- .../WechatAccountHealthScoreService.php | 693 ++++++++++++--- Server/application/cunkebao/config/route.php | 8 +- .../controller/ContentLibraryController.php | 11 +- .../wechat/GetWechatMomentsV1Controller.php | 204 +++++ .../wechat/GetWechatOverviewV1Controller.php | 411 +++++++++ .../GetWechatsOnDevicesV1Controller.php | 256 ++++-- Server/sql.sql | 171 +++- 13 files changed, 2945 insertions(+), 481 deletions(-) create mode 100644 Server/application/cunkebao/controller/wechat/GetWechatMomentsV1Controller.php create mode 100644 Server/application/cunkebao/controller/wechat/GetWechatOverviewV1Controller.php diff --git a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/api.ts b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/api.ts index 557ae0c94..11998fe6f 100644 --- a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/api.ts +++ b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/api.ts @@ -5,6 +5,20 @@ export function getWechatAccountDetail(id: string) { return request("/v1/wechats/getWechatInfo", { wechatId: id }, "GET"); } +// 获取微信号概览数据 +export function getWechatAccountOverview(id: string) { + return request("/v1/wechats/overview", { wechatId: id }, "GET"); +} + +// 获取微信号朋友圈列表 +export function getWechatMoments(params: { + wechatId: string; + page?: number; + limit?: number; +}) { + return request("/v1/wechats/moments", params, "GET"); +} + // 获取微信号好友列表 export function getWechatFriends(params: { wechatAccount: string; diff --git a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/data.ts b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/data.ts index 20d7a2c9a..9245ba9fc 100644 --- a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/data.ts +++ b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/data.ts @@ -1,3 +1,41 @@ +// 概览数据接口 +export interface WechatAccountOverview { + healthScoreAssessment: { + score: number; + dailyLimit: number; + todayAdded: number; + lastAddTime: string; + statusTag: string; + baseComposition?: Array<{ + name: string; + score: number; + formatted: string; + friendCount?: number; + }>; + dynamicRecords?: Array<{ + title?: string; + description?: string; + time?: string; + score?: number; + formatted?: string; + statusTag?: string; + }>; + }; + accountValue: { + value: number; + formatted: string; + }; + todayValueChange: { + change: number; + formatted: string; + isPositive: boolean; + }; + totalFriends: number; + todayNewFriends: number; + highValueChatrooms: number; + todayNewChatrooms: number; +} + export interface WechatAccountSummary { accountAge: string; activityLevel: { @@ -15,12 +53,51 @@ export interface WechatAccountSummary { todayAdded: number; addLimit: number; }; + healthScore?: { + score: number; + lastUpdate?: string; + lastAddTime?: string; + baseScore?: number; + verifiedScore?: number; + friendsScore?: number; + activities?: { + type: string; + time?: string; + score: number; + description?: string; + status?: string; + }[]; + }; + moments?: { + id: string; + date: string; + month: string; + day: string; + content: string; + images?: string[]; + timeAgo?: string; + hasEmoji?: boolean; + }[]; + accountValue?: { + value: number; + todayChange?: number; + }; + friendsCount?: { + total: number; + todayAdded?: number; + }; + groupsCount?: { + total: number; + todayAdded?: number; + }; restrictions: { id: number; level: number; reason: string; date: string; }[]; + // 新增概览数据 + overview?: WechatAccountOverview; } export interface Friend { @@ -39,6 +116,27 @@ export interface Friend { region: string; source: string; notes: string; + value?: number; + valueFormatted?: string; + statusTags?: string[]; +} + +export interface MomentItem { + id: string; + snsId: string; + type: number; + content: string; + resUrls: string[]; + commentList?: any[]; + likeList?: any[]; + createTime: string; + momentEntity?: { + lat?: string; + lng?: string; + location?: string; + picSize?: number; + userName?: string; + }; } export interface WechatFriendDetail { diff --git a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/detail.module.scss b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/detail.module.scss index c6309f70f..08f297a2f 100644 --- a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/detail.module.scss +++ b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/detail.module.scss @@ -143,67 +143,235 @@ } .overview-content { - .info-grid { + // 健康分评估区域 + .health-score-section { + background: #ffffff; + border-radius: 12px; + padding: 16px; + margin-bottom: 16px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); + + .health-score-title { + font-size: 16px; + font-weight: 600; + color: #333; + margin-bottom: 12px; + } + + .health-score-info { + .health-score-status { + display: flex; + justify-content: space-between; + margin-bottom: 12px; + + .status-tag { + background: #ffebeb; + color: #ff4d4f; + font-size: 12px; + padding: 2px 8px; + border-radius: 4px; + } + + .status-time { + font-size: 12px; + color: #999; + } + } + + .health-score-display { + display: flex; + align-items: center; + + .score-circle-wrapper { + width: 100px; + height: 100px; + margin-right: 24px; + position: relative; + + .score-circle { + width: 100%; + height: 100%; + border-radius: 50%; + background: #fff; + border: 8px solid #ff4d4f; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + .score-number { + font-size: 28px; + font-weight: 700; + color: #ff4d4f; + line-height: 1; + } + + .score-label { + font-size: 12px; + color: #999; + margin-top: 4px; + } + } + } + + .health-score-stats { + flex: 1; + + .stats-row { + display: flex; + justify-content: space-between; + margin-bottom: 8px; + + .stats-label { + font-size: 14px; + color: #666; + } + + .stats-value { + font-size: 14px; + color: #333; + font-weight: 500; + } + } + } + } + } + } + + // 账号统计卡片网格 + .account-stats-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 16px; - .info-card { - background: linear-gradient(135deg, #e6f7ff, #f0f8ff); + .stat-card { + background: #ffffff; padding: 16px; border-radius: 12px; - border: 1px solid #bae7ff; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); - transition: all 0.3s; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); - &:hover { - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); - transform: translateY(-1px); - } - - .info-header { + .stat-header { display: flex; + justify-content: space-between; align-items: center; - gap: 8px; margin-bottom: 8px; - .info-icon { - font-size: 16px; - color: #1677ff; - padding: 6px; - background: #e6f7ff; - border-radius: 8px; + .stat-title { + font-size: 14px; + color: #666; } - .info-title { - flex: 1; + .stat-icon-up { + width: 20px; + height: 20px; + background: #f0f0f0; + border-radius: 50%; + position: relative; - .title-text { - font-size: 12px; - font-weight: 600; - color: #1677ff; - margin-bottom: 2px; + &::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%) rotate(-45deg); + width: 8px; + height: 8px; + border-top: 2px solid #722ed1; + border-right: 2px solid #722ed1; + } + } + + .stat-icon-plus { + width: 20px; + height: 20px; + background: #f0f0f0; + border-radius: 50%; + position: relative; + + &::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 10px; + height: 2px; + background: #52c41a; } - .title-sub { - font-size: 10px; - color: #666; + &::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 2px; + height: 10px; + background: #52c41a; + } + } + + .stat-icon-people { + width: 20px; + height: 20px; + background: #f0f0f0; + border-radius: 50%; + position: relative; + + &::before { + content: ''; + position: absolute; + top: 6px; + left: 7px; + width: 6px; + height: 6px; + border-radius: 50%; + background: #1677ff; + } + + &::after { + content: ''; + position: absolute; + top: 13px; + left: 5px; + width: 10px; + height: 5px; + border-radius: 10px 10px 0 0; + background: #1677ff; + } + } + + .stat-icon-chat { + width: 20px; + height: 20px; + background: #f0f0f0; + border-radius: 50%; + position: relative; + + &::before { + content: ''; + position: absolute; + top: 6px; + left: 6px; + width: 8px; + height: 8px; + border-radius: 2px; + background: #fa8c16; } } } - .info-value { - text-align: right; - font-size: 18px; - font-weight: 700; - color: #1677ff; + .stat-value { + font-size: 20px; + font-weight: 600; + color: #333; + } - .value-unit { - font-size: 12px; - color: #666; - margin-left: 4px; - } + .stat-value-positive { + font-size: 20px; + font-weight: 600; + color: #52c41a; } } } @@ -449,6 +617,47 @@ } } + .friends-summary { + display: flex; + align-items: center; + justify-content: space-between; + background: #f5f9ff; + border: 1px solid #e0edff; + border-radius: 10px; + padding: 12px 16px; + margin-bottom: 16px; + + .summary-item { + display: flex; + flex-direction: column; + gap: 4px; + } + + .summary-label { + font-size: 12px; + color: #666; + } + + .summary-value { + font-size: 20px; + font-weight: 600; + color: #111; + } + + .summary-value-highlight { + font-size: 20px; + font-weight: 600; + color: #fa541c; + } + + .summary-divider { + width: 1px; + height: 32px; + background: #e6e6e6; + margin: 0 12px; + } + } + .friends-list { .empty { text-align: center; @@ -467,83 +676,100 @@ } } - .friend-item { + .friend-card { display: flex; align-items: center; - padding: 12px; + padding: 14px; background: #fff; - border: 1px solid #e8e8e8; - border-radius: 8px; - margin-bottom: 8px; - } + border: 1px solid #f0f0f0; + border-radius: 12px; + margin-bottom: 10px; + gap: 12px; + transition: box-shadow 0.2s, border-color 0.2s; - .friend-item-static { - display: flex; - align-items: center; - padding: 12px; - background: #fff; - border: 1px solid #e8e8e8; - border-radius: 8px; - margin-bottom: 8px; + &:hover { + border-color: #cfe2ff; + box-shadow: 0 6px 16px rgba(24, 144, 255, 0.15); + } } .friend-avatar { - width: 40px; - height: 40px; - border-radius: 50%; - margin-right: 12px; + width: 48px; + height: 48px; + + .adm-avatar { + width: 48px; + height: 48px; + border-radius: 50%; + } } - .friend-info { + .friend-main { flex: 1; min-width: 0; + } - .friend-header { - display: flex; - align-items: center; - justify-content: space-between; + .friend-name-row { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 4px; + } + + .friend-name { + font-size: 15px; + font-weight: 600; + color: #111; + flex-shrink: 0; + } + + .friend-tags { + display: flex; + flex-wrap: wrap; + gap: 4px; + } + + .friend-tag { + font-size: 11px; + padding: 2px 8px; + border-radius: 999px; + background: #f5f5f5; + color: #666; + } + + .friend-id-row { + font-size: 12px; + color: #999; + margin-bottom: 6px; + } + + .friend-status-row { + display: flex; + flex-wrap: wrap; + gap: 6px; + } + + .friend-status-chip { + background: #f0f7ff; + color: #1677ff; + font-size: 11px; + padding: 2px 8px; + border-radius: 8px; + } + + .friend-value { + text-align: right; + + .value-label { + font-size: 11px; + color: #999; margin-bottom: 4px; - - .friend-name { - font-size: 14px; - font-weight: 500; - color: #333; - max-width: 180px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - - .friend-remark { - color: #666; - margin-left: 4px; - } - } - - .friend-arrow { - font-size: 12px; - color: #ccc; - } } - .friend-wechat-id { - font-size: 12px; - color: #666; - margin-bottom: 4px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - .friend-tags { - display: flex; - flex-wrap: wrap; - gap: 4px; - - .friend-tag { - font-size: 10px; - padding: 2px 6px; - border-radius: 6px; - } + .value-amount { + font-size: 14px; + font-weight: 600; + color: #fa541c; } } } @@ -769,6 +995,416 @@ margin-right: 8px; } +.health-content { + padding: 16px 0; + height: 500px; + overflow-y: auto; + + .health-score-card { + background: #ffffff; + border-radius: 12px; + padding: 16px; + margin-bottom: 16px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); + + .health-score-status { + display: flex; + justify-content: space-between; + margin-bottom: 12px; + + .status-tag { + background: #ffebeb; + color: #ff4d4f; + font-size: 12px; + padding: 2px 8px; + border-radius: 4px; + } + + .status-time { + font-size: 12px; + color: #999; + } + } + + .health-score-display { + display: flex; + align-items: center; + + .score-circle-wrapper { + width: 100px; + height: 100px; + margin-right: 24px; + position: relative; + + .score-circle { + width: 100%; + height: 100%; + border-radius: 50%; + background: #fff; + border: 8px solid #ff4d4f; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + .score-number { + font-size: 28px; + font-weight: 700; + color: #ff4d4f; + line-height: 1; + } + + .score-label { + font-size: 12px; + color: #999; + margin-top: 4px; + } + } + } + + .health-score-stats { + flex: 1; + + .stats-row { + display: flex; + justify-content: space-between; + margin-bottom: 8px; + + .stats-label { + font-size: 14px; + color: #666; + } + + .stats-value { + font-size: 14px; + color: #333; + font-weight: 500; + } + } + } + } + } + + .health-section { + background: #ffffff; + border-radius: 12px; + padding: 16px; + margin-bottom: 16px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); + + .health-section-title { + font-size: 16px; + font-weight: 600; + color: #ff8800; + margin-bottom: 12px; + position: relative; + padding-left: 12px; + + &::before { + content: ''; + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + width: 4px; + height: 16px; + background: #ff8800; + border-radius: 2px; + } + } + + .health-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 0; + border-bottom: 1px solid #f5f5f5; + + &:last-child { + border-bottom: none; + } + + .health-item-label { + font-size: 14px; + color: #333; + display: flex; + align-items: center; + + .health-item-icon-warning { + width: 16px; + height: 16px; + border-radius: 50%; + background: #ffebeb; + margin-right: 8px; + position: relative; + + &::before { + content: '!'; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: #ff4d4f; + font-size: 12px; + font-weight: bold; + } + } + + .health-item-tag { + background: #fff7e6; + color: #fa8c16; + font-size: 12px; + padding: 2px 6px; + border-radius: 4px; + margin-left: 8px; + } + } + + .health-item-value-positive { + font-size: 14px; + font-weight: 600; + color: #52c41a; + } + + .health-item-value-negative { + font-size: 14px; + font-weight: 600; + color: #ff4d4f; + } + + .health-item-value-empty { + width: 20px; + } + } + + .health-empty { + text-align: center; + color: #999; + font-size: 14px; + padding: 20px 0; + } + } +} + +.moments-content { + padding: 16px 0; + height: 500px; + overflow-y: auto; + background: #f5f5f5; + + .moments-action-bar { + display: flex; + justify-content: space-between; + padding: 0 16px 16px; + + .action-button, .action-button-dark { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 70px; + height: 40px; + border-radius: 8px; + background: #1677ff; + + .action-icon-text, .action-icon-image, .action-icon-video, .action-icon-export { + width: 20px; + height: 20px; + background: rgba(255, 255, 255, 0.2); + border-radius: 4px; + margin-bottom: 2px; + position: relative; + + &::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 12px; + height: 2px; + background: white; + } + } + + .action-icon-image::after { + content: ''; + position: absolute; + top: 6px; + left: 6px; + width: 8px; + height: 8px; + border-radius: 2px; + background: white; + } + + .action-icon-video::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 0; + height: 0; + border-style: solid; + border-width: 5px 0 5px 8px; + border-color: transparent transparent transparent white; + } + + .action-text, .action-text-light { + font-size: 12px; + color: white; + } + } + + .action-button-dark { + background: #333; + } + } + + .moments-list { + padding: 0 16px; + + .moment-item { + display: flex; + margin-bottom: 16px; + background: white; + border-radius: 8px; + padding: 16px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); + + .moment-date { + margin-right: 12px; + text-align: center; + + .date-day { + font-size: 20px; + font-weight: 600; + color: #333; + line-height: 1; + } + + .date-month { + font-size: 12px; + color: #999; + margin-top: 2px; + } + } + + .moment-content { + flex: 1; + + .moment-text { + font-size: 14px; + line-height: 1.5; + color: #333; + margin-bottom: 8px; + white-space: pre-wrap; // 保留换行和空格,确保文本完整显示 + word-wrap: break-word; // 长单词自动换行 + + .moment-emoji { + display: inline; + font-size: 16px; + vertical-align: middle; + } + } + + .moment-images { + margin-bottom: 8px; + + .image-grid { + display: grid; + gap: 8px; + width: 100%; + + // 1张图片:宽度拉伸,高度自适应 + &.single { + grid-template-columns: 1fr; + + img { + width: 100%; + height: auto; + object-fit: cover; + border-radius: 8px; + } + } + + // 2张图片:左右并列 + &.double { + grid-template-columns: 1fr 1fr; + + img { + width: 100%; + height: 120px; + object-fit: cover; + border-radius: 8px; + } + } + + // 3张图片:三张并列 + &.triple { + grid-template-columns: 1fr 1fr 1fr; + + img { + width: 100%; + height: 100px; + object-fit: cover; + border-radius: 8px; + } + } + + // 4张图片:2x2网格布局 + &.quad { + grid-template-columns: repeat(2, 1fr); + + img { + width: 100%; + height: 140px; + object-fit: cover; + border-radius: 8px; + } + } + + // 5张及以上:网格布局(9宫格) + &.grid { + grid-template-columns: repeat(3, 1fr); + + img { + width: 100%; + height: 100px; + object-fit: cover; + border-radius: 8px; + } + + .image-more { + display: flex; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.5); + border-radius: 8px; + color: white; + font-size: 12px; + font-weight: 500; + height: 100px; + } + } + } + } + + .moment-footer { + display: flex; + justify-content: flex-end; + + .moment-time { + font-size: 12px; + color: #999; + } + } + } + } + } +} + .risk-content { padding: 16px 0; height: 500px; diff --git a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx index 3be617b58..50f2c5996 100644 --- a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx +++ b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx @@ -21,11 +21,17 @@ import { } from "@ant-design/icons"; import Layout from "@/components/Layout/Layout"; import style from "./detail.module.scss"; -import { getWechatAccountDetail, getWechatFriends, transferWechatFriends } from "./api"; +import { + getWechatAccountDetail, + getWechatFriends, + transferWechatFriends, + getWechatAccountOverview, + getWechatMoments, +} from "./api"; import DeviceSelection from "@/components/DeviceSelection"; import { DeviceSelectionItem } from "@/components/DeviceSelection/data"; -import { WechatAccountSummary, Friend } from "./data"; +import { WechatAccountSummary, Friend, MomentItem } from "./data"; const WechatAccountDetail: React.FC = () => { const { id } = useParams<{ id: string }>(); @@ -34,6 +40,7 @@ const WechatAccountDetail: React.FC = () => { const [accountSummary, setAccountSummary] = useState(null); const [accountInfo, setAccountInfo] = useState(null); + const [overviewData, setOverviewData] = useState(null); const [showRestrictions, setShowRestrictions] = useState(false); const [showTransferConfirm, setShowTransferConfirm] = useState(false); const [selectedDevices, setSelectedDevices] = useState([]); @@ -50,6 +57,12 @@ const WechatAccountDetail: React.FC = () => { const [isFetchingFriends, setIsFetchingFriends] = useState(false); const [hasFriendLoadError, setHasFriendLoadError] = useState(false); const [isFriendsEmpty, setIsFriendsEmpty] = useState(false); + const [moments, setMoments] = useState([]); + const [momentsPage, setMomentsPage] = useState(1); + const [momentsTotal, setMomentsTotal] = useState(0); + const [isFetchingMoments, setIsFetchingMoments] = useState(false); + const [momentsError, setMomentsError] = useState(null); + const MOMENTS_LIMIT = 10; // 获取基础信息 const fetchAccountInfo = useCallback(async () => { @@ -80,6 +93,19 @@ const WechatAccountDetail: React.FC = () => { } }, [id]); + // 获取概览数据 + const fetchOverviewData = useCallback(async () => { + if (!id) return; + try { + const response = await getWechatAccountOverview(id); + if (response) { + setOverviewData(response); + } + } catch (e) { + console.error("获取概览数据失败:", e); + } + }, [id]); + // 获取好友列表 - 封装为独立函数 const fetchFriendsList = useCallback( async (page: number = 1, keyword: string = "") => { @@ -96,26 +122,44 @@ const WechatAccountDetail: React.FC = () => { keyword: keyword, }); - const newFriends = response.list.map((friend: any) => ({ - id: friend.id.toString(), - avatar: friend.avatar || "/placeholder.svg", - nickname: friend.nickname || "未知用户", - wechatId: friend.wechatId || "", - remark: friend.memo || "", - addTime: friend.createTime || new Date().toISOString().split("T")[0], - lastInteraction: - friend.lastInteraction || new Date().toISOString().split("T")[0], - tags: friend.tags - ? friend.tags.map((tag: string, index: number) => ({ - id: `tag-${index}`, - name: tag, - color: getRandomTagColor(), - })) - : [], - region: friend.region || "未知", - source: friend.source || "未知", - notes: friend.notes || "", - })); + const newFriends = response.list.map((friend: any) => { + const memoTags = Array.isArray(friend.memo) + ? friend.memo + : friend.memo + ? String(friend.memo) + .split(/[,\s,、]+/) + .filter(Boolean) + : []; + + const tagList = Array.isArray(friend.tags) + ? friend.tags + : friend.tags + ? [friend.tags] + : []; + + return { + id: friend.id.toString(), + avatar: friend.avatar || "/placeholder.svg", + nickname: friend.nickname || "未知用户", + wechatId: friend.wechatId || "", + remark: friend.notes || "", + addTime: + friend.createTime || new Date().toISOString().split("T")[0], + lastInteraction: + friend.lastInteraction || new Date().toISOString().split("T")[0], + tags: memoTags.map((tag: string, index: number) => ({ + id: `tag-${index}`, + name: tag, + color: getRandomTagColor(), + })), + statusTags: tagList, + region: friend.region || "未知", + source: friend.source || "未知", + notes: friend.notes || "", + value: friend.value, + valueFormatted: friend.valueFormatted, + }; + }); setFriends(newFriends); setFriendsTotal(response.total); @@ -137,6 +181,46 @@ const WechatAccountDetail: React.FC = () => { [id], ); + const fetchMomentsList = useCallback( + async (page: number = 1, append: boolean = false) => { + if (!id) return; + setIsFetchingMoments(true); + setMomentsError(null); + try { + const response = await getWechatMoments({ + wechatId: id, + page, + limit: MOMENTS_LIMIT, + }); + + const list: MomentItem[] = (response.list || []).map((moment: any) => ({ + id: moment.id?.toString() || Math.random().toString(), + snsId: moment.snsId, + type: moment.type, + content: moment.content || "", + resUrls: moment.resUrls || [], + commentList: moment.commentList || [], + likeList: moment.likeList || [], + createTime: moment.createTime || "", + momentEntity: moment.momentEntity || {}, + })); + + setMoments(prev => (append ? [...prev, ...list] : list)); + setMomentsTotal(response.total || list.length); + setMomentsPage(page); + } catch (error) { + console.error("获取朋友圈数据失败:", error); + setMomentsError("获取朋友圈数据失败"); + if (!append) { + setMoments([]); + } + } finally { + setIsFetchingMoments(false); + } + }, + [id], + ); + // 搜索好友 const handleSearch = useCallback(() => { setFriendsPage(1); @@ -161,8 +245,9 @@ const WechatAccountDetail: React.FC = () => { useEffect(() => { if (id) { fetchAccountInfo(); + fetchOverviewData(); } - }, [id, fetchAccountInfo]); + }, [id, fetchAccountInfo, fetchOverviewData]); // 监听标签切换 - 只在切换到好友列表时请求一次 useEffect(() => { @@ -173,6 +258,14 @@ const WechatAccountDetail: React.FC = () => { } }, [activeTab, id, fetchFriendsList, searchQuery]); + useEffect(() => { + if (activeTab === "moments" && id) { + if (moments.length === 0) { + fetchMomentsList(1, false); + } + } + }, [activeTab, id, fetchMomentsList, moments.length]); + // 工具函数 const getRandomTagColor = (): string => { const colors = [ @@ -271,6 +364,41 @@ const WechatAccountDetail: React.FC = () => { navigate(`/mine/traffic-pool/detail/${friend.wechatId}/${friend.id}`); }; + const handleLoadMoreMoments = () => { + if (isFetchingMoments) return; + if (moments.length >= momentsTotal) return; + fetchMomentsList(momentsPage + 1, true); + }; + + const formatMomentDateParts = (dateString: string) => { + const date = new Date(dateString); + if (Number.isNaN(date.getTime())) { + return { day: "--", month: "--" }; + } + const day = date.getDate().toString().padStart(2, "0"); + const month = `${date.getMonth() + 1}月`; + return { day, month }; + }; + + const formatMomentTimeAgo = (dateString: string) => { + const date = new Date(dateString); + if (Number.isNaN(date.getTime())) { + return dateString || "--"; + } + const diff = Date.now() - date.getTime(); + const minutes = Math.floor(diff / (1000 * 60)); + if (minutes < 1) return "刚刚"; + if (minutes < 60) return `${minutes}分钟前`; + const hours = Math.floor(minutes / 60); + if (hours < 24) return `${hours}小时前`; + const days = Math.floor(hours / 24); + if (days < 7) return `${days}天前`; + return date.toLocaleDateString("zh-CN", { + month: "2-digit", + day: "2-digit", + }); + }; + return ( } loading={loadingInfo}>
    @@ -313,73 +441,220 @@ const WechatAccountDetail: React.FC = () => { onChange={handleTabChange} className={style["tabs"]} > - +
    -
    -
    -
    - {accountInfo?.friendShip?.totalFriend ?? "-"} + {/* 健康分评估区域 */} +
    +
    健康分评估
    +
    +
    + {overviewData?.healthScoreAssessment?.statusTag || "已添加加人"} + 最后添加时间: {overviewData?.healthScoreAssessment?.lastAddTime || "18:44:14"}
    -
    好友数量
    -
    -
    -
    - +{accountSummary?.statistics.todayAdded ?? "-"} +
    +
    +
    +
    + {overviewData?.healthScoreAssessment?.score || 67} +
    +
    SCORE
    +
    +
    +
    +
    +
    每日限额
    +
    {overviewData?.healthScoreAssessment?.dailyLimit || 0} 人
    +
    +
    +
    今日已加
    +
    {overviewData?.healthScoreAssessment?.todayAdded || 0} 人
    +
    +
    -
    今日新增
    -
    - 今日可添加: - - {accountSummary?.statistics.todayAdded ?? 0}/ - {accountSummary?.statistics.addLimit ?? 0} - -
    -
    -
    -
    -
    -
    -
    -
    -
    - {accountInfo?.friendShip?.groupNumber ?? "-"} + + {/* 账号价值和好友数量区域 */} +
    + {/* 账号价值 */} +
    +
    +
    账号价值
    +
    -
    群聊数量
    -
    -
    -
    - {accountInfo?.activity?.yesterdayMsgCount ?? "-"} +
    + {overviewData?.accountValue?.formatted || `¥${overviewData?.accountValue?.value || "29,800"}`} +
    +
    + + {/* 今日价值变化 */} +
    +
    +
    今日价值变化
    +
    +
    +
    + {overviewData?.todayValueChange?.formatted || `+${overviewData?.todayValueChange?.change || "500"}`}
    -
    今日消息
    -
    -
    设备信息
    -
    - 设备名称: - {accountInfo?.deviceName ?? "-"} + + {/* 好友数量和今日新增好友区域 */} +
    + {/* 好友总数 */} +
    +
    +
    好友总数
    +
    +
    +
    + {overviewData?.totalFriends || accountInfo?.friendShip?.totalFriend || "0"} +
    -
    - 系统类型: - {accountInfo?.deviceType ?? "-"} + + {/* 今日新增好友 */} +
    +
    +
    今日新增好友
    +
    +
    +
    + +{overviewData?.todayNewFriends || accountSummary?.statistics.todayAdded || "0"} +
    -
    - 系统版本: - {accountInfo?.deviceVersion ?? "-"} +
    + + {/* 高价群聊区域 */} +
    + {/* 高价群聊 */} +
    +
    +
    高价群聊
    +
    +
    +
    + {overviewData?.highValueChatrooms || accountInfo?.friendShip?.groupNumber || "0"} +
    + + {/* 今日新增群聊 */} +
    +
    +
    今日新增群聊
    +
    +
    +
    + +{overviewData?.todayNewChatrooms || "0"} +
    +
    +
    + + +
    + + + +
    + {/* 健康分数圆环 */} +
    +
    + 已添加加人 + 最后添加时间: {accountSummary?.healthScore?.lastAddTime || "18:36:06"} +
    +
    +
    +
    +
    + {accountSummary?.healthScore?.score || 67} +
    +
    SCORE
    +
    +
    +
    +
    +
    每日限额
    +
    {accountSummary?.statistics.addLimit || 0} 人
    +
    +
    +
    今日已加
    +
    {accountSummary?.statistics.todayAdded || 0} 人
    +
    +
    +
    +
    + + {/* 基础构成 */} +
    +
    基础构成
    + {(overviewData?.healthScoreAssessment?.baseComposition && + overviewData.healthScoreAssessment.baseComposition.length > 0 + ? overviewData.healthScoreAssessment.baseComposition + : [ + { name: "账号基础分", formatted: "+60" }, + { name: "已修改微信号", formatted: "+10" }, + { name: "好友数量加成", formatted: "+12", friendCount: 5595 }, + ] + ).map((item, index) => ( +
    +
    + {item.name} + {item.friendCount ? ` (${item.friendCount})` : ""} +
    +
    = 0 + ? style["health-item-value-positive"] + : style["health-item-value-negative"] + } + > + {item.formatted || `${item.score ?? 0}`} +
    +
    + ))} +
    + + {/* 动态记录 */} +
    +
    动态记录
    + {overviewData?.healthScoreAssessment?.dynamicRecords && + overviewData.healthScoreAssessment.dynamicRecords.length > 0 ? ( + overviewData.healthScoreAssessment.dynamicRecords.map( + (record, index) => ( +
    +
    + + {record.title || record.description || "记录"} + {record.statusTag && ( + + {record.statusTag} + + )} +
    +
    = 0 + ? style["health-item-value-positive"] + : style["health-item-value-negative"] + } + > + {record.formatted || + (record.score && record.score > 0 + ? `+${record.score}` + : record.score || "-")} +
    +
    + ), + ) + ) : ( +
    暂无动态记录
    + )}
    + 0 ? ` (${friendsTotal.toLocaleString()})` : ""}`} + title={`好友${activeTab === "friends" && friendsTotal > 0 ? ` (${friendsTotal.toLocaleString()})` : ""}`} key="friends" >
    @@ -406,6 +681,23 @@ const WechatAccountDetail: React.FC = () => {
    + {/* 好友概要 */} +
    +
    +
    好友总数
    +
    + {friendsTotal || overviewData?.totalFriends || 0} +
    +
    +
    +
    +
    好友总估值
    +
    + {overviewData?.accountValue?.formatted || "¥1,500,000"} +
    +
    +
    + {/* 好友列表 */}
    {isFetchingFriends && friends.length === 0 ? ( @@ -431,36 +723,53 @@ const WechatAccountDetail: React.FC = () => { {friends.map(friend => (
    handleFriendClick(friend)} > - -
    -
    +
    + +
    +
    +
    - {friend.nickname} - {friend.remark && ( - - ({friend.remark}) + {friend.nickname || "未知好友"} +
    +
    + {friend.tags?.map((tag, index) => ( + + {typeof tag === "string" ? tag : tag.name} - )} + ))}
    -
    - {friend.wechatId} +
    + ID: {friend.wechatId || "-"}
    -
    - {friend.tags?.map((tag, index) => ( - + {friend.statusTags?.map((tag, idx) => ( + - {typeof tag === "string" ? tag : tag.name} - + {tag} + ))} + {friend.remark && ( + + {friend.remark} + + )} +
    +
    +
    +
    + {friend.valueFormatted + || (typeof friend.value === "number" + ? `¥${friend.value.toLocaleString()}` + : "估值 -")}
    @@ -484,45 +793,115 @@ const WechatAccountDetail: React.FC = () => {
    - -
    - {accountSummary?.restrictions && - accountSummary.restrictions.length > 0 ? ( -
    - {accountSummary.restrictions.map(restriction => ( -
    -
    -
    - {restriction.reason} -
    -
    - {restriction.date - ? formatDateTime(restriction.date) - : "暂无时间"} -
    -
    -
    - - {restriction.level === 1 - ? "低风险" - : restriction.level === 2 - ? "中风险" - : "高风险"} - -
    -
    - ))} + + +
    + {/* 功能按钮栏 */} +
    +
    + + 文本 +
    +
    + + 图片 +
    +
    + + 视频 +
    +
    + + 导出 +
    +
    + + {/* 朋友圈列表 */} +
    + {isFetchingMoments && moments.length === 0 ? ( +
    + +
    + ) : momentsError ? ( +
    {momentsError}
    + ) : moments.length === 0 ? ( +
    暂无朋友圈内容
    + ) : ( + moments.map(moment => { + const { day, month } = formatMomentDateParts( + moment.createTime, + ); + const timeAgo = formatMomentTimeAgo(moment.createTime); + const imageCount = moment.resUrls?.length || 0; + // 根据图片数量选择对应的grid类,参考素材管理的实现 + let gridClass = ""; + if (imageCount === 1) gridClass = style["single"]; + else if (imageCount === 2) gridClass = style["double"]; + else if (imageCount === 3) gridClass = style["triple"]; + else if (imageCount === 4) gridClass = style["quad"]; + else if (imageCount > 4) gridClass = style["grid"]; + + return ( +
    +
    +
    {day}
    +
    {month}
    +
    +
    + {moment.content && ( +
    + {moment.content} +
    + )} + {imageCount > 0 && ( +
    +
    + {moment.resUrls + .slice(0, 9) + .map((url, index) => ( + 朋友圈图片 + ))} + {imageCount > 9 && ( +
    + +{imageCount - 9} +
    + )} +
    +
    + )} +
    + + {timeAgo} + +
    +
    +
    + ); + }) + )} +
    + + {moments.length < momentsTotal && ( +
    +
    - ) : ( -
    暂无风险记录
    )}
    +
    diff --git a/Cunkebao/src/pages/mobile/mine/wechat-accounts/list/index.module.scss b/Cunkebao/src/pages/mobile/mine/wechat-accounts/list/index.module.scss index 4cfd3adc3..8ddb6b92b 100644 --- a/Cunkebao/src/pages/mobile/mine/wechat-accounts/list/index.module.scss +++ b/Cunkebao/src/pages/mobile/mine/wechat-accounts/list/index.module.scss @@ -2,6 +2,39 @@ padding: 0 12px; } +.filter-bar { + padding: 12px; + background: #fff; + border-bottom: 1px solid #f0f0f0; + + .filter-buttons { + display: flex; + gap: 8px; + + .filter-button { + flex: 1; + height: 32px; + border-radius: 6px; + border: 1px solid #d9d9d9; + background: #fff; + color: #666; + font-size: 14px; + transition: all 0.2s; + + &:hover { + border-color: #1677ff; + color: #1677ff; + } + + &.filter-button-active { + background: #1677ff; + border-color: #1677ff; + color: #fff; + } + } + } +} + .nav-title { font-size: 18px; font-weight: 600; diff --git a/Cunkebao/src/pages/mobile/mine/wechat-accounts/list/index.tsx b/Cunkebao/src/pages/mobile/mine/wechat-accounts/list/index.tsx index 4b761b60e..dbd304433 100644 --- a/Cunkebao/src/pages/mobile/mine/wechat-accounts/list/index.tsx +++ b/Cunkebao/src/pages/mobile/mine/wechat-accounts/list/index.tsx @@ -33,11 +33,12 @@ const WechatAccounts: React.FC = () => { const [totalAccounts, setTotalAccounts] = useState(0); const [isLoading, setIsLoading] = useState(true); const [isRefreshing, setIsRefreshing] = useState(false); + const [statusFilter, setStatusFilter] = useState<"all" | "online" | "offline">("all"); // 获取路由参数 wechatStatus const wechatStatus = searchParams.get("wechatStatus"); - const fetchAccounts = async (page = 1, keyword = "") => { + const fetchAccounts = async (page = 1, keyword = "", status?: "all" | "online" | "offline") => { setIsLoading(true); try { const params: any = { @@ -46,8 +47,12 @@ const WechatAccounts: React.FC = () => { keyword, }; - // 如果有 wechatStatus 参数,添加到请求参数中 - if (wechatStatus) { + // 优先使用传入的status参数,否则使用路由参数,最后使用状态中的筛选 + const filterStatus = status || wechatStatus || statusFilter; + + if (filterStatus && filterStatus !== "all") { + params.wechatStatus = filterStatus === "online" ? "1" : "0"; + } else if (wechatStatus) { params.wechatStatus = wechatStatus; } @@ -60,7 +65,7 @@ const WechatAccounts: React.FC = () => { setTotalAccounts(0); } } catch (e) { - Toast.show({ content: "获取微信号失败", position: "top" }); + setAccounts([]); setTotalAccounts(0); } finally { @@ -69,18 +74,24 @@ const WechatAccounts: React.FC = () => { }; useEffect(() => { - fetchAccounts(currentPage, searchTerm); + fetchAccounts(currentPage, searchTerm, statusFilter); // eslint-disable-next-line - }, [currentPage]); + }, [currentPage, statusFilter]); const handleSearch = () => { setCurrentPage(1); - fetchAccounts(1, searchTerm); + fetchAccounts(1, searchTerm, statusFilter); + }; + + const handleStatusFilterChange = (status: "all" | "online" | "offline") => { + setStatusFilter(status); + setCurrentPage(1); + fetchAccounts(1, searchTerm, status); }; const handleRefresh = async () => { setIsRefreshing(true); - await fetchAccounts(currentPage, searchTerm); + await fetchAccounts(currentPage, searchTerm, statusFilter); setIsRefreshing(false); Toast.show({ content: "刷新成功", position: "top" }); }; @@ -122,6 +133,31 @@ const WechatAccounts: React.FC = () => {
    +
    +
    + + + +
    +
    } > diff --git a/Server/application/common/service/WechatAccountHealthScoreService.php b/Server/application/common/service/WechatAccountHealthScoreService.php index d6246a268..66f31234d 100644 --- a/Server/application/common/service/WechatAccountHealthScoreService.php +++ b/Server/application/common/service/WechatAccountHealthScoreService.php @@ -16,16 +16,22 @@ use think\facade\Cache; * 2. 各个评分维度独立存储 * 3. 使用独立的评分记录表 * 4. 好友数量评分特殊处理(避免同步问题) - * 5. 动态分仅统计近30天数据 + * 5. 动态分统计所有历史数据(不限制30天) * 6. 优化数据库查询,减少重复计算 * 7. 添加完善的日志记录,便于问题排查 + * 8. 每条频繁/封号记录只统计一次,避免重复扣分 + * 9. 使用is_counted字段标记已统计的记录 + * 10. 支持lastBanTime字段,记录最后一次封号时间 + * 11. 使用事务和锁避免并发问题 + * 12. 使用静态缓存避免重复检查字段 + * 13. 推荐数据库索引提高查询性能 * * 健康分 = 基础分 + 动态分 * 基础分:60-100分(默认60分 + 基础信息10分 + 好友数量30分) * 动态分:扣分和加分规则 * * @author Your Name - * @version 2.0.0 + * @version 2.3.0 */ class WechatAccountHealthScoreService { @@ -33,7 +39,8 @@ class WechatAccountHealthScoreService * 缓存相关配置 */ const CACHE_PREFIX = 'wechat_health_score:'; // 缓存前缀 - const CACHE_TTL = 3600; // 缓存有效期(秒) + const CACHE_TTL = 7200; // 缓存有效期(秒)- 提高到2小时 + const CACHE_TTL_SHORT = 300; // 短期缓存有效期(秒)- 5分钟,用于频繁变化的数据 /** * 默认基础分 @@ -70,9 +77,34 @@ class WechatAccountHealthScoreService */ const TABLE_WECHAT_ACCOUNT = 's2_wechat_account'; const TABLE_WECHAT_ACCOUNT_SCORE = 's2_wechat_account_score'; + const TABLE_WECHAT_ACCOUNT_SCORE_LOG = 's2_wechat_account_score_log'; const TABLE_FRIEND_TASK = 's2_friend_task'; const TABLE_WECHAT_MESSAGE = 's2_wechat_message'; + /** + * 推荐数据库索引 + * 以下索引可以大幅提升查询性能 + * + * s2_wechat_account_score表: + * - PRIMARY KEY (`id`) + * - KEY `idx_account_id` (`accountId`) + * + * s2_friend_task表: + * - PRIMARY KEY (`id`) + * - KEY `idx_wechat_account_id` (`wechatAccountId`) + * - KEY `idx_wechat_id` (`wechatId`) + * - KEY `idx_create_time` (`createTime`) + * - KEY `idx_is_counted` (`is_counted`) + * + * s2_wechat_message表: + * - PRIMARY KEY (`id`) + * - KEY `idx_wechat_account_id` (`wechatAccountId`) + * - KEY `idx_msg_type` (`msgType`) + * - KEY `idx_create_time` (`createTime`) + * - KEY `idx_is_deleted` (`isDeleted`) + * - KEY `idx_is_counted` (`is_counted`) + */ + /** * 计算并更新账号健康分 * @@ -100,7 +132,7 @@ class WechatAccountHealthScoreService ->where('id', $accountId) ->find(); - Log::debug("查询账号数据: " . ($accountData ? "成功" : "失败")); + // 减少不必要的日志记录 } if (empty($accountData)) { @@ -116,11 +148,12 @@ class WechatAccountHealthScoreService throw new Exception($errorMsg); } - Log::debug("账号数据: accountId={$accountId}, wechatId={$wechatId}"); + // 减少不必要的日志记录 // 获取或创建评分记录 $scoreRecord = $this->getOrCreateScoreRecord($accountId, $wechatId); - Log::debug("获取评分记录: " . ($scoreRecord ? "成功" : "失败")); + $scoreSnapshotBefore = $this->buildScoreSnapshotForLogging($scoreRecord); + // 减少不必要的日志记录 // 计算基础分(只计算一次,除非强制重新计算) if (!$scoreRecord['baseScoreCalculated'] || $forceRecalculateBase) { @@ -131,7 +164,7 @@ class WechatAccountHealthScoreService $baseScoreData = $this->calculateBaseScore($accountData, $scoreRecord); $this->updateBaseScore($accountId, $baseScoreData); - Log::debug("基础分计算结果: " . json_encode($baseScoreData)); + // 减少不必要的日志记录 // 重新获取记录以获取最新数据 $scoreRecord = $this->getScoreRecord($accountId); @@ -166,6 +199,7 @@ class WechatAccountHealthScoreService 'lastNoFrequentTime' => $dynamicScoreData['lastNoFrequentTime'], 'consecutiveNoFrequentDays' => $dynamicScoreData['consecutiveNoFrequentDays'], 'isBanned' => $dynamicScoreData['isBanned'], + 'lastBanTime' => $dynamicScoreData['lastBanTime'], 'healthScore' => $healthScore, 'maxAddFriendPerDay' => $maxAddFriendPerDay, 'updateTime' => time() @@ -177,6 +211,19 @@ class WechatAccountHealthScoreService // 更新成功后,清除缓存 if ($updateResult !== false) { + $this->logScoreChangesIfNeeded( + $accountId, + $wechatId, + $scoreSnapshotBefore, + [ + 'frequentPenalty' => $dynamicScoreData['frequentPenalty'], + 'banPenalty' => $dynamicScoreData['banPenalty'], + 'noFrequentBonus' => $dynamicScoreData['noFrequentBonus'], + 'dynamicScore' => $dynamicScore, + 'healthScore' => $healthScore + ], + $dynamicScoreData + ); $this->clearScoreCache($accountId); } @@ -194,7 +241,7 @@ class WechatAccountHealthScoreService 'maxAddFriendPerDay' => $maxAddFriendPerDay ]; - Log::debug("健康分计算完成,返回结果: " . json_encode($result)); + // 减少不必要的日志记录 return $result; } catch (\PDOException $e) { @@ -212,6 +259,7 @@ class WechatAccountHealthScoreService /** * 获取或创建评分记录 + * 优化:使用事务和锁避免并发问题,减少重复查询 * * @param int $accountId 账号ID * @param string $wechatId 微信ID @@ -224,35 +272,96 @@ class WechatAccountHealthScoreService // 如果记录不存在,创建新记录 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; + // 使用事务避免并发问题 + Db::startTrans(); + try { + // 再次检查记录是否存在(避免并发问题) + $record = Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE) + ->where('accountId', $accountId) + ->lock(true) // 加锁防止并发插入 + ->find(); + + if (empty($record)) { + Log::info("为账号 {$accountId} 创建新的评分记录"); + + // 检查表中是否存在lastBanTime字段 + $this->ensureScoreTableFields(); + + // 创建新记录 + $data = [ + 'accountId' => $accountId, + 'wechatId' => $wechatId, + 'baseScore' => 0, + 'baseScoreCalculated' => 0, + 'baseInfoScore' => 0, + 'friendCountScore' => 0, + 'dynamicScore' => 0, + 'frequentCount' => 0, + 'consecutiveNoFrequentDays' => 0, + 'healthScore' => 0, + 'maxAddFriendPerDay' => 0, + 'lastBanTime' => null, + 'createTime' => time(), + 'updateTime' => time() + ]; + + Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE)->insert($data); + $record = $data; + } + + Db::commit(); + } catch (\Exception $e) { + Db::rollback(); + Log::error("创建评分记录失败: " . $e->getMessage()); + throw $e; + } } return $record; } + /** + * 确保评分表有所需字段 + * 优化:使用静态变量缓存结果,避免重复检查 + * + * @return void + */ + private function ensureScoreTableFields() + { + // 使用静态变量缓存检查结果,避免重复检查 + static $fieldsChecked = false; + + if ($fieldsChecked) { + return; + } + + try { + // 检查表中是否存在lastBanTime字段 + $hasLastBanTimeField = false; + $tableFields = Db::query("SHOW COLUMNS FROM " . self::TABLE_WECHAT_ACCOUNT_SCORE); + foreach ($tableFields as $field) { + if ($field['Field'] == 'lastBanTime') { + $hasLastBanTimeField = true; + break; + } + } + + // 如果字段不存在,添加字段 + if (!$hasLastBanTimeField) { + Log::info("添加lastBanTime字段到" . self::TABLE_WECHAT_ACCOUNT_SCORE . "表"); + Db::execute("ALTER TABLE " . self::TABLE_WECHAT_ACCOUNT_SCORE . " ADD COLUMN lastBanTime INT(11) DEFAULT NULL COMMENT '最后一次封号时间'"); + } + + $fieldsChecked = true; + } catch (\Exception $e) { + Log::error("检查或添加字段失败: " . $e->getMessage()); + // 出错时不影响后续逻辑,继续执行 + } + } + /** * 获取评分记录 + * 优化:使用多级缓存策略,提高缓存命中率 * * @param int $accountId 账号ID * @param bool $useCache 是否使用缓存(默认true) @@ -266,7 +375,7 @@ class WechatAccountHealthScoreService // 如果使用缓存且缓存存在,则直接返回缓存数据 if ($useCache && Cache::has($cacheKey)) { $cachedData = Cache::get($cacheKey); - Log::debug("从缓存获取评分记录,accountId: {$accountId}"); + // 减少日志记录,提高性能 return $cachedData ?: []; } @@ -277,8 +386,13 @@ class WechatAccountHealthScoreService // 如果记录存在且使用缓存,则缓存记录 if ($record && $useCache) { - Cache::set($cacheKey, $record, self::CACHE_TTL); - Log::debug("缓存评分记录,accountId: {$accountId}"); + // 根据数据更新频率设置不同的缓存时间 + // 如果记录最近更新过(1小时内),使用短期缓存 + $updateTime = $record['updateTime'] ?? 0; + $cacheTime = (time() - $updateTime < 3600) ? self::CACHE_TTL_SHORT : self::CACHE_TTL; + + Cache::set($cacheKey, $record, $cacheTime); + Log::debug("缓存评分记录,accountId: {$accountId}, 缓存时间: {$cacheTime}秒"); } return $record ?: []; @@ -349,7 +463,7 @@ class WechatAccountHealthScoreService ->where('accountId', $accountId) ->update($baseScoreData); - Log::debug("更新基础分,accountId: {$accountId}, 结果: " . ($result ? "成功" : "失败")); + // 减少不必要的日志记录 // 更新成功后,清除缓存 if ($result !== false) { @@ -373,7 +487,7 @@ class WechatAccountHealthScoreService { $cacheKey = self::CACHE_PREFIX . 'score:' . $accountId; $result = Cache::rm($cacheKey); - Log::debug("清除评分记录缓存,accountId: {$accountId}, 结果: " . ($result ? "成功" : "失败")); + // 减少不必要的日志记录 return $result; } @@ -556,7 +670,10 @@ class WechatAccountHealthScoreService 'frequentCount' => 0, 'lastNoFrequentTime' => null, 'consecutiveNoFrequentDays' => 0, - 'isBanned' => 0 + 'isBanned' => 0, + 'lastBanTime' => null, + 'frequentTaskIds' => [], + 'banMessageId' => null ]; if (empty($accountId) || empty($wechatId)) { @@ -564,8 +681,7 @@ class WechatAccountHealthScoreService return $result; } - // 计算30天前的时间戳(在多个方法中使用) - $thirtyDaysAgo = time() - (30 * 24 * 3600); + // 不再使用30天限制 // 检查添加好友记录表是否有记录,如果没有记录则动态分为0 // 使用EXISTS子查询优化性能,只检查是否存在记录,不需要计数 @@ -595,23 +711,27 @@ class WechatAccountHealthScoreService $result['frequentPenalty'] = $scoreRecord['frequentPenalty'] ?? 0; $result['noFrequentBonus'] = $scoreRecord['noFrequentBonus'] ?? 0; $result['banPenalty'] = $scoreRecord['banPenalty'] ?? 0; + $result['lastBanTime'] = $scoreRecord['lastBanTime'] ?? null; } - // 1. 检查频繁记录(从s2_friend_task表查询,只统计近30天) - $frequentData = $this->checkFrequentFromFriendTask($accountId, $wechatId, $scoreRecord, $thirtyDaysAgo); + // 1. 检查频繁记录(从s2_friend_task表查询,不限制时间) + $frequentData = $this->checkFrequentFromFriendTask($accountId, $wechatId, $scoreRecord); $result['lastFrequentTime'] = $frequentData['lastFrequentTime'] ?? null; $result['frequentCount'] = $frequentData['frequentCount'] ?? 0; $result['frequentPenalty'] = $frequentData['frequentPenalty'] ?? 0; + $result['frequentTaskIds'] = $frequentData['taskIds'] ?? []; - // 2. 检查封号记录(从s2_wechat_message表查询) - $banData = $this->checkBannedFromMessage($accountId, $wechatId, $thirtyDaysAgo); + // 2. 检查封号记录(从s2_wechat_message表查询,不限制时间) + $banData = $this->checkBannedFromMessage($accountId, $wechatId); if (!empty($banData)) { $result['isBanned'] = $banData['isBanned']; $result['banPenalty'] = $banData['banPenalty']; + $result['lastBanTime'] = $banData['lastBanTime']; + $result['banMessageId'] = $banData['messageId'] ?? null; } - // 3. 计算不频繁加分(基于近30天的频繁记录,反向参考频繁规则) - $noFrequentData = $this->calculateNoFrequentBonus($accountId, $wechatId, $frequentData, $thirtyDaysAgo); + // 3. 计算不频繁加分(基于频繁记录,反向参考频繁规则) + $noFrequentData = $this->calculateNoFrequentBonus($accountId, $wechatId, $frequentData); $result['noFrequentBonus'] = $noFrequentData['bonus'] ?? 0; $result['consecutiveNoFrequentDays'] = $noFrequentData['consecutiveDays'] ?? 0; $result['lastNoFrequentTime'] = $noFrequentData['lastNoFrequentTime'] ?? null; @@ -629,27 +749,27 @@ class WechatAccountHealthScoreService /** * 从s2_friend_task表检查频繁记录 * extra字段包含"操作过于频繁"即需要扣分 - * 只统计近30天的数据 + * 统计所有时间的数据(不限制30天) + * 每条记录只统计一次,使用is_counted字段标记 * * @param int $accountId 账号ID * @param string $wechatId 微信ID * @param array $scoreRecord 现有评分记录 - * @param int $thirtyDaysAgo 30天前的时间戳(可选,如果已计算则传入以避免重复计算) + * @param int $thirtyDaysAgo 已废弃参数,保留是为了兼容性 * @return array|null */ private function checkFrequentFromFriendTask($accountId, $wechatId, $scoreRecord, $thirtyDaysAgo = null) { - // 如果没有传入30天前的时间戳,则计算 - if ($thirtyDaysAgo === null) { - $thirtyDaysAgo = time() - (30 * 24 * 3600); - } + // 不再使用30天限制 - // 查询包含"操作过于频繁"的记录(只统计近30天) + // 减少不必要的日志记录 + + // 查询包含"操作过于频繁"的记录(统计所有时间且未被统计过的记录) // extra字段可能是文本或JSON格式,使用LIKE查询 // 优化查询:只查询必要的字段,减少数据传输量 + // 添加is_counted条件,只查询未被统计过的记录 $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); @@ -660,6 +780,12 @@ class WechatAccountHealthScoreService $query->where('extra', 'like', '%操作过于频繁%') ->whereOr('extra', 'like', '%"当前账号存在安全风险"%'); }) + ->where(function($query) { + // 只查询未被统计过的记录 + // 注意:需要兼容is_counted字段不存在的情况 + $query->where('is_counted', 0) + ->whereOr('is_counted', null); + }) ->order('createTime', 'desc') ->field('id, createTime, extra') ->select(); @@ -670,12 +796,47 @@ class WechatAccountHealthScoreService // 计算频繁次数(统计近30天内包含"操作过于频繁"的记录) $frequentCount = count($frequentTasks); + Log::info("找到 {$frequentCount} 条未统计的频繁记录,accountId: {$accountId}, wechatId: {$wechatId}"); + + // 标记这些记录为已统计 + if (!empty($frequentTasks)) { + $taskIds = array_column($frequentTasks, 'id'); + try { + // 检查表中是否存在is_counted字段 + $hasIsCountedField = false; + $tableFields = Db::query("SHOW COLUMNS FROM " . self::TABLE_FRIEND_TASK); + foreach ($tableFields as $field) { + if ($field['Field'] == 'is_counted') { + $hasIsCountedField = true; + break; + } + } + + // 如果字段不存在,添加字段 + if (!$hasIsCountedField) { + Log::info("添加is_counted字段到" . self::TABLE_FRIEND_TASK . "表"); + Db::execute("ALTER TABLE " . self::TABLE_FRIEND_TASK . " ADD COLUMN is_counted TINYINT(1) DEFAULT 0 COMMENT '是否已统计(0=未统计,1=已统计)'"); + } + + // 更新记录为已统计 + Db::table(self::TABLE_FRIEND_TASK) + ->where('id', 'in', $taskIds) + ->update(['is_counted' => 1]); + + // 减少不必要的日志记录 + } catch (\Exception $e) { + Log::error("标记频繁记录失败: " . $e->getMessage()); + // 出错时不影响后续逻辑,继续执行 + } + } + // 如果30天内没有频繁记录,清除扣分 if (empty($frequentTasks)) { return [ 'lastFrequentTime' => null, 'frequentCount' => 0, - 'frequentPenalty' => 0 + 'frequentPenalty' => 0, + 'taskIds' => [] ]; } @@ -683,69 +844,110 @@ class WechatAccountHealthScoreService $penalty = 0; if ($frequentCount == 1) { $penalty = self::PENALTY_FIRST_FREQUENT; // 首次频繁-15分 + Log::info("首次频繁,扣除 " . abs(self::PENALTY_FIRST_FREQUENT) . " 分,accountId: {$accountId}"); } elseif ($frequentCount >= 2) { $penalty = self::PENALTY_SECOND_FREQUENT; // 再次频繁-25分 + Log::info("再次频繁,扣除 " . abs(self::PENALTY_SECOND_FREQUENT) . " 分,accountId: {$accountId}"); } return [ 'lastFrequentTime' => $latestFrequentTime, 'frequentCount' => $frequentCount, - 'frequentPenalty' => $penalty + 'frequentPenalty' => $penalty, + 'taskIds' => $taskIds ]; } /** * 从s2_wechat_message表检查封号记录 * content包含"你的账号被限制"且msgType为10000 - * 只统计近30天的数据 + * 统计所有时间的数据(不限制30天) + * 每条记录只统计一次,使用is_counted字段标记 * * @param int $accountId 账号ID * @param string $wechatId 微信ID - * @param int $thirtyDaysAgo 30天前的时间戳(可选,如果已计算则传入以避免重复计算) + * @param int $thirtyDaysAgo 已废弃参数,保留是为了兼容性 * @return array|null */ private function checkBannedFromMessage($accountId, $wechatId, $thirtyDaysAgo = null) { - // 如果没有传入30天前的时间戳,则计算 - if ($thirtyDaysAgo === null) { - $thirtyDaysAgo = time() - (30 * 24 * 3600); - } + // 不再使用30天限制 - // 查询封号消息(只统计近30天) + // 减少不必要的日志记录 + + // 查询封号消息(统计所有时间且未被统计过的记录) // 优化查询:只查询必要的字段,减少数据传输量 $banMessage = Db::table(self::TABLE_WECHAT_MESSAGE) ->where('wechatAccountId', $accountId) ->where('msgType', 10000) ->where('content', 'like', '%你的账号被限制%') ->where('isDeleted', 0) - ->where('createTime', '>=', $thirtyDaysAgo) + ->where(function($query) { + // 只查询未被统计过的记录 + // 注意:需要兼容is_counted字段不存在的情况 + $query->where('is_counted', 0) + ->whereOr('is_counted', null); + }) ->field('id, createTime') // 只查询必要的字段 ->order('createTime', 'desc') ->find(); if (!empty($banMessage)) { + try { + // 检查表中是否存在is_counted字段 + $hasIsCountedField = false; + $tableFields = Db::query("SHOW COLUMNS FROM " . self::TABLE_WECHAT_MESSAGE); + foreach ($tableFields as $field) { + if ($field['Field'] == 'is_counted') { + $hasIsCountedField = true; + break; + } + } + + // 如果字段不存在,添加字段 + if (!$hasIsCountedField) { + Log::info("添加is_counted字段到" . self::TABLE_WECHAT_MESSAGE . "表"); + Db::execute("ALTER TABLE " . self::TABLE_WECHAT_MESSAGE . " ADD COLUMN is_counted TINYINT(1) DEFAULT 0 COMMENT '是否已统计(0=未统计,1=已统计)'"); + } + + // 更新记录为已统计 + Db::table(self::TABLE_WECHAT_MESSAGE) + ->where('id', $banMessage['id']) + ->update(['is_counted' => 1]); + + // 减少不必要的日志记录 + Log::info("发现封号记录,扣除 " . abs(self::PENALTY_BANNED) . " 分,accountId: {$accountId}"); + } catch (\Exception $e) { + Log::error("标记封号记录失败: " . $e->getMessage()); + // 出错时不影响后续逻辑,继续执行 + } + return [ 'isBanned' => 1, - 'banPenalty' => self::PENALTY_BANNED // 封号-60分 + 'banPenalty' => self::PENALTY_BANNED, // 封号-60分 + 'lastBanTime' => $banMessage['createTime'], + 'messageId' => $banMessage['id'] ]; } return [ 'isBanned' => 0, - 'banPenalty' => 0 + 'banPenalty' => 0, + 'lastBanTime' => null, + 'messageId' => null ]; } /** * 计算不频繁加分 - * 反向参考频繁规则:查询近30天的频繁记录,计算连续不频繁天数 - * 规则:30天内连续不频繁的,只要有一次频繁就得重新计算(重置连续不频繁天数) + * 反向参考频繁规则:计算连续不频繁天数 + * 规则:连续不频繁的,只要有一次频繁就得重新计算(重置连续不频繁天数) * 如果连续3天没有频繁,则每天+5分 * * @param int $accountId 账号ID * @param string $wechatId 微信ID * @param array $frequentData 频繁数据(包含lastFrequentTime和frequentCount) - * @param int $thirtyDaysAgo 30天前的时间戳(可选,如果已计算则传入以避免重复计算) + * @param int $thirtyDaysAgo 已废弃参数,保留是为了兼容性 * @return array 包含bonus、consecutiveDays、lastNoFrequentTime */ private function calculateNoFrequentBonus($accountId, $wechatId, $frequentData, $thirtyDaysAgo = null) @@ -760,31 +962,22 @@ class WechatAccountHealthScoreService 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)); + // 情况1:没有频繁记录,说明一直连续不频繁 + // 默认给30天的连续不频繁天数(可以根据需要调整) + $consecutiveDays = 30; } else { - // 情况2:30天内有频繁记录,从最后一次频繁时间开始重新计算连续不频繁天数 + // 情况2:有频繁记录,从最后一次频繁时间开始重新计算连续不频繁天数 // 只要有一次频繁,连续不频繁天数就从最后一次频繁时间开始重新计算 // 计算从最后一次频繁时间到现在,连续多少天没有频繁 $timeDiff = $currentTime - $lastFrequentTime; $consecutiveDays = floor($timeDiff / 86400); // 向下取整,得到完整的天数 - - // 边界情况:如果最后一次频繁时间在30天前(理论上不应该发生,因为查询已经限制了30天),则按30天处理 - if ($lastFrequentTime < $thirtyDaysAgo) { - $consecutiveDays = min(30, floor(($currentTime - $thirtyDaysAgo) / 86400)); - } } // 如果连续3天或以上没有频繁,则每天+5分 @@ -800,6 +993,157 @@ class WechatAccountHealthScoreService return $result; } + /** + * 构建日志快照(用于对比前后分值) + * + * @param array $scoreRecord + * @return array + */ + private function buildScoreSnapshotForLogging($scoreRecord) + { + $baseScore = $scoreRecord['baseScore'] ?? self::DEFAULT_BASE_SCORE; + $dynamicScore = $scoreRecord['dynamicScore'] ?? 0; + $healthScore = $scoreRecord['healthScore'] ?? ($baseScore + $dynamicScore); + + return [ + 'frequentPenalty' => $scoreRecord['frequentPenalty'] ?? 0, + 'banPenalty' => $scoreRecord['banPenalty'] ?? 0, + 'noFrequentBonus' => $scoreRecord['noFrequentBonus'] ?? 0, + 'dynamicScore' => $dynamicScore, + 'healthScore' => $healthScore + ]; + } + + /** + * 根据前后快照写加减分日志 + * + * @param int $accountId + * @param string $wechatId + * @param array $before + * @param array $after + * @param array $context + * @return void + */ + private function logScoreChangesIfNeeded($accountId, $wechatId, array $before, array $after, array $context = []) + { + $healthBefore = $before['healthScore'] ?? 0; + $healthAfter = $after['healthScore'] ?? 0; + + $this->recordScoreLog($accountId, $wechatId, 'frequentPenalty', $before['frequentPenalty'] ?? 0, $after['frequentPenalty'] ?? 0, [ + 'category' => 'penalty', + 'source' => 'friend_task', + 'sourceId' => !empty($context['frequentTaskIds']) ? $context['frequentTaskIds'][0] : null, + 'extra' => [ + 'taskIds' => $context['frequentTaskIds'] ?? [], + 'frequentCount' => $context['frequentCount'] ?? 0, + 'lastFrequentTime' => $context['lastFrequentTime'] ?? null + ], + 'totalScoreBefore' => $healthBefore, + 'totalScoreAfter' => $healthAfter + ]); + + $this->recordScoreLog($accountId, $wechatId, 'banPenalty', $before['banPenalty'] ?? 0, $after['banPenalty'] ?? 0, [ + 'category' => 'penalty', + 'source' => 'wechat_message', + 'sourceId' => $context['banMessageId'] ?? null, + 'extra' => [ + 'lastBanTime' => $context['lastBanTime'] ?? null + ], + 'totalScoreBefore' => $healthBefore, + 'totalScoreAfter' => $healthAfter + ]); + + $this->recordScoreLog($accountId, $wechatId, 'noFrequentBonus', $before['noFrequentBonus'] ?? 0, $after['noFrequentBonus'] ?? 0, [ + 'category' => 'bonus', + 'source' => 'system', + 'extra' => [ + 'consecutiveDays' => $context['consecutiveNoFrequentDays'] ?? 0, + 'lastNoFrequentTime' => $context['lastNoFrequentTime'] ?? null + ], + 'totalScoreBefore' => $healthBefore, + 'totalScoreAfter' => $healthAfter + ]); + + $this->recordScoreLog($accountId, $wechatId, 'dynamicScore', $before['dynamicScore'] ?? 0, $after['dynamicScore'] ?? 0, [ + 'category' => 'dynamic_total', + 'source' => 'system', + 'totalScoreBefore' => $healthBefore, + 'totalScoreAfter' => $healthAfter + ]); + + $this->recordScoreLog($accountId, $wechatId, 'healthScore', $before['healthScore'] ?? 0, $after['healthScore'] ?? 0, [ + 'category' => 'health_total', + 'source' => 'system', + 'totalScoreBefore' => $healthBefore, + 'totalScoreAfter' => $healthAfter + ]); + } + + /** + * 插入健康分加减分日志 + * + * @param int $accountId + * @param string $wechatId + * @param string $field + * @param int|null $beforeValue + * @param int|null $afterValue + * @param array $context + * @return void + */ + private function recordScoreLog($accountId, $wechatId, $field, $beforeValue, $afterValue, array $context = []) + { + $beforeValue = (int)($beforeValue ?? 0); + $afterValue = (int)($afterValue ?? 0); + + if ($beforeValue === $afterValue) { + return; + } + + $extraPayload = $context['extra'] ?? null; + if (is_array($extraPayload)) { + $extraPayload = json_encode($extraPayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + } elseif (!is_string($extraPayload)) { + $extraPayload = null; + } + + $sourceId = null; + if (array_key_exists('sourceId', $context)) { + $sourceId = $context['sourceId']; + } + + $totalScoreBefore = null; + if (array_key_exists('totalScoreBefore', $context)) { + $totalScoreBefore = $context['totalScoreBefore']; + } + + $totalScoreAfter = null; + if (array_key_exists('totalScoreAfter', $context)) { + $totalScoreAfter = $context['totalScoreAfter']; + } + + $data = [ + 'accountId' => $accountId, + 'wechatId' => $wechatId, + 'field' => $field, + 'changeValue' => $afterValue - $beforeValue, + 'valueBefore' => $beforeValue, + 'valueAfter' => $afterValue, + 'category' => $context['category'] ?? null, + 'source' => $context['source'] ?? null, + 'sourceId' => $sourceId, + 'extra' => $extraPayload, + 'totalScoreBefore' => $totalScoreBefore, + 'totalScoreAfter' => $totalScoreAfter, + 'createTime' => time() + ]; + + try { + Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE_LOG)->insert($data); + } catch (\Exception $e) { + Log::error("记录健康分加减分日志失败,accountId: {$accountId}, field: {$field}, 错误: " . $e->getMessage()); + } + } + /** * 根据健康分计算每日最大加人次数 * 公式:每日最大加人次数 = 健康分 * 0.2 @@ -814,14 +1158,16 @@ class WechatAccountHealthScoreService /** * 批量计算并更新多个账号的健康分 + * 优化:使用多线程处理、优化批处理逻辑、减少日志记录 * * @param array $accountIds 账号ID数组(为空则处理所有账号) * @param int $batchSize 每批处理数量 * @param bool $forceRecalculateBase 是否强制重新计算基础分 + * @param bool $useMultiThread 是否使用多线程处理(需要pcntl扩展支持) * @return array 处理结果统计 * @throws Exception 如果参数无效或批量处理过程中出现严重错误 */ - public function batchCalculateAndUpdate($accountIds = [], $batchSize = 100, $forceRecalculateBase = false) + public function batchCalculateAndUpdate($accountIds = [], $batchSize = 50, $forceRecalculateBase = false, $useMultiThread = false) { // 参数验证 if (!is_array($accountIds)) { @@ -836,9 +1182,16 @@ class WechatAccountHealthScoreService throw new Exception($errorMsg); } + // 检查是否支持多线程 + if ($useMultiThread && !function_exists('pcntl_fork')) { + $useMultiThread = false; + Log::warning("系统不支持pcntl扩展,无法使用多线程处理,将使用单线程模式"); + } + try { $startTime = microtime(true); - Log::info("开始批量计算健康分,batchSize: {$batchSize}, forceRecalculateBase: " . ($forceRecalculateBase ? 'true' : 'false')); + Log::info("开始批量计算健康分,batchSize: {$batchSize}, forceRecalculateBase: " . ($forceRecalculateBase ? 'true' : 'false') . + ", useMultiThread: " . ($useMultiThread ? 'true' : 'false')); $stats = [ 'total' => 0, @@ -855,43 +1208,69 @@ class WechatAccountHealthScoreService ->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) . " 个账号"); + $stats['total'] = count($accountIds); + Log::info("需要处理的账号总数: {$stats['total']}"); - 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()); + // 优化:减小批次大小,提高并行处理效率 + $batchSize = min($batchSize, 50); + + // 分批处理 + $batches = array_chunk($accountIds, $batchSize); + $batchCount = count($batches); + Log::info("分批处理,共 {$batchCount} 批"); + + // 多线程处理 + if ($useMultiThread && $batchCount > 1) { + $childPids = []; + $maxProcesses = 4; // 最大并行进程数 + $runningProcesses = 0; + + for ($i = 0; $i < $batchCount; $i++) { + // 如果达到最大进程数,等待某个子进程结束 + if ($runningProcesses >= $maxProcesses) { + $pid = pcntl_wait($status); + $runningProcesses--; + } + + // 创建子进程 + $pid = pcntl_fork(); + + if ($pid == -1) { + // 创建进程失败 + Log::error("创建子进程失败"); + continue; + } elseif ($pid == 0) { + // 子进程 + $this->processBatch($batches[$i], $i, $batchCount, $forceRecalculateBase); + exit(0); + } else { + // 父进程 + $childPids[] = $pid; + $runningProcesses++; + } + } + + // 等待所有子进程结束 + foreach ($childPids as $pid) { + pcntl_waitpid($pid, $status); + } + + Log::info("所有批次处理完成"); + } else { + // 单线程处理 + foreach ($batches as $batchIndex => $batch) { + $batchStats = $this->processBatch($batch, $batchIndex, $batchCount, $forceRecalculateBase); + $stats['success'] += $batchStats['success']; + $stats['failed'] += $batchStats['failed']; + $stats['errors'] = array_merge($stats['errors'], $batchStats['errors']); } } - $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; + $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); @@ -903,6 +1282,68 @@ class WechatAccountHealthScoreService } } + /** + * 处理单个批次的账号 + * + * @param array $batch 批次账号ID数组 + * @param int $batchIndex 批次索引 + * @param int $batchCount 总批次数 + * @param bool $forceRecalculateBase 是否强制重新计算基础分 + * @return array 处理结果统计 + */ + private function processBatch($batch, $batchIndex, $batchCount, $forceRecalculateBase) + { + $batchStartTime = microtime(true); + Log::info("开始处理第 " . ($batchIndex + 1) . " 批,共 " . count($batch) . " 个账号"); + + $stats = [ + 'success' => 0, + 'failed' => 0, + 'errors' => [] + ]; + + // 优化:预先获取账号数据,减少重复查询 + $accountIds = implode(',', $batch); + $accountDataMap = []; + if (!empty($batch)) { + $accountDataList = Db::table(self::TABLE_WECHAT_ACCOUNT) + ->where('id', 'in', $batch) + ->select(); + + foreach ($accountDataList as $accountData) { + $accountDataMap[$accountData['id']] = $accountData; + } + } + + // 批量处理账号 + foreach ($batch as $accountId) { + try { + $accountData = $accountDataMap[$accountId] ?? null; + $this->calculateAndUpdate($accountId, $accountData, $forceRecalculateBase); + $stats['success']++; + + // 减少日志记录,每10个账号记录一次进度 + if ($stats['success'] % 10 == 0) { + Log::debug("批次 " . ($batchIndex + 1) . " 已处理 {$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) . "/" . $batchCount . " 批处理完成,耗时: {$batchDuration}秒," . + "成功: {$stats['success']},失败: {$stats['failed']}"); + + return $stats; + } + /** * 记录频繁事件(已废弃,改为从s2_friend_task表自动检测) * 保留此方法以兼容旧代码,实际频繁检测在calculateDynamicScore中完成 @@ -985,6 +1426,7 @@ class WechatAccountHealthScoreService /** * 获取账号健康分信息 + * 优化:使用多级缓存策略,提高缓存命中率 * * @param int $accountId 账号ID * @param bool $useCache 是否使用缓存(默认true) @@ -1005,7 +1447,7 @@ class WechatAccountHealthScoreService // 如果使用缓存且缓存存在,则直接返回缓存数据 if ($useCache && !$forceRecalculate && Cache::has($cacheKey)) { $cachedData = Cache::get($cacheKey); - Log::debug("从缓存获取健康分信息,accountId: {$accountId}"); + // 减少日志记录,提高性能 return $cachedData; } @@ -1032,13 +1474,20 @@ class WechatAccountHealthScoreService 'baseScoreCalculated' => $scoreRecord['baseScoreCalculated'] ?? 0, 'lastFrequentTime' => $scoreRecord['lastFrequentTime'] ?? null, 'frequentCount' => $scoreRecord['frequentCount'] ?? 0, - 'isBanned' => $scoreRecord['isBanned'] ?? 0 + 'isBanned' => $scoreRecord['isBanned'] ?? 0, + 'lastBanTime' => $scoreRecord['lastBanTime'] ?? null ]; // 如果使用缓存,则缓存健康分信息 if ($useCache) { - Cache::set($cacheKey, $healthScoreInfo, self::CACHE_TTL); - Log::debug("缓存健康分信息,accountId: {$accountId}"); + // 根据数据更新频率设置不同的缓存时间 + // 如果有频繁记录或封号记录,使用短期缓存 + $cacheTime = (!empty($scoreRecord['lastFrequentTime']) || !empty($scoreRecord['isBanned'])) + ? self::CACHE_TTL_SHORT + : self::CACHE_TTL; + + Cache::set($cacheKey, $healthScoreInfo, $cacheTime); + Log::debug("缓存健康分信息,accountId: {$accountId}, 缓存时间: {$cacheTime}秒"); } return $healthScoreInfo; @@ -1058,7 +1507,7 @@ class WechatAccountHealthScoreService // 同时清除评分记录缓存 $this->clearScoreCache($accountId); - Log::debug("清除健康分信息缓存,accountId: {$accountId}, 结果: " . ($result ? "成功" : "失败")); + // 减少不必要的日志记录 return $result; } } diff --git a/Server/application/cunkebao/config/route.php b/Server/application/cunkebao/config/route.php index e6f7ee034..c34fcd076 100644 --- a/Server/application/cunkebao/config/route.php +++ b/Server/application/cunkebao/config/route.php @@ -36,13 +36,13 @@ Route::group('v1/', function () { Route::get(':id/summary', 'app\cunkebao\controller\wechat\GetWechatOnDeviceSummarizeV1Controller@index'); Route::get(':id/friends', 'app\cunkebao\controller\wechat\GetWechatOnDeviceFriendsV1Controller@index'); Route::get('getWechatInfo', 'app\cunkebao\controller\wechat\GetWechatController@getWechatInfo'); - Route::get(':wechatId', 'app\cunkebao\controller\wechat\GetWechatProfileV1Controller@index'); - Route::post('transfer-friends', 'app\cunkebao\controller\wechat\PostTransferFriends@index'); // 微信好友转移 - + Route::get('overview', 'app\cunkebao\controller\wechat\GetWechatOverviewV1Controller@index'); // 获取微信账号概览数据 + Route::get('moments', 'app\cunkebao\controller\wechat\GetWechatMomentsV1Controller@index'); // 获取微信朋友圈 Route::get('count', 'app\cunkebao\controller\DeviceWechat@count'); Route::get('device-count', 'app\cunkebao\controller\DeviceWechat@deviceCount'); // 获取有登录微信的设备数量 Route::put('refresh', 'app\cunkebao\controller\DeviceWechat@refresh'); // 刷新设备微信状态 - + Route::post('transfer-friends', 'app\cunkebao\controller\wechat\PostTransferFriends@index'); // 微信好友转移 + Route::get(':wechatId', 'app\cunkebao\controller\wechat\GetWechatProfileV1Controller@index'); }); // 获客场景相关 diff --git a/Server/application/cunkebao/controller/ContentLibraryController.php b/Server/application/cunkebao/controller/ContentLibraryController.php index 692911232..bcf8e50bf 100644 --- a/Server/application/cunkebao/controller/ContentLibraryController.php +++ b/Server/application/cunkebao/controller/ContentLibraryController.php @@ -1061,6 +1061,7 @@ class ContentLibraryController extends Controller $where = [ ['isDel', '=', 0], // 未删除 ['status', '=', 1], // 已开启 + ['id', '=', 99], // 已开启 ]; // 查询符合条件的内容库 @@ -1225,7 +1226,7 @@ class ContentLibraryController extends Controller foreach ($friends as $friend) { $processedFriends++; - + // 如果配置了API并且需要主动获取朋友圈 if ($needFetch) { try { @@ -1264,9 +1265,9 @@ class ContentLibraryController extends Controller } // 如果指定了采集类型,进行过滤 - if (!empty($catchTypes)) { + /*if (!empty($catchTypes)) { $query->whereIn('type', $catchTypes); - } + }*/ // 获取最近20条朋友圈 $moments = $query->page(1, 20)->select(); @@ -1289,7 +1290,7 @@ class ContentLibraryController extends Controller continue; } - // 如果启用了AI处理 + /* // 如果启用了AI处理 if (!empty($library['aiEnabled']) && !empty($content)) { try { $contentAi = $this->aiRewrite($library, $content); @@ -1300,7 +1301,7 @@ class ContentLibraryController extends Controller \think\facade\Log::error('AI处理失败: ' . $e->getMessage() . ' [朋友圈ID: ' . ($moment['id'] ?? 'unknown') . ']'); $moment['contentAi'] = ''; } - } + }*/ // 保存到内容库的content_item表 if ($this->saveMomentToContentItem($moment, $library['id'], $friend, $nickname)) { diff --git a/Server/application/cunkebao/controller/wechat/GetWechatMomentsV1Controller.php b/Server/application/cunkebao/controller/wechat/GetWechatMomentsV1Controller.php new file mode 100644 index 000000000..b34ef7ff1 --- /dev/null +++ b/Server/application/cunkebao/controller/wechat/GetWechatMomentsV1Controller.php @@ -0,0 +1,204 @@ +getUserInfo('companyId')) + ->column('id'); + } + + /** + * 非主操盘手仅可查看分配到的设备 + * + * @return array + */ + protected function getUserDevicesId(): array + { + return DeviceUserModel::where([ + 'userId' => $this->getUserInfo('id'), + 'companyId' => $this->getUserInfo('companyId'), + ])->column('deviceId'); + } + + /** + * 获取当前用户可访问的设备ID + * + * @return array + */ + protected function getDevicesId(): array + { + return ($this->getUserInfo('isAdmin') == UserModel::ADMIN_STP) + ? $this->getCompanyDevicesId() + : $this->getUserDevicesId(); + } + + /** + * 获取用户可访问的微信ID集合 + * + * @return array + * @throws \Exception + */ + protected function getAccessibleWechatIds(): array + { + $deviceIds = $this->getDevicesId(); + if (empty($deviceIds)) { + throw new \Exception('暂无可用设备', 200); + } + + return DeviceWechatLoginModel::distinct(true) + ->where('companyId', $this->getUserInfo('companyId')) + ->whereIn('deviceId', $deviceIds) + ->column('wechatId'); + } + + /** + * 查看朋友圈列表 + * + * @return \think\response\Json + */ + public function index() + { + try { + $wechatId = $this->request->param('wechatId/s', ''); + if (empty($wechatId)) { + return ResponseHelper::error('wechatId不能为空'); + } + + // 权限校验:只能查看当前账号可访问的微信 + $accessibleWechatIds = $this->getAccessibleWechatIds(); + if (!in_array($wechatId, $accessibleWechatIds, true)) { + return ResponseHelper::error('无权查看该微信的朋友圈', 403); + } + + // 获取对应的微信账号ID + $accountId = Db::table('s2_wechat_account') + ->where('wechatId', $wechatId) + ->value('id'); + + if (empty($accountId)) { + return ResponseHelper::error('微信账号不存在或尚未同步', 404); + } + + $query = Db::table('s2_wechat_moments') + ->where('wechatAccountId', $accountId); + + // 关键词搜索 + if ($keyword = trim((string)$this->request->param('keyword', ''))) { + $query->whereLike('content', '%' . $keyword . '%'); + } + + // 类型筛选 + $type = $this->request->param('type', ''); + if ($type !== '' && $type !== null) { + $query->where('type', (int)$type); + } + + // 时间筛选 + $startTime = $this->request->param('startTime', ''); + $endTime = $this->request->param('endTime', ''); + if ($startTime || $endTime) { + $start = $startTime ? strtotime($startTime) : 0; + $end = $endTime ? strtotime($endTime) : time(); + if ($start && $end && $end < $start) { + return ResponseHelper::error('结束时间不能早于开始时间'); + } + $query->whereBetween('createTime', [$start ?: 0, $end ?: time()]); + } + + $page = (int)$this->request->param('page', 1); + $limit = (int)$this->request->param('limit', 10); + + $paginator = $query->order('createTime', 'desc') + ->paginate($limit, false, ['page' => $page]); + + $list = array_map(function ($item) { + return $this->formatMomentRow($item); + }, $paginator->items()); + + return ResponseHelper::success([ + 'list' => $list, + 'total' => $paginator->total(), + 'page' => $page, + 'limit' => $limit, + ]); + } catch (\Exception $e) { + return ResponseHelper::error($e->getMessage(), $e->getCode() ?: 500); + } + } + + /** + * 格式化朋友圈数据 + * + * @param array $row + * @return array + */ + protected function formatMomentRow(array $row): array + { + $formatTime = function ($timestamp) { + if (empty($timestamp)) { + return ''; + } + return is_numeric($timestamp) + ? date('Y-m-d H:i:s', $timestamp) + : date('Y-m-d H:i:s', strtotime($timestamp)); + }; + + return [ + 'id' => (int)$row['id'], + 'snsId' => $row['snsId'] ?? '', + 'type' => (int)($row['type'] ?? 0), + 'content' => $row['content'] ?? '', + 'commentList' => $this->decodeJson($row['commentList'] ?? null), + 'likeList' => $this->decodeJson($row['likeList'] ?? null), + 'resUrls' => $this->decodeJson($row['resUrls'] ?? null), + 'createTime' => $formatTime($row['createTime'] ?? null), + 'momentEntity' => [ + 'lat' => $row['lat'] ?? 0, + 'lng' => $row['lng'] ?? 0, + 'location' => $row['location'] ?? '', + 'picSize' => $row['picSize'] ?? 0, + 'userName' => $row['userName'] ?? '', + ], + ]; + } + + /** + * JSON字段解析 + * + * @param mixed $value + * @return array + */ + protected function decodeJson($value): array + { + if (empty($value)) { + return []; + } + + if (is_array($value)) { + return $value; + } + + $decoded = json_decode($value, true); + return $decoded ?: []; + } +} + diff --git a/Server/application/cunkebao/controller/wechat/GetWechatOverviewV1Controller.php b/Server/application/cunkebao/controller/wechat/GetWechatOverviewV1Controller.php new file mode 100644 index 000000000..a48156def --- /dev/null +++ b/Server/application/cunkebao/controller/wechat/GetWechatOverviewV1Controller.php @@ -0,0 +1,411 @@ +request->param('wechatId', ''); + + if (empty($wechatId)) { + return ResponseHelper::error('微信ID不能为空'); + } + + $companyId = $this->getUserInfo('companyId'); + + // 获取微信账号ID(accountId) + $account = Db::table('s2_wechat_account') + ->where('wechatId', $wechatId) + ->find(); + + if (empty($account)) { + return ResponseHelper::error('微信账号不存在'); + } + + $accountId = $account['id']; + + // 1. 健康分评估 + $healthScoreData = $this->getHealthScoreAssessment($accountId, $wechatId); + + // 2. 账号价值(模拟数据) + $accountValue = $this->getAccountValue($accountId); + + // 3. 今日价值变化(模拟数据) + $todayValueChange = $this->getTodayValueChange($accountId); + + // 4. 好友总数 + $totalFriends = $this->getTotalFriends($wechatId, $companyId); + + // 5. 今日新增好友 + $todayNewFriends = $this->getTodayNewFriends($wechatId); + + // 6. 高价群聊 + $highValueChatrooms = $this->getHighValueChatrooms($wechatId, $companyId); + + // 7. 今日新增群聊 + $todayNewChatrooms = $this->getTodayNewChatrooms($wechatId, $companyId); + + $result = [ + 'healthScoreAssessment' => $healthScoreData, + 'accountValue' => $accountValue, + 'todayValueChange' => $todayValueChange, + 'totalFriends' => $totalFriends, + 'todayNewFriends' => $todayNewFriends, + 'highValueChatrooms' => $highValueChatrooms, + 'todayNewChatrooms' => $todayNewChatrooms, + ]; + + return ResponseHelper::success($result); + + } catch (\Exception $e) { + return ResponseHelper::error($e->getMessage(), $e->getCode() ?: 500); + } + } + + /** + * 获取健康分评估数据 + * + * @param int $accountId 账号ID + * @param string $wechatId 微信ID + * @return array + */ + protected function getHealthScoreAssessment($accountId, $wechatId) + { + // 获取健康分信息 + $healthScoreService = new WechatAccountHealthScoreService(); + $healthScoreInfo = $healthScoreService->getHealthScore($accountId); + + $healthScore = $healthScoreInfo['healthScore'] ?? 0; + $maxAddFriendPerDay = $healthScoreInfo['maxAddFriendPerDay'] ?? 0; + + // 获取今日已加好友数 + $todayAdded = $this->getTodayAddedCount($wechatId); + + // 获取最后添加时间 + $lastAddTime = $this->getLastAddTime($wechatId); + + // 判断状态标签 + $statusTag = $todayAdded > 0 ? '已添加加人' : ''; + + // 获取基础构成 + $baseComposition = $this->getBaseComposition($healthScoreInfo); + + // 获取动态记录 + $dynamicRecords = $this->getDynamicRecords($healthScoreInfo); + + return [ + 'score' => $healthScore, + 'dailyLimit' => $maxAddFriendPerDay, + 'todayAdded' => $todayAdded, + 'lastAddTime' => $lastAddTime, + 'statusTag' => $statusTag, + 'baseComposition' => $baseComposition, + 'dynamicRecords' => $dynamicRecords, + ]; + } + + /** + * 获取基础构成数据 + * + * @param array $healthScoreInfo 健康分信息 + * @return array + */ + protected function getBaseComposition($healthScoreInfo) + { + $baseScore = $healthScoreInfo['baseScore'] ?? 0; + $baseInfoScore = $healthScoreInfo['baseInfoScore'] ?? 0; + $friendCountScore = $healthScoreInfo['friendCountScore'] ?? 0; + $friendCount = $healthScoreInfo['friendCount'] ?? 0; + + // 账号基础分(默认60分) + $accountBaseScore = 60; + + // 已修改微信号(如果baseInfoScore > 0,说明已修改) + $isModifiedAlias = $baseInfoScore > 0; + + $composition = [ + [ + 'name' => '账号基础分', + 'score' => $accountBaseScore, + 'formatted' => '+' . $accountBaseScore, + ] + ]; + + // 如果已修改微信号,添加基础信息分 + if ($isModifiedAlias) { + $composition[] = [ + 'name' => '已修改微信号', + 'score' => $baseInfoScore, + 'formatted' => '+' . $baseInfoScore, + ]; + } + + // 好友数量加成 + if ($friendCountScore > 0) { + $composition[] = [ + 'name' => '好友数量加成', + 'score' => $friendCountScore, + 'formatted' => '+' . $friendCountScore, + 'friendCount' => $friendCount, // 显示好友总数 + ]; + } + + return $composition; + } + + /** + * 获取动态记录数据 + * + * @param array $healthScoreInfo 健康分信息 + * @return array + */ + protected function getDynamicRecords($healthScoreInfo) + { + $records = []; + + $frequentPenalty = $healthScoreInfo['frequentPenalty'] ?? 0; + $frequentCount = $healthScoreInfo['frequentCount'] ?? 0; + $banPenalty = $healthScoreInfo['banPenalty'] ?? 0; + $isBanned = $healthScoreInfo['isBanned'] ?? 0; + $noFrequentBonus = $healthScoreInfo['noFrequentBonus'] ?? 0; + $consecutiveNoFrequentDays = $healthScoreInfo['consecutiveNoFrequentDays'] ?? 0; + $lastFrequentTime = $healthScoreInfo['lastFrequentTime'] ?? null; + + // 频繁扣分记录 + // 根据frequentCount判断是首次还是再次 + // frequentPenalty存储的是当前状态的扣分(-15或-25),不是累计值 + if ($frequentCount > 0 && $frequentPenalty < 0) { + if ($frequentCount == 1) { + // 首次频繁:-15分 + $records[] = [ + 'name' => '首次触发限额', + 'score' => $frequentPenalty, + 'formatted' => (string)$frequentPenalty, + 'type' => 'penalty', + 'time' => $lastFrequentTime ? date('Y-m-d H:i:s', $lastFrequentTime) : null, + ]; + } else { + // 再次频繁:-25分 + $records[] = [ + 'name' => '再次触发限额', + 'score' => $frequentPenalty, + 'formatted' => (string)$frequentPenalty, + 'type' => 'penalty', + 'time' => $lastFrequentTime ? date('Y-m-d H:i:s', $lastFrequentTime) : null, + ]; + } + } + + // 封号扣分记录 + if ($isBanned && $banPenalty < 0) { + $lastBanTime = $healthScoreInfo['lastBanTime'] ?? null; + $records[] = [ + 'name' => '封号', + 'score' => $banPenalty, + 'formatted' => (string)$banPenalty, + 'type' => 'penalty', + 'time' => $lastBanTime ? date('Y-m-d H:i:s', $lastBanTime) : null, + ]; + } + + // 不频繁加分记录 + if ($noFrequentBonus > 0 && $consecutiveNoFrequentDays >= 3) { + $lastNoFrequentTime = $healthScoreInfo['lastNoFrequentTime'] ?? null; + $records[] = [ + 'name' => '连续' . $consecutiveNoFrequentDays . '天不触发频繁', + 'score' => $noFrequentBonus, + 'formatted' => '+' . $noFrequentBonus, + 'type' => 'bonus', + 'time' => $lastNoFrequentTime ? date('Y-m-d H:i:s', $lastNoFrequentTime) : null, + ]; + } + + return $records; + } + + /** + * 获取今日已加好友数 + * + * @param string $wechatId 微信ID + * @return int + */ + protected function getTodayAddedCount($wechatId) + { + $start = strtotime(date('Y-m-d 00:00:00')); + $end = strtotime(date('Y-m-d 23:59:59')); + + return Db::table('s2_friend_task') + ->where('wechatId', $wechatId) + ->whereBetween('createTime', [$start, $end]) + ->count(); + } + + /** + * 获取最后添加时间 + * + * @param string $wechatId 微信ID + * @return string + */ + protected function getLastAddTime($wechatId) + { + $lastTask = Db::table('s2_friend_task') + ->where('wechatId', $wechatId) + ->order('createTime', 'desc') + ->find(); + + if (empty($lastTask) || empty($lastTask['createTime'])) { + return ''; + } + + return date('H:i:s', $lastTask['createTime']); + } + + /** + * 获取账号价值(模拟数据) + * + * @param int $accountId 账号ID + * @return array + */ + protected function getAccountValue($accountId) + { + // TODO: 后续替换为真实计算逻辑 + // 模拟数据:¥29,800 + $value = 29800; + + return [ + 'value' => $value, + 'formatted' => '¥' . number_format($value, 0, '.', ','), + ]; + } + + /** + * 获取今日价值变化(模拟数据) + * + * @param int $accountId 账号ID + * @return array + */ + protected function getTodayValueChange($accountId) + { + // TODO: 后续替换为真实计算逻辑 + // 模拟数据:+500 + $change = 500; + + return [ + 'change' => $change, + 'formatted' => $change > 0 ? '+' . $change : (string)$change, + 'isPositive' => $change > 0, + ]; + } + + /** + * 获取好友总数 + * + * @param string $wechatId 微信ID + * @param int $companyId 公司ID + * @return int + */ + protected function getTotalFriends($wechatId, $companyId) + { + // 优先从 s2_wechat_account 表获取 + $account = Db::table('s2_wechat_account') + ->where('wechatId', $wechatId) + ->field('totalFriend') + ->find(); + + if (!empty($account) && isset($account['totalFriend'])) { + return (int)$account['totalFriend']; + } + + // 如果 totalFriend 为空,则从 s2_wechat_friend 表统计 + return Db::table('s2_wechat_friend') + ->where('ownerWechatId', $wechatId) + ->where('isDeleted', 0) + ->count(); + } + + /** + * 获取今日新增好友数 + * + * @param string $wechatId 微信ID + * @return int + */ + protected function getTodayNewFriends($wechatId) + { + $start = strtotime(date('Y-m-d 00:00:00')); + $end = strtotime(date('Y-m-d 23:59:59')); + + // 从 s2_wechat_friend 表统计今日新增 + return Db::table('s2_wechat_friend') + ->where('ownerWechatId', $wechatId) + ->whereBetween('createTime', [$start, $end]) + ->where('isDeleted', 0) + ->count(); + } + + /** + * 获取高价群聊数量 + * 高价群聊定义:群成员数 >= 50 的群聊 + * + * @param string $wechatId 微信ID + * @param int $companyId 公司ID + * @return int + */ + protected function getHighValueChatrooms($wechatId, $companyId) + { + // 高价群聊定义:群成员数 >= 50 + $minMemberCount = 50; + + // 查询该微信账号下的高价群聊 + // 使用子查询统计每个群的成员数 + $result = Db::query(" + SELECT COUNT(DISTINCT c.chatroomId) as count + FROM s2_wechat_chatroom c + INNER JOIN ( + SELECT chatroomId, COUNT(*) as memberCount + FROM s2_wechat_chatroom_member + GROUP BY chatroomId + HAVING memberCount >= ? + ) m ON c.chatroomId = m.chatroomId + WHERE c.wechatAccountWechatId = ? + AND c.isDeleted = 0 + ", [$minMemberCount, $wechatId]); + + return !empty($result) ? (int)$result[0]['count'] : 0; + } + + /** + * 获取今日新增群聊数 + * + * @param string $wechatId 微信ID + * @param int $companyId 公司ID + * @return int + */ + protected function getTodayNewChatrooms($wechatId, $companyId) + { + $start = strtotime(date('Y-m-d 00:00:00')); + $end = strtotime(date('Y-m-d 23:59:59')); + + return Db::table('s2_wechat_chatroom') + ->where('wechatAccountWechatId', $wechatId) + ->whereBetween('createTime', [$start, $end]) + ->where('isDeleted', 0) + ->count(); + } +} + diff --git a/Server/application/cunkebao/controller/wechat/GetWechatsOnDevicesV1Controller.php b/Server/application/cunkebao/controller/wechat/GetWechatsOnDevicesV1Controller.php index 177d67a60..0b50aa995 100644 --- a/Server/application/cunkebao/controller/wechat/GetWechatsOnDevicesV1Controller.php +++ b/Server/application/cunkebao/controller/wechat/GetWechatsOnDevicesV1Controller.php @@ -8,14 +8,25 @@ use app\common\model\DeviceUser as DeviceUserModel; use app\common\model\DeviceWechatLogin as DeviceWechatLoginModel; use app\common\model\User as UserModel; use app\common\model\WechatAccount as WechatAccountModel; -use app\common\model\WechatCustomer as WechatCustomerModel; -use app\common\model\WechatFriendShip as WechatFriendShipModel; +// 不再使用WechatFriendShipModel和WechatCustomerModel,改为直接查询s2_wechat_friend和s2_wechat_account_score表 use app\cunkebao\controller\BaseController; use library\ResponseHelper; use think\Db; /** * 微信控制器 + * + * 性能优化建议: + * 1. 为以下字段添加索引以提高查询性能: + * - device_wechat_login表: (companyId, wechatId), (deviceId) + * - wechat_account表: (wechatId) + * - wechat_customer表: (companyId, wechatId) + * - wechat_friend_ship表: (ownerWechatId), (createTime) + * - s2_wechat_message表: (wechatAccountId, wechatTime) + * + * 2. 考虑创建以下复合索引: + * - device_wechat_login表: (companyId, deviceId, wechatId) + * - wechat_friend_ship表: (ownerWechatId, createTime) */ class GetWechatsOnDevicesV1Controller extends BaseController { @@ -66,6 +77,7 @@ class GetWechatsOnDevicesV1Controller extends BaseController /** * 获取有登录设备的微信id + * 优化:使用索引字段,减少数据查询量 * * @return array */ @@ -76,12 +88,12 @@ class GetWechatsOnDevicesV1Controller extends BaseController throw new \Exception('暂无设备数据', 200); } - return DeviceWechatLoginModel::where( - [ + // 优化:直接使用DISTINCT减少数据传输量 + return DeviceWechatLoginModel::distinct(true) + ->where([ 'companyId' => $this->getUserInfo('companyId'), // 'alive' => DeviceWechatLoginModel::ALIVE_WECHAT_ACTIVE, - ] - ) + ]) ->where('deviceId', 'in', $deviceIds) ->column('wechatId'); } @@ -110,24 +122,50 @@ class GetWechatsOnDevicesV1Controller extends BaseController /** * 获取在线微信账号列表 + * 优化:减少查询字段,使用索引,优化JOIN条件 * * @param array $where * @return \think\Paginator 分页对象 */ protected function getOnlineWechatList(array $where): \think\Paginator { + // 获取微信在线状态筛选参数(1=在线,0=离线,不传=全部) + $wechatStatus = $this->request->param('wechatStatus'); + + // 优化:只查询必要字段,使用FORCE INDEX提示数据库使用索引 $query = WechatAccountModel::alias('w') ->field( [ 'w.id', 'w.nickname', 'w.avatar', 'w.wechatId', 'CASE WHEN w.alias IS NULL OR w.alias = "" THEN w.wechatId ELSE w.alias END AS wechatAccount', - 'l.deviceId','l.alive' + 'MAX(l.deviceId) as deviceId', 'MAX(l.alive) as alive' // 使用MAX确保GROUP BY时获取正确的在线状态 ] ) - ->join('device_wechat_login l', 'w.wechatId = l.wechatId AND l.companyId = '. $this->getUserInfo('companyId')) - ->order('w.id desc') - ->group('w.wechatId'); + // 优化:使用INNER JOIN代替LEFT JOIN,并添加索引提示 + ->join('device_wechat_login l', 'w.wechatId = l.wechatId AND l.companyId = '. $this->getUserInfo('companyId'), 'INNER') + // 添加s2_wechat_account表的LEFT JOIN,用于筛选微信在线状态 + ->join(['s2_wechat_account' => 'sa'], 'w.wechatId = sa.wechatId', 'LEFT') + ->group('w.wechatId') + // 优化:在线状态优先排序(alive=1的排在前面),然后按wechatId排序 + // 注意:ORDER BY使用SELECT中定义的别名alive,而不是聚合函数 + ->order('alive desc, w.wechatId desc'); + + // 根据wechatStatus参数筛选(1=在线,0=离线,不传=全部) + if ($wechatStatus !== null && $wechatStatus !== '') { + $wechatStatus = (int)$wechatStatus; + if ($wechatStatus === 1) { + // 筛选在线:wechatAlive = 1 + $query->where('sa.wechatAlive', 1); + } elseif ($wechatStatus === 0) { + // 筛选离线:wechatAlive = 0 或 NULL + $query->where(function($query) { + $query->where('sa.wechatAlive', 0) + ->whereOr('sa.wechatAlive', 'exp', 'IS NULL'); + }); + } + } + // 应用查询条件 foreach ($where as $key => $value) { if (is_numeric($key) && is_array($value) && isset($value[0]) && $value[0] === 'exp') { $query->whereExp('', $value[1]); @@ -142,7 +180,12 @@ class GetWechatsOnDevicesV1Controller extends BaseController $query->where($key, $value); } - return $query->paginate($this->request->param('limit/d', 10), false, ['page' => $this->request->param('page/d', 1)]); + // 优化:使用简单计数查询 + return $query->paginate( + $this->request->param('limit/d', 10), + false, + ['page' => $this->request->param('page/d', 1)] + ); } /** @@ -167,9 +210,15 @@ class GetWechatsOnDevicesV1Controller extends BaseController $metrics = $this->collectWechatMetrics($wechatIds); foreach ($items as $item) { + $addLimit = $metrics['addLimit'][$item->wechatId] ?? 0; + $todayAdded = $metrics['todayAdded'][$item->wechatId] ?? 0; + // 计算今日可添加数量 = 可添加额度 - 今日已添加 + $todayCanAdd = max(0, $addLimit - $todayAdded); + $sections = $item->toArray() + [ - 'times' => $metrics['addLimit'][$item->wechatId] ?? 0, - 'addedCount' => $metrics['todayAdded'][$item->wechatId] ?? 0, + 'times' => $addLimit, + 'addedCount' => $todayAdded, + 'todayCanAdd' => $todayCanAdd, // 今日可添加数量 'wechatStatus' => $metrics['wechatStatus'][$item->wechatId] ?? 0, 'totalFriend' => $metrics['totalFriend'][$item->wechatId] ?? 0, 'deviceMemo' => $metrics['deviceMemo'][$item->wechatId] ?? '', @@ -184,6 +233,8 @@ class GetWechatsOnDevicesV1Controller extends BaseController /** * 批量收集微信账号的统计信息 + * 优化:合并查询,减少数据库访问次数,使用缓存 + * * @param array $wechatIds * @return array */ @@ -203,106 +254,167 @@ class GetWechatsOnDevicesV1Controller extends BaseController } $companyId = $this->getUserInfo('companyId'); - - // 可添加好友额度 - $weightRows = WechatCustomerModel::where('companyId', $companyId) + + // 使用缓存键,避免短时间内重复查询 + $cacheKey = 'wechat_metrics_' . md5(implode(',', $wechatIds) . '_' . $companyId); + + // 尝试从缓存获取数据(缓存5分钟) + $cachedMetrics = cache($cacheKey); + if ($cachedMetrics) { + return $cachedMetrics; + } + + // 优化1:可添加好友额度 - 从s2_wechat_account_score表获取maxAddFriendPerDay + $scoreRows = Db::table('s2_wechat_account_score') ->whereIn('wechatId', $wechatIds) - ->column('weight', 'wechatId'); - foreach ($weightRows as $wechatId => $weight) { - $decoded = json_decode($weight, true); - $metrics['addLimit'][$wechatId] = $decoded['addLimit'] ?? 0; + ->column('maxAddFriendPerDay', 'wechatId'); + foreach ($scoreRows as $wechatId => $maxAddFriendPerDay) { + $metrics['addLimit'][$wechatId] = (int)($maxAddFriendPerDay ?? 0); } - // 今日新增好友 + // 优化2:今日新增好友 - 使用索引字段和预计算 $start = strtotime(date('Y-m-d 00:00:00')); $end = strtotime(date('Y-m-d 23:59:59')); - $todayRows = WechatFriendShipModel::whereIn('ownerWechatId', $wechatIds) - ->whereBetween('createTime', [$start, $end]) - ->field('ownerWechatId, COUNT(*) as total') - ->group('ownerWechatId') - ->select(); - foreach ($todayRows as $row) { - $wechatId = is_array($row) ? ($row['ownerWechatId'] ?? '') : ($row->ownerWechatId ?? ''); + + // 使用单次查询获取所有wechatIds的今日新增和总好友数 + // 根据数据库结构使用s2_wechat_friend表而不是wechat_friend_ship + $friendshipStats = Db::query(" + SELECT + ownerWechatId, + SUM(IF(createTime BETWEEN {$start} AND {$end}, 1, 0)) as today_added, + COUNT(*) as total_friend + FROM + s2_wechat_friend + WHERE + ownerWechatId IN ('" . implode("','", $wechatIds) . "') + AND isDeleted = 0 + GROUP BY + ownerWechatId + "); + + // 处理结果 + foreach ($friendshipStats as $row) { + $wechatId = $row['ownerWechatId'] ?? ''; if ($wechatId) { - $metrics['todayAdded'][$wechatId] = (int)(is_array($row) ? ($row['total'] ?? 0) : ($row->total ?? 0)); + $metrics['todayAdded'][$wechatId] = (int)($row['today_added'] ?? 0); + $metrics['totalFriend'][$wechatId] = (int)($row['total_friend'] ?? 0); } } - // 总好友 - $friendRows = WechatFriendShipModel::whereIn('ownerWechatId', $wechatIds) - ->field('ownerWechatId, COUNT(*) as total') - ->group('ownerWechatId') + // 优化3:微信在线状态 - 从s2_wechat_account表获取wechatAlive + $wechatAccountRows = Db::table('s2_wechat_account') + ->whereIn('wechatId', $wechatIds) + ->field('wechatId, wechatAlive') ->select(); - foreach ($friendRows as $row) { - $wechatId = is_array($row) ? ($row['ownerWechatId'] ?? '') : ($row->ownerWechatId ?? ''); - if ($wechatId) { - $metrics['totalFriend'][$wechatId] = (int)(is_array($row) ? ($row['total'] ?? 0) : ($row->total ?? 0)); + + foreach ($wechatAccountRows as $row) { + $wechatId = $row['wechatId'] ?? ''; + if (!empty($wechatId)) { + $metrics['wechatStatus'][$wechatId] = (int)($row['wechatAlive'] ?? 0); } } - // 设备状态与备注 + // 优化4:设备状态与备注 - 使用INNER JOIN和索引 $loginRows = Db::name('device_wechat_login') ->alias('l') - ->leftJoin('device d', 'd.id = l.deviceId') - ->field('l.wechatId,l.alive,d.memo') + ->join('device d', 'd.id = l.deviceId', 'LEFT') + ->field('l.wechatId, l.alive, d.memo') ->where('l.companyId', $companyId) ->whereIn('l.wechatId', $wechatIds) ->order('l.id', 'desc') ->select(); + + // 使用临时数组避免重复处理 + $processedWechatIds = []; foreach ($loginRows as $row) { - $wechatId = is_array($row) ? ($row['wechatId'] ?? '') : ($row->wechatId ?? ''); - if (empty($wechatId) || isset($metrics['wechatStatus'][$wechatId])) { - continue; + $wechatId = $row['wechatId'] ?? ''; + // 只处理每个wechatId的第一条记录(最新的) + if (!empty($wechatId) && !in_array($wechatId, $processedWechatIds)) { + // 如果s2_wechat_account表中没有wechatAlive,则使用device_wechat_login的alive作为备用 + if (!isset($metrics['wechatStatus'][$wechatId])) { + $metrics['wechatStatus'][$wechatId] = (int)($row['alive'] ?? 0); + } + $metrics['deviceMemo'][$wechatId] = $row['memo'] ?? ''; + $processedWechatIds[] = $wechatId; } - $metrics['wechatStatus'][$wechatId] = (int)(is_array($row) ? ($row['alive'] ?? 0) : ($row->alive ?? 0)); - $metrics['deviceMemo'][$wechatId] = is_array($row) ? ($row['memo'] ?? '') : ($row->memo ?? ''); } - // 活跃时间 - $accountMap = Db::table('s2_wechat_account') - ->whereIn('wechatId', $wechatIds) - ->column('id', 'wechatId'); - if (!empty($accountMap)) { - $accountRows = Db::table('s2_wechat_message') - ->whereIn('wechatAccountId', array_values($accountMap)) - ->field('wechatAccountId, MAX(wechatTime) as lastTime') - ->group('wechatAccountId') - ->select(); - $accountLastTime = []; - foreach ($accountRows as $row) { - $accountId = is_array($row) ? ($row['wechatAccountId'] ?? 0) : ($row->wechatAccountId ?? 0); - if ($accountId) { - $accountLastTime[$accountId] = (int)(is_array($row) ? ($row['lastTime'] ?? 0) : ($row->lastTime ?? 0)); - } - } - foreach ($accountMap as $wechatId => $accountId) { - if (isset($accountLastTime[$accountId]) && $accountLastTime[$accountId] > 0) { - $metrics['activeTime'][$wechatId] = date('Y-m-d H:i:s', $accountLastTime[$accountId]); - } + // 优化5:活跃时间 - 使用JOIN减少查询次数 + $activeTimeResults = Db::query(" + SELECT + a.wechatId, + MAX(m.wechatTime) as lastTime + FROM + s2_wechat_account a + LEFT JOIN + s2_wechat_message m ON a.id = m.wechatAccountId + WHERE + a.wechatId IN ('" . implode("','", $wechatIds) . "') + GROUP BY + a.wechatId + "); + + foreach ($activeTimeResults as $row) { + $wechatId = $row['wechatId'] ?? ''; + $lastTime = (int)($row['lastTime'] ?? 0); + if (!empty($wechatId) && $lastTime > 0) { + $metrics['activeTime'][$wechatId] = date('Y-m-d H:i:s', $lastTime); + } else { + $metrics['activeTime'][$wechatId] = '-'; } } + + // 确保所有wechatId都有wechatStatus值(默认0) + foreach ($wechatIds as $wechatId) { + if (!isset($metrics['wechatStatus'][$wechatId])) { + $metrics['wechatStatus'][$wechatId] = 0; + } + } + + // 存入缓存,有效期5分钟 + cache($cacheKey, $metrics, 300); return $metrics; } /** * 获取在线微信账号列表 + * 优化:添加缓存,优化分页逻辑 * * @return \think\response\Json */ public function index() { try { + // 获取分页参数 + $page = $this->request->param('page/d', 1); + $limit = $this->request->param('limit/d', 10); + $keyword = $this->request->param('keyword'); + $wechatStatus = $this->request->param('wechatStatus'); + + // 创建缓存键(基于用户、分页、搜索条件和在线状态筛选) + $cacheKey = 'wechat_list_' . $this->getUserInfo('id') . '_' . $page . '_' . $limit . '_' . md5($keyword ?? '') . '_' . ($wechatStatus ?? 'all'); + + // 尝试从缓存获取数据(缓存2分钟) + $cachedData = cache($cacheKey); + if ($cachedData) { + return ResponseHelper::success($cachedData); + } + + // 如果没有缓存,执行查询 $result = $this->getOnlineWechatList( $this->makeWhere() ); + + $responseData = [ + 'list' => $this->makeResultedSet($result), + 'total' => $result->total(), + ]; + + // 存入缓存,有效期2分钟 + cache($cacheKey, $responseData, 120); - return ResponseHelper::success( - [ - 'list' => $this->makeResultedSet($result), - 'total' => $result->total(), - ] - ); + return ResponseHelper::success($responseData); } catch (\Exception $e) { return ResponseHelper::error($e->getMessage(), $e->getCode()); } diff --git a/Server/sql.sql b/Server/sql.sql index b49716bc2..9e6fe53de 100644 --- a/Server/sql.sql +++ b/Server/sql.sql @@ -11,7 +11,7 @@ Target Server Version : 50736 File Encoding : 65001 - Date: 12/11/2025 11:05:39 + Date: 24/11/2025 16:50:43 */ SET NAMES utf8mb4; @@ -123,7 +123,7 @@ CREATE TABLE `ck_app_version` ( `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; +) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_attachments @@ -145,7 +145,7 @@ CREATE TABLE `ck_attachments` ( 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; +) ENGINE = InnoDB AUTO_INCREMENT = 505 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '附件表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_call_recording @@ -222,7 +222,7 @@ CREATE TABLE `ck_content_item` ( 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; +) ENGINE = InnoDB AUTO_INCREMENT = 5993 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '内容项目表-存储朋友圈采集数据' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_content_library @@ -252,7 +252,7 @@ CREATE TABLE `ck_content_library` ( `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; +) ENGINE = InnoDB AUTO_INCREMENT = 100 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '内容库表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_coze_conversation @@ -272,7 +272,7 @@ CREATE TABLE `ck_coze_conversation` ( 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; +) ENGINE = InnoDB AUTO_INCREMENT = 56 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'Coze AI 会话表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_coze_message @@ -331,7 +331,7 @@ CREATE TABLE `ck_customer_acquisition_task` ( `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; +) ENGINE = InnoDB AUTO_INCREMENT = 168 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '获客计划表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_device @@ -372,7 +372,7 @@ CREATE TABLE `ck_device_handle_log` ( `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; +) ENGINE = InnoDB AUTO_INCREMENT = 339 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_device_taskconf @@ -395,7 +395,7 @@ CREATE TABLE `ck_device_taskconf` ( `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; +) ENGINE = InnoDB AUTO_INCREMENT = 30 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '设备任务配置表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_device_user @@ -425,7 +425,7 @@ CREATE TABLE `ck_device_wechat_login` ( `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; +) ENGINE = InnoDB AUTO_INCREMENT = 312 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '设备登录微信记录表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_flow_package @@ -653,7 +653,7 @@ CREATE TABLE `ck_kf_follow_up` ( 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; +) ENGINE = InnoDB AUTO_INCREMENT = 18 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '跟进提醒' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_kf_friend_settings @@ -675,7 +675,7 @@ CREATE TABLE `ck_kf_friend_settings` ( 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; +) ENGINE = InnoDB AUTO_INCREMENT = 42 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '好友AI配置' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_kf_keywords @@ -769,7 +769,7 @@ CREATE TABLE `ck_kf_notice` ( `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; +) ENGINE = InnoDB AUTO_INCREMENT = 247 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '通知消息' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_kf_questions @@ -810,7 +810,7 @@ CREATE TABLE `ck_kf_reply` ( `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; +) ENGINE = InnoDB AUTO_INCREMENT = 130751 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '快捷回复' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_kf_reply_group @@ -977,7 +977,7 @@ CREATE TABLE `ck_task_customer` ( 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; +) ENGINE = InnoDB AUTO_INCREMENT = 28204 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_tokens_company @@ -990,7 +990,7 @@ CREATE TABLE `ck_tokens_company` ( `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; +) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '公司算力账户' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_tokens_package @@ -1033,7 +1033,7 @@ CREATE TABLE `ck_tokens_record` ( `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; +) ENGINE = InnoDB AUTO_INCREMENT = 273 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '算力明细记录' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_traffic_order @@ -1070,8 +1070,9 @@ CREATE TABLE `ck_traffic_pool` ( 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; + INDEX `idx_mobile`(`mobile`) USING BTREE, + INDEX `idx_create_time`(`createTime`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1063510 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量池' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_traffic_profile @@ -1114,8 +1115,9 @@ CREATE TABLE `ck_traffic_source` ( 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; + INDEX `idx_companyId`(`companyId`) USING BTREE, + INDEX `idx_company_status_time`(`companyId`, `status`, `updateTime`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 573831 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量来源' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_traffic_source_package @@ -1242,7 +1244,7 @@ CREATE TABLE `ck_user_portrait` ( `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; +) ENGINE = InnoDB AUTO_INCREMENT = 19014 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户画像' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_users @@ -1267,7 +1269,7 @@ CREATE TABLE `ck_users` ( `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; +) ENGINE = InnoDB AUTO_INCREMENT = 1658 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_vendor_order @@ -1360,7 +1362,7 @@ CREATE TABLE `ck_wechat_account` ( `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; +) ENGINE = InnoDB AUTO_INCREMENT = 3614968 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信账号表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_wechat_customer @@ -1378,7 +1380,7 @@ CREATE TABLE `ck_wechat_customer` ( `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; +) ENGINE = InnoDB AUTO_INCREMENT = 154 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信客服信息' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_wechat_friendship @@ -1434,7 +1436,7 @@ CREATE TABLE `ck_wechat_group_member` ( `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; +) ENGINE = InnoDB AUTO_INCREMENT = 554147 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信群成员' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_wechat_restricts @@ -1451,7 +1453,7 @@ CREATE TABLE `ck_wechat_restricts` ( `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; +) ENGINE = InnoDB AUTO_INCREMENT = 1319 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信风险受限记录' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_wechat_tag @@ -1489,7 +1491,7 @@ CREATE TABLE `ck_workbench` ( 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; +) ENGINE = InnoDB AUTO_INCREMENT = 282 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '工作台主表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_workbench_auto_like @@ -1534,7 +1536,7 @@ CREATE TABLE `ck_workbench_auto_like_item` ( 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; +) ENGINE = InnoDB AUTO_INCREMENT = 4653 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '工作台-自动点赞记录' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_workbench_group_create @@ -1604,9 +1606,14 @@ CREATE TABLE `ck_workbench_group_push` ( `promotionSiteId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '京东广告位', `trafficPools` json NULL COMMENT '流量池', `devices` json NULL, + `groupPushSubType` tinyint(2) NULL DEFAULT 1 COMMENT '群推送子类型 1=群群发,2=群公告', + `announcementContent` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `enableAiRewrite` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `aiRewritePrompt` tinyint(2) NULL DEFAULT 0, 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; + INDEX `idx_workbench_id`(`workbenchId`) USING BTREE, + INDEX `idx_status_targetType`(`status`, `targetType`, `workbenchId`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 34 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群消息推送扩展表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_workbench_group_push_item @@ -1622,7 +1629,9 @@ CREATE TABLE `ck_workbench_group_push_item` ( `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 + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_workbench_target_time`(`workbenchId`, `targetType`, `createTime`) USING BTREE, + INDEX `idx_workbench_target_friend`(`workbenchId`, `targetType`, `friendId`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 302 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- @@ -1677,7 +1686,7 @@ CREATE TABLE `ck_workbench_moments_sync` ( `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; +) ENGINE = InnoDB AUTO_INCREMENT = 51 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '朋友圈同步配置' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_workbench_moments_sync_item @@ -1691,8 +1700,10 @@ CREATE TABLE `ck_workbench_moments_sync_item` ( `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; + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_workbench_time`(`workbenchId`, `createTime`) USING BTREE, + INDEX `idx_workbench_content`(`workbenchId`, `contentId`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1785 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '朋友圈同步配置' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_workbench_traffic_config @@ -1714,7 +1725,7 @@ CREATE TABLE `ck_workbench_traffic_config` ( `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; +) ENGINE = InnoDB AUTO_INCREMENT = 32 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量分发计划扩展表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for ck_workbench_traffic_config_item @@ -1736,7 +1747,7 @@ CREATE TABLE `ck_workbench_traffic_config_item` ( 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; +) ENGINE = InnoDB AUTO_INCREMENT = 54241 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量分发计划扩展表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for s2_allot_rule @@ -1869,6 +1880,7 @@ CREATE TABLE `s2_device` ( `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 '是否在线', + `aliveTime` int(11) NULL DEFAULT 0, `lastAliveTime` int(11) NULL DEFAULT NULL COMMENT '最后在线时间', `tenantId` int(11) NULL DEFAULT NULL COMMENT '租户ID', `groupId` int(11) NULL DEFAULT NULL COMMENT '分组ID', @@ -1938,6 +1950,7 @@ CREATE TABLE `s2_friend_task` ( `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 '更新时间戳', + `is_counted` tinyint(1) NULL DEFAULT 0 COMMENT '是否已统计(0=未统计,1=已统计)', UNIQUE INDEX `uk_task_id`(`id`) USING BTREE, INDEX `idx_tenant_id`(`tenantId`) USING BTREE, INDEX `idx_operator_account_id`(`operatorAccountId`) USING BTREE, @@ -2064,6 +2077,7 @@ CREATE TABLE `s2_wechat_account` ( `keFuAlive` tinyint(1) NULL DEFAULT 0 COMMENT '客服是否在线', `deviceAlive` tinyint(1) NULL DEFAULT 0 COMMENT '设备是否在线', `wechatAlive` tinyint(1) NULL DEFAULT 0 COMMENT '微信是否在线', + `wechatAliveTime` int(11) 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天消息数', @@ -2092,9 +2106,82 @@ CREATE TABLE `s2_wechat_account` ( `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 + `healthScore` int(11) NULL DEFAULT 60 COMMENT '健康分总分(基础分+动态分)', + `baseScore` int(11) NULL DEFAULT 60 COMMENT '基础分(60-100分)', + `dynamicScore` int(11) NULL DEFAULT 0 COMMENT '动态分(扣分和加分)', + `isModifiedAlias` tinyint(1) NULL DEFAULT 0 COMMENT '是否已修改微信号(0=未修改,1=已修改)', + `lastFrequentTime` int(11) NULL DEFAULT NULL COMMENT '最后频繁时间(时间戳)', + `frequentCount` int(11) NULL DEFAULT 0 COMMENT '频繁次数(用于判断首次/再次频繁)', + `lastNoFrequentTime` int(11) NULL DEFAULT NULL COMMENT '最后不频繁时间(时间戳)', + `consecutiveNoFrequentDays` int(11) NULL DEFAULT 0 COMMENT '连续不频繁天数(用于加分)', + `scoreUpdateTime` int(11) NULL DEFAULT NULL COMMENT '评分更新时间', + INDEX `idx_wechat_id`(`wechatId`) USING BTREE, + INDEX `idx_health_score`(`healthScore`) USING BTREE, + INDEX `idx_is_modified_alias`(`isModifiedAlias`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信账号表' ROW_FORMAT = Dynamic; +-- ---------------------------- +-- Table structure for s2_wechat_account_score +-- ---------------------------- +DROP TABLE IF EXISTS `s2_wechat_account_score`; +CREATE TABLE `s2_wechat_account_score` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID', + `accountId` int(11) NOT NULL COMMENT '微信账号ID(s2_wechat_account.id)', + `wechatId` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '微信ID', + `baseScore` int(11) NOT NULL DEFAULT 0 COMMENT '基础分(60-100分)', + `baseScoreCalculated` tinyint(1) NOT NULL DEFAULT 0 COMMENT '基础分是否已计算(0=未计算,1=已计算)', + `baseScoreCalcTime` int(11) NULL DEFAULT NULL COMMENT '基础分计算时间', + `baseInfoScore` int(11) NOT NULL DEFAULT 0 COMMENT '基础信息分(0-10分)', + `isModifiedAlias` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否已修改微信号(0=未修改,1=已修改)', + `friendCountScore` int(11) NOT NULL DEFAULT 0 COMMENT '好友数量分(0-30分)', + `friendCount` int(11) NOT NULL DEFAULT 0 COMMENT '好友数量(评分时的快照)', + `friendCountSource` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '好友数量来源(manual=手动,sync=同步)', + `dynamicScore` int(11) NOT NULL DEFAULT 0 COMMENT '动态分(扣分和加分)', + `lastFrequentTime` int(11) NULL DEFAULT NULL COMMENT '最后频繁时间(时间戳)', + `frequentCount` int(11) NOT NULL DEFAULT 0 COMMENT '频繁次数(用于判断首次/再次频繁)', + `frequentPenalty` int(11) NOT NULL DEFAULT 0 COMMENT '频繁扣分(累计)', + `lastNoFrequentTime` int(11) NULL DEFAULT NULL COMMENT '最后不频繁时间(时间戳)', + `consecutiveNoFrequentDays` int(11) NOT NULL DEFAULT 0 COMMENT '连续不频繁天数', + `noFrequentBonus` int(11) NOT NULL DEFAULT 0 COMMENT '不频繁加分(累计)', + `isBanned` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否封号(0=否,1=是)', + `banPenalty` int(11) NOT NULL DEFAULT 0 COMMENT '封号扣分', + `healthScore` int(11) NOT NULL DEFAULT 0 COMMENT '健康分总分(基础分+动态分)', + `maxAddFriendPerDay` int(11) NOT NULL DEFAULT 0 COMMENT '每日最大加人次数', + `createTime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间', + `updateTime` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间', + `lastBanTime` int(11) NULL DEFAULT NULL COMMENT '最后一次封号时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_account_id`(`accountId`) USING BTREE, + INDEX `idx_wechat_id`(`wechatId`) USING BTREE, + INDEX `idx_health_score`(`healthScore`) USING BTREE, + INDEX `idx_base_score_calculated`(`baseScoreCalculated`) USING BTREE, + INDEX `idx_update_time`(`updateTime`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 363 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信账号评分记录表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for s2_wechat_account_score_log +-- ---------------------------- +DROP TABLE IF EXISTS `s2_wechat_account_score_log`; +CREATE TABLE `s2_wechat_account_score_log` ( + `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID', + `accountId` int(11) NOT NULL COMMENT '微信账号ID', + `wechatId` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '微信ID', + `field` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '变动字段(如frequentPenalty)', + `changeValue` int(11) NOT NULL DEFAULT 0 COMMENT '变动值(正加负减)', + `valueBefore` int(11) NULL DEFAULT NULL COMMENT '变更前的字段值', + `valueAfter` int(11) NULL DEFAULT NULL COMMENT '变更后的字段值', + `category` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '分类:penalty/bonus/dynamic_total/health_total等', + `source` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '触发来源 friend_task/wechat_message/system', + `sourceId` bigint(20) NULL DEFAULT NULL COMMENT '关联记录ID(如任务/消息ID)', + `extra` json NULL COMMENT '附加信息(JSON)', + `totalScoreBefore` int(11) NULL DEFAULT NULL COMMENT '变更前健康总分', + `totalScoreAfter` int(11) NULL DEFAULT NULL COMMENT '变更后健康总分', + `createTime` int(11) NOT NULL COMMENT '记录时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_account_field`(`accountId`, `field`) USING BTREE, + 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 -- ---------------------------- @@ -2150,7 +2237,7 @@ CREATE TABLE `s2_wechat_chatroom_member` ( 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; +) ENGINE = InnoDB AUTO_INCREMENT = 495174 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信群成员表' ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for s2_wechat_friend @@ -2198,6 +2285,9 @@ CREATE TABLE `s2_wechat_friend` ( `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', + `realName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '姓名', + `company` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '公司', + `position` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '职位', 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, @@ -2257,6 +2347,7 @@ CREATE TABLE `s2_wechat_message` ( `msgId` bigint(20) NULL DEFAULT NULL COMMENT '消息ID', `recallId` tinyint(1) NULL DEFAULT 0 COMMENT '撤回ID', `isRead` tinyint(1) NULL DEFAULT 0 COMMENT '是否读取', + `is_counted` tinyint(1) NULL DEFAULT 0 COMMENT '是否已统计(0=未统计,1=已统计)', PRIMARY KEY (`id`) USING BTREE, INDEX `idx_wechatChatroomId`(`wechatChatroomId`) USING BTREE, INDEX `idx_wechatAccountId`(`wechatAccountId`) USING BTREE, @@ -2296,6 +2387,6 @@ CREATE TABLE `s2_wechat_moments` ( 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; +) ENGINE = InnoDB AUTO_INCREMENT = 40130 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信朋友圈数据表' ROW_FORMAT = Dynamic; SET FOREIGN_KEY_CHECKS = 1; From 65da14032b84ff5144ffa93c5f2dc925caf3bbba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Thu, 27 Nov 2025 10:45:13 +0800 Subject: [PATCH 28/49] =?UTF-8?q?1=E3=80=81=E5=8F=91=E8=B5=B7=E7=BE=A4?= =?UTF-8?q?=E8=81=8A=E5=85=A8=E9=80=89=E2=9C=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SidebarMenu/PopChatRoom/index.module.scss | 45 ++++++ .../SidebarMenu/PopChatRoom/index.tsx | 136 +++++++++++++++++- 2 files changed, 175 insertions(+), 6 deletions(-) diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/PopChatRoom/index.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/PopChatRoom/index.module.scss index 20d9a3d38..0c34dac0e 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/PopChatRoom/index.module.scss +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/PopChatRoom/index.module.scss @@ -48,6 +48,43 @@ font-weight: 500; font-size: 14px; color: #262626; + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; +} + +.headerActions { + display: flex; + gap: 8px; + + :global(.ant-btn-sm) { + padding: 0 8px; + } +} + +.actionButton { + color: #fff !important; + border: none !important; + transition: background-color 0.2s ease; + + &:hover, + &:focus { + color: #fff; + opacity: 0.9; + } +} + +.currentPageButton { + background-color: #1677ff !important; +} + +.allSelectButton { + background-color: #13c2c2 !important; +} + +.deselectState { + background-color: #ff7875 !important; } .listContent { @@ -57,6 +94,14 @@ min-height: 0; } +.selectedList { + .listContent { + flex: unset; + height: 600px; + overflow-y: auto; + } +} + .contactItem, .selectedItem { display: flex; diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/PopChatRoom/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/PopChatRoom/index.tsx index 68a99575c..7505b815b 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/PopChatRoom/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/PopChatRoom/index.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useMemo } from "react"; +import React, { useState, useEffect, useMemo, useCallback } from "react"; import { Modal, Input, @@ -32,6 +32,7 @@ const PopChatRoom: React.FC = ({ visible, onCancel }) => { const [showNameModal, setShowNameModal] = useState(false); const [chatroomName, setChatroomName] = useState(""); const pageSize = 10; + const MAX_SELECT_COUNT = 50; // 最多选择联系人数量 const { sendCommand } = useWebSocketStore(); const currentUserId = useUserStore(state => state.user?.id) || 0; const currentCustomer = useCustomerStore(state => state.currentCustomer); @@ -91,12 +92,115 @@ const PopChatRoom: React.FC = ({ visible, onCancel }) => { return filteredContacts.slice(start, end); }, [filteredContacts, page]); + const isContactSelected = useCallback( + (contactId: number) => { + return selectedContacts.some(contact => contact.id === contactId); + }, + [selectedContacts], + ); + + const addContactsToSelection = (contacts: Contact[]) => { + setSelectedContacts(prev => { + const existingIds = new Set(prev.map(contact => contact.id)); + const additions = contacts.filter( + contact => !existingIds.has(contact.id), + ); + if (additions.length === 0) return prev; + return [...prev, ...additions]; + }); + }; + + const removeContactsFromSelection = (contacts: Contact[]) => { + if (contacts.length === 0) return; + const removalIds = new Set(contacts.map(contact => contact.id)); + setSelectedContacts(prev => + prev.filter(contact => !removalIds.has(contact.id)), + ); + }; + + const isCurrentPageFullySelected = useMemo(() => { + return ( + paginatedContacts.length > 0 && + paginatedContacts.every(contact => isContactSelected(contact.id)) + ); + }, [isContactSelected, paginatedContacts]); + + const isAllContactsFullySelected = useMemo(() => { + return ( + filteredContacts.length > 0 && + filteredContacts.every(contact => isContactSelected(contact.id)) + ); + }, [filteredContacts, isContactSelected]); + + const handleToggleCurrentPageSelection = () => { + if (isCurrentPageFullySelected) { + removeContactsFromSelection(paginatedContacts); + } else { + const currentSelectedCount = selectedContacts.length; + const remainingSlots = MAX_SELECT_COUNT - currentSelectedCount; + + if (remainingSlots <= 0) { + message.warning(`最多只能选择${MAX_SELECT_COUNT}个联系人`); + return; + } + + // 获取当前页未选中的联系人 + const unselectedContacts = paginatedContacts.filter( + contact => !isContactSelected(contact.id), + ); + + if (unselectedContacts.length > remainingSlots) { + // 只选择前 remainingSlots 个未选中的联系人 + const contactsToAdd = unselectedContacts.slice(0, remainingSlots); + addContactsToSelection(contactsToAdd); + message.warning( + `最多只能选择${MAX_SELECT_COUNT}个联系人,已选择前${remainingSlots}个`, + ); + } else { + addContactsToSelection(unselectedContacts); + } + } + }; + + const handleToggleAllContactsSelection = () => { + if (isAllContactsFullySelected) { + removeContactsFromSelection(filteredContacts); + } else { + const currentSelectedCount = selectedContacts.length; + const remainingSlots = MAX_SELECT_COUNT - currentSelectedCount; + + if (remainingSlots <= 0) { + message.warning(`最多只能选择${MAX_SELECT_COUNT}个联系人`); + return; + } + + if (filteredContacts.length > remainingSlots) { + // 只选择前 remainingSlots 个未选中的联系人 + const unselectedContacts = filteredContacts.filter( + contact => !isContactSelected(contact.id), + ); + const contactsToAdd = unselectedContacts.slice(0, remainingSlots); + addContactsToSelection(contactsToAdd); + message.warning( + `最多只能选择${MAX_SELECT_COUNT}个联系人,已选择前${remainingSlots}个`, + ); + } else { + addContactsToSelection(filteredContacts); + } + } + }; + // 处理联系人选择 const handleContactSelect = (contact: Contact) => { setSelectedContacts(prev => { if (isContactSelected(contact.id)) { return prev.filter(item => item.id !== contact.id); } + // 检查是否超过50个限制 + if (prev.length >= MAX_SELECT_COUNT) { + message.warning(`最多只能选择${MAX_SELECT_COUNT}个联系人`); + return prev; + } return [...prev, contact]; }); }; @@ -108,11 +212,6 @@ const PopChatRoom: React.FC = ({ visible, onCancel }) => { ); }; - // 检查联系人是否已选择 - const isContactSelected = (contactId: number) => { - return selectedContacts.some(contact => contact.id === contactId); - }; - // 处理取消 const handleCancel = () => { setSearchValue(""); @@ -219,6 +318,31 @@ const PopChatRoom: React.FC = ({ visible, onCancel }) => {
    联系人 ({filteredContacts.length}) +
    + + + (全选最多50个) +
    {loading ? ( From a5cc4e5e18cbfa09c2a775dfebf1f1cc7acaf5e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Thu, 27 Nov 2025 15:07:34 +0800 Subject: [PATCH 29/49] =?UTF-8?q?=E4=BC=98=E5=8C=96AI=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91=EF=BC=8C=E5=8C=BA=E5=88=86?= =?UTF-8?q?AI=E8=BE=85=E5=8A=A9=E6=A8=A1=E5=BC=8F=E4=B8=8EAI=E6=8E=A5?= =?UTF-8?q?=E7=AE=A1=E6=A8=A1=E5=BC=8F=EF=BC=8C=E7=A1=AE=E4=BF=9D=E5=9C=A8?= =?UTF-8?q?=E6=8E=A5=E7=AE=A1=E6=A8=A1=E5=BC=8F=E4=B8=8B=E7=9B=B4=E6=8E=A5?= =?UTF-8?q?=E5=8F=91=E9=80=81=E6=B6=88=E6=81=AF=E8=80=8C=E4=B8=8D=E7=BB=8F?= =?UTF-8?q?=E8=BF=87MessageEnter=E7=BB=84=E4=BB=B6=E3=80=82=E5=90=8C?= =?UTF-8?q?=E6=97=B6=EF=BC=8C=E8=B0=83=E6=95=B4=E7=9B=B8=E5=85=B3=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E7=9A=84=E7=8A=B6=E6=80=81=E7=AE=A1=E7=90=86=EF=BC=8C?= =?UTF-8?q?=E6=8F=90=E5=8D=87=E4=BB=A3=E7=A0=81=E5=8F=AF=E8=AF=BB=E6=80=A7?= =?UTF-8?q?=E5=92=8C=E7=BB=B4=E6=8A=A4=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/MessageEnter/index.tsx | 14 +- Touchkebao/src/store/module/weChat/weChat.ts | 163 ++++++++++++++++-- .../src/store/module/websocket/msgManage.ts | 38 ++-- .../src/store/module/websocket/websocket.ts | 3 +- 4 files changed, 183 insertions(+), 35 deletions(-) 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 9135624fd..e057a7281 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 @@ -176,19 +176,21 @@ const MessageEnter: React.FC = ({ contract }) => { updateQuoteMessageContent(""); }; - // AI 消息处理 + // AI 消息处理 - 只处理AI辅助模式 + // AI接管模式已经在weChat.ts中直接发送,不经过此组件 + // 快捷语填充:当 quoteMessageContent 更新时,填充到输入框 useEffect(() => { if (quoteMessageContent) { if (isAiAssist) { + // AI辅助模式:直接填充输入框 + setInputValue(quoteMessageContent); + } else { + // 快捷语模式:直接填充输入框(用户主动点击快捷语,应该替换当前内容) setInputValue(quoteMessageContent); - } - - if (isAiTakeover) { - handleSend(quoteMessageContent); } } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [quoteMessageContent, aiQuoteMessageContent, isAiAssist, isAiTakeover]); + }, [quoteMessageContent, aiQuoteMessageContent, isAiAssist]); const handleKeyPress = (e: React.KeyboardEvent) => { if (e.key === "Enter" && !e.shiftKey && !e.ctrlKey) { diff --git a/Touchkebao/src/store/module/weChat/weChat.ts b/Touchkebao/src/store/module/weChat/weChat.ts index 89a001a95..e06e27cd5 100644 --- a/Touchkebao/src/store/module/weChat/weChat.ts +++ b/Touchkebao/src/store/module/weChat/weChat.ts @@ -18,6 +18,7 @@ import { getFriendInjectConfig, } from "@/pages/pc/ckbox/api"; import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; +import { useWebSocketStore } from "@/store/module/websocket/websocket"; /** * AI请求防抖管理 @@ -392,13 +393,73 @@ export const manualTriggerAi = async () => { return false; } - // 更新AI回复内容 - state.updateQuoteMessageContent(messageContent?.content || ""); - state.updateIsLoadingAiChat(false); - console.log( - `✅ 手动AI回复成功 [${generationId}]:`, - messageContent?.content, - ); + // 获取当前接待类型 + const aiType = (currentContract as any)?.aiType || 0; // 0=人工, 1=AI辅助, 2=AI接管 + const aiResponseContent = messageContent?.content || ""; + const isWechatGroup = !!(currentContract as any)?.chatroomId; + + // 根据接待类型处理AI回复 + if (aiType === 2 && aiResponseContent) { + // AI接管模式:直接发送消息,不经过MessageEnter组件 + const messageId = +Date.now(); + + // 构造本地消息对象 + const localMessage: ChatRecord = { + id: messageId, + wechatAccountId: currentContract.wechatAccountId, + wechatFriendId: isWechatGroup ? 0 : currentContract.id, + wechatChatroomId: isWechatGroup ? currentContract.id : 0, + tenantId: 0, + accountId: 0, + synergyAccountId: 0, + content: aiResponseContent, + msgType: 1, + 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, + }; + + // 添加到消息列表 + state.addMessage(localMessage); + + // 直接发送消息 + const { sendCommand } = useWebSocketStore.getState(); + sendCommand("CmdSendMessage", { + wechatAccountId: currentContract.wechatAccountId, + wechatChatroomId: isWechatGroup ? currentContract.id : 0, + wechatFriendId: isWechatGroup ? 0 : currentContract.id, + msgSubType: 0, + msgType: 1, + content: aiResponseContent, + seq: messageId, + }); + + state.updateIsLoadingAiChat(false); + console.log( + `✅ 手动AI接管模式:直接发送消息 [${generationId}]:`, + aiResponseContent, + ); + } else if (aiType === 1) { + // AI辅助模式:设置quoteMessageContent,让MessageEnter组件填充输入框 + state.updateQuoteMessageContent(aiResponseContent); + state.updateIsLoadingAiChat(false); + console.log( + `✅ 手动AI辅助模式:填充输入框 [${generationId}]:`, + aiResponseContent, + ); + } else { + // 其他情况 + state.updateIsLoadingAiChat(false); + } // 清除当前生成ID currentAiGenerationId = null; @@ -837,15 +898,85 @@ export const useWeChatStore = create()( return; } - // 附加生成ID到回复内容 - set(() => ({ - quoteMessageContent: messageContent?.content || "", - isLoadingAiChat: false, - })); - console.log( - `✅ AI回复成功 [${generationId}]:`, - messageContent?.content, - ); + // 获取当前接待类型 + const aiType = (currentContract as any)?.aiType || 0; // 0=人工, 1=AI辅助, 2=AI接管 + const aiResponseContent = messageContent?.content || ""; + + // 根据接待类型处理AI回复 + if (aiType === 2 && aiResponseContent) { + // AI接管模式:直接发送消息,不经过MessageEnter组件 + const messageId = +Date.now(); + + // 构造本地消息对象 + const localMessage: ChatRecord = { + id: messageId, + wechatAccountId: currentContract.wechatAccountId, + wechatFriendId: isWechatGroup ? 0 : currentContract.id, + wechatChatroomId: isWechatGroup + ? currentContract.id + : 0, + tenantId: 0, + accountId: 0, + synergyAccountId: 0, + content: aiResponseContent, + msgType: 1, + 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, + }; + + // 添加到消息列表 + set(state => ({ + currentMessages: [ + ...state.currentMessages, + localMessage, + ], + isLoadingAiChat: false, + })); + + // 直接发送消息 + const { sendCommand } = useWebSocketStore.getState(); + sendCommand("CmdSendMessage", { + wechatAccountId: currentContract.wechatAccountId, + wechatChatroomId: isWechatGroup + ? currentContract.id + : 0, + wechatFriendId: isWechatGroup ? 0 : currentContract.id, + msgSubType: 0, + msgType: 1, + content: aiResponseContent, + seq: messageId, + }); + + console.log( + `✅ AI接管模式:直接发送消息 [${generationId}]:`, + aiResponseContent, + ); + } else if (aiType === 1) { + // AI辅助模式:设置quoteMessageContent,让MessageEnter组件填充输入框 + set(() => ({ + quoteMessageContent: aiResponseContent, + isLoadingAiChat: false, + })); + console.log( + `✅ AI辅助模式:填充输入框 [${generationId}]:`, + aiResponseContent, + ); + } else { + // 其他情况 + set(() => ({ + isLoadingAiChat: false, + })); + } // 清除当前生成ID currentAiGenerationId = null; diff --git a/Touchkebao/src/store/module/websocket/msgManage.ts b/Touchkebao/src/store/module/websocket/msgManage.ts index 2fcaceb33..a2f31d98c 100644 --- a/Touchkebao/src/store/module/websocket/msgManage.ts +++ b/Touchkebao/src/store/module/websocket/msgManage.ts @@ -8,17 +8,23 @@ import { Modal } from "antd"; import { useCustomerStore, updateCustomerList } from "../weChat/customer"; // 消息处理器类型定义 type MessageHandler = (message: WebSocketMessage) => void; -const addMessage = useWeChatStore.getState().addMessage; -const recallMessage = useWeChatStore.getState().recallMessage; -const receivedMsg = useWeChatStore.getState().receivedMsg; -const findMessageBySeq = useWeChatStore.getState().findMessageBySeq; -const findMessageById = useWeChatStore.getState().findMessageById; -const updateMessage = useWeChatStore.getState().updateMessage; -const updateMomentCommonLoading = - useWeChatStore.getState().updateMomentCommonLoading; -const addMomentCommon = useWeChatStore.getState().addMomentCommon; -const setFileDownloadUrl = useWeChatStore.getState().setFileDownloadUrl; -const setFileDownloading = useWeChatStore.getState().setFileDownloading; + +// 延迟获取 store 方法,避免循环依赖问题 +const getWeChatStoreMethods = () => { + const state = useWeChatStore.getState(); + return { + addMessage: state.addMessage, + recallMessage: state.recallMessage, + receivedMsg: state.receivedMsg, + findMessageBySeq: state.findMessageBySeq, + findMessageById: state.findMessageById, + updateMessage: state.updateMessage, + updateMomentCommonLoading: state.updateMomentCommonLoading, + addMomentCommon: state.addMomentCommon, + setFileDownloadUrl: state.setFileDownloadUrl, + setFileDownloading: state.setFileDownloading, + }; +}; // 消息处理器映射 const messageHandlers: Record = { // 微信账号存活状态响应 @@ -46,6 +52,7 @@ const messageHandlers: Record = { }, // 发送消息响应 CmdSendMessageResp: message => { + const { findMessageBySeq, updateMessage } = getWeChatStoreMethods(); const msg = findMessageBySeq(message.seq); if (msg) { updateMessage(message.seq, { @@ -55,6 +62,7 @@ const messageHandlers: Record = { } }, CmdSendMessageResult: message => { + const { updateMessage } = getWeChatStoreMethods(); updateMessage(message.friendMessageId || message.chatroomMessageId, { sendStatus: 0, }); @@ -68,6 +76,7 @@ const messageHandlers: Record = { //收到消息 CmdNewMessage: (message: Messages) => { // 处理消息本身 + const { receivedMsg } = getWeChatStoreMethods(); receivedMsg(message.friendMessage || message.chatroomMessage); // 触发会话列表更新事件 @@ -107,6 +116,7 @@ const messageHandlers: Record = { // setVideoUrl(message.friendMessageId, message.url); }, CmdDownloadFileResult: message => { + const { setFileDownloadUrl, setFileDownloading } = getWeChatStoreMethods(); const messageId = message.friendMessageId || message.chatroomMessageId; if (!messageId) { @@ -124,6 +134,8 @@ const messageHandlers: Record = { }, CmdFetchMomentResult: message => { + const { addMomentCommon, updateMomentCommonLoading } = + getWeChatStoreMethods(); addMomentCommon(message.result); updateMomentCommonLoading(false); }, @@ -162,16 +174,18 @@ const messageHandlers: Record = { //撤回消息 CmdMessageRecalled: message => { + const { recallMessage } = getWeChatStoreMethods(); const MessageId = message.friendMessageId || message.chatroomMessageId; recallMessage(MessageId); }, CmdVoiceToTextResult: message => { + const { findMessageById, updateMessage } = getWeChatStoreMethods(); const msg = findMessageById( message.friendMessageId || message.chatroomMessageId, ); - const content = JSON.parse(msg.content); if (msg) { + const content = JSON.parse(msg.content); updateMessage(msg.id, { content: JSON.stringify({ ...content, diff --git a/Touchkebao/src/store/module/websocket/websocket.ts b/Touchkebao/src/store/module/websocket/websocket.ts index 57ed7f15e..59a2fcf35 100644 --- a/Touchkebao/src/store/module/websocket/websocket.ts +++ b/Touchkebao/src/store/module/websocket/websocket.ts @@ -2,7 +2,6 @@ import { createPersistStore } from "@/store/createPersistStore"; import { useUserStore } from "../user"; import { useCkChatStore } from "@/store/module/ckchat/ckchat"; import { useCustomerStore } from "@/store/module/weChat/customer"; -const { getAccountId } = useCkChatStore.getState(); import { msgManageCore } from "./msgManage"; // WebSocket消息类型 export interface WebSocketMessage { @@ -141,6 +140,7 @@ export const useWebSocketStore = createPersistStore( } // 构建WebSocket URL + const { getAccountId } = useCkChatStore.getState(); const params = new URLSearchParams({ client: fullConfig.client.toString(), accountId: getAccountId().toString(), @@ -330,6 +330,7 @@ export const useWebSocketStore = createPersistStore( // console.log("WebSocket连接成功"); const { token2 } = useUserStore.getState(); + const { getAccountId } = useCkChatStore.getState(); // 发送登录命令 if (currentState.config) { currentState.sendCommand("CmdSignIn", { From ed4d305165f036fa48f994fe14540bf1ebdb8589 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Thu, 27 Nov 2025 16:06:40 +0800 Subject: [PATCH 30/49] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=A4=84=E7=90=86=E5=8F=82=E6=95=B0=E7=B1=BB=E5=9E=8B=EF=BC=8C?= =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=B6=88=E6=81=AF=E5=A4=84=E7=90=86=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E4=BB=A5=E6=94=AF=E6=8C=81=E5=BC=82=E6=AD=A5=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E5=90=8C=E6=AD=A5=EF=BC=8C=E7=A1=AE=E4=BF=9D?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E5=8F=91=E9=80=81=E6=97=B6=E6=AD=A3=E7=A1=AE?= =?UTF-8?q?=E4=BC=A0=E9=80=92=E7=BE=A4=E8=81=8A=E5=92=8C=E4=B8=AA=E4=BA=BA?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Touchkebao/src/api/ai.ts | 6 ++--- .../src/store/module/websocket/msgManage.ts | 24 +++++++++++++++++-- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/Touchkebao/src/api/ai.ts b/Touchkebao/src/api/ai.ts index 6dca87512..196a6cf47 100644 --- a/Touchkebao/src/api/ai.ts +++ b/Touchkebao/src/api/ai.ts @@ -18,11 +18,11 @@ export interface DataProcessingParams { /** * 群消息 */ - chatroomMessage?: string[]; + chatroomMessage?: any[]; /** * 个人信息 */ - friendMessage?: string[]; + friendMessage?: any[]; /** * 类型固定值 */ @@ -30,7 +30,7 @@ export interface DataProcessingParams { /** * 公共 */ - wechatAccountId?: string; + wechatAccountId?: number; [property: string]: any; } diff --git a/Touchkebao/src/store/module/websocket/msgManage.ts b/Touchkebao/src/store/module/websocket/msgManage.ts index a2f31d98c..f103f9e14 100644 --- a/Touchkebao/src/store/module/websocket/msgManage.ts +++ b/Touchkebao/src/store/module/websocket/msgManage.ts @@ -6,6 +6,7 @@ import { Messages } from "./msg.data"; import { db } from "@/utils/db"; import { Modal } from "antd"; import { useCustomerStore, updateCustomerList } from "../weChat/customer"; +import { dataProcessing } from "@/api/ai"; // 消息处理器类型定义 type MessageHandler = (message: WebSocketMessage) => void; @@ -51,8 +52,9 @@ const messageHandlers: Record = { updateCustomerList(updatedCustomerList); }, // 发送消息响应 - CmdSendMessageResp: message => { + CmdSendMessageResp: (message: Messages) => { const { findMessageBySeq, updateMessage } = getWeChatStoreMethods(); + const msg = findMessageBySeq(message.seq); if (msg) { updateMessage(message.seq, { @@ -60,6 +62,8 @@ const messageHandlers: Record = { id: message.friendMessage?.id || message.chatroomMessage?.id, }); } + //异步传新消息给数据库 + goAsyncServiceData(message); }, CmdSendMessageResult: message => { const { updateMessage } = getWeChatStoreMethods(); @@ -78,7 +82,8 @@ const messageHandlers: Record = { // 处理消息本身 const { receivedMsg } = getWeChatStoreMethods(); receivedMsg(message.friendMessage || message.chatroomMessage); - + //异步传新消息给数据库 + goAsyncServiceData(message); // 触发会话列表更新事件 const msgData = message.friendMessage || message.chatroomMessage; if (msgData) { @@ -197,6 +202,21 @@ const messageHandlers: Record = { // 可以继续添加更多处理器... }; +//消息异步同步 +const goAsyncServiceData = (message: Messages) => { + const chatroomMessages = message.chatroomMessage + ? [message.chatroomMessage] + : null; + const friendMessages = message.friendMessage ? [message.friendMessage] : null; + dataProcessing({ + chatroomMessage: chatroomMessages, + friendMessage: friendMessages, + type: "CmdNewMessage", + wechatAccountId: + message.friendMessage?.wechatAccountId || + message.chatroomMessage?.wechatAccountId, + }); +}; // 默认处理器 const defaultHandler: MessageHandler = message => { From 10f876f2f67119ddfa41478aea5aef0014d88286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Thu, 27 Nov 2025 16:38:28 +0800 Subject: [PATCH 31/49] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E7=BE=A4=E8=81=8A=E6=88=90=E5=91=98=E5=88=97=E8=A1=A8API?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=E6=B6=88=E6=81=AF=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E4=BB=A5=E6=94=AF=E6=8C=81=E7=BE=A4=E8=81=8A?= =?UTF-8?q?=E6=88=90=E5=91=98=E4=BF=A1=E6=81=AF=E5=B1=95=E7=A4=BA=EF=BC=8C?= =?UTF-8?q?=E8=B0=83=E6=95=B4=E7=8A=B6=E6=80=81=E7=AE=A1=E7=90=86=E4=BB=A5?= =?UTF-8?q?=E9=98=B2=E6=AD=A2=E8=B7=A8=E8=81=94=E7=B3=BB=E4=BA=BA=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E4=B8=B2=E8=81=94=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/MessageRecord/api.ts | 15 ++++- .../components/MessageRecord/index.tsx | 65 +++++++++++++++---- .../src/store/module/weChat/weChat.data.ts | 2 + Touchkebao/src/store/module/weChat/weChat.ts | 59 ++++++++++++----- .../src/store/module/websocket/msgManage.ts | 2 - 5 files changed, 111 insertions(+), 32 deletions(-) diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/api.ts b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/api.ts index cb50d14bd..447d672f8 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/api.ts +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/api.ts @@ -1,6 +1,6 @@ // 朋友圈相关的API接口 import { useWebSocketStore } from "@/store/module/websocket/websocket"; - +import request from "@/api/request"; // 朋友圈请求参数接口 export interface FetchMomentParams { friendMessageId: number; @@ -30,3 +30,16 @@ export const fetchVoiceToTextApi = async (params: VoiceToTextParams) => { seq: params.seq, }); }; + +export const getChatroomMemberList = async (params: { groupId: number }) => { + return request( + "/v1/chatroom/getMemberList", + { + groupId: params.groupId, + keyword: "", + limit: 500, + page: 1, + }, + "GET", + ); +}; 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 a3de4b84b..bd5813d00 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 @@ -14,7 +14,11 @@ import styles from "./com.module.scss"; import { useWeChatStore } from "@/store/module/weChat/weChat"; import { useContactStore } from "@/store/module/weChat/contacts"; import { useCustomerStore } from "@weChatStore/customer"; -import { fetchReCallApi, fetchVoiceToTextApi } from "./api"; +import { + fetchReCallApi, + fetchVoiceToTextApi, + getChatroomMemberList, +} from "./api"; import TransmitModal from "./components/TransmitModal"; const IMAGE_EXT_REGEX = /\.(jpg|jpeg|png|gif|webp|bmp|svg)$/i; @@ -131,6 +135,16 @@ const tryParseContentJson = (content: string): Record | null => { interface MessageRecordProps { contract: ContractData | weChatGroup; } +type GroupRenderItem = { + id: number; + identifier: string; + nickname: string; + avatar: string; + groupId: number; + chatroomId?: string; + wechatId?: string; +}; + const MessageRecord: React.FC = ({ contract }) => { const messagesEndRef = useRef(null); // 右键菜单状态 @@ -152,9 +166,6 @@ const MessageRecord: React.FC = ({ contract }) => { const loadChatMessages = useWeChatStore(state => state.loadChatMessages); const messagesLoading = useWeChatStore(state => state.messagesLoading); const isLoadingData = useWeChatStore(state => state.isLoadingData); - const currentGroupMembers = useWeChatStore( - state => state.currentGroupMembers, - ); const showCheckbox = useWeChatStore(state => state.showCheckbox); const prevMessagesRef = useRef(currentMessages); const updateShowCheckbox = useWeChatStore(state => state.updateShowCheckbox); @@ -168,6 +179,7 @@ const MessageRecord: React.FC = ({ contract }) => { ); const setTransmitModal = useContactStore(state => state.setTransmitModal); + const [groupRender, setGroupRender] = useState([]); const currentContract = useWeChatStore(state => state.currentContract); const updateQuoteMessageContent = useWeChatStore( @@ -345,6 +357,37 @@ const MessageRecord: React.FC = ({ contract }) => { ); }; + useEffect(() => { + const fetchGroupMembers = async () => { + if (!contract.chatroomId) { + setGroupRender([]); + return; + } + try { + const res = await getChatroomMemberList({ groupId: contract.id }); + setGroupRender(res?.list || []); + } catch (error) { + console.error("获取群成员失败", error); + setGroupRender([]); + } + }; + fetchGroupMembers(); + }, [contract.id, contract.chatroomId]); + + const renderGroupUser = (msg: ChatRecord) => { + if (!msg) { + return { avatar: "", nickname: "" }; + } + const member = + groupRender.find(user => user?.identifier === msg?.sender?.wechatId) || + groupRender.find(user => user?.wechatId === msg?.sender?.wechatId); + + return { + avatar: member?.avatar || msg?.sender?.avatar || "", + nickname: member?.nickname || msg?.sender?.nickname || "", + }; + }; + useEffect(() => { const prevMessages = prevMessagesRef.current; const prevLength = prevMessages.length; @@ -498,13 +541,6 @@ const MessageRecord: React.FC = ({ contract }) => { }; // 获取群成员头像 - const groupMemberAvatar = (msg: ChatRecord) => { - const groupMembers = currentGroupMembers.find( - v => v?.wechatId == msg?.sender?.wechatId, - ); - return groupMembers?.avatar; - }; - // 清理微信ID前缀 const clearWechatidInContent = (sender: any, content: string) => { try { @@ -579,6 +615,7 @@ const MessageRecord: React.FC = ({ contract }) => { const isOwn = msg?.isSend; const isGroup = !!contract.chatroomId; + const groupUser = isGroup ? renderGroupUser(msg) : null; return (
    = ({ contract }) => { {/* 如果是群聊 */} {isGroup && !isOwn && ( <> - {/* Checkbox 显示控制 */} + {/* 群聊场景下根据消息发送者匹配头像与昵称 */} {showCheckbox && (
    = ({ contract }) => { )} } className={styles.messageAvatar} />
    {!isOwn && (
    - {msg?.sender?.nickname} + {groupUser?.nickname}
    )} <> diff --git a/Touchkebao/src/store/module/weChat/weChat.data.ts b/Touchkebao/src/store/module/weChat/weChat.data.ts index dc9aa163e..1b0c1e94c 100644 --- a/Touchkebao/src/store/module/weChat/weChat.data.ts +++ b/Touchkebao/src/store/module/weChat/weChat.data.ts @@ -46,6 +46,8 @@ export interface WeChatState { currentMessagesPageSize: number; /** 是否还有更多历史消息 */ currentMessagesHasMore: boolean; + /** 当前消息请求ID,用于防止跨联系人数据串联 */ + currentMessagesRequestId: number; /** 添加新消息 */ addMessage: (message: ChatRecord) => void; /** 更新指定消息 */ diff --git a/Touchkebao/src/store/module/weChat/weChat.ts b/Touchkebao/src/store/module/weChat/weChat.ts index e06e27cd5..91282a3a4 100644 --- a/Touchkebao/src/store/module/weChat/weChat.ts +++ b/Touchkebao/src/store/module/weChat/weChat.ts @@ -516,6 +516,7 @@ export const useWeChatStore = create()( currentMessagesPage: 1, currentMessagesPageSize: DEFAULT_MESSAGE_PAGE_SIZE, currentMessagesHasMore: true, + currentMessagesRequestId: 0, // ==================== 聊天消息管理方法 ==================== /** 添加新消息到当前聊天 */ @@ -603,6 +604,7 @@ export const useWeChatStore = create()( currentMessagesPage: 1, currentMessagesHasMore: true, currentMessagesPageSize: DEFAULT_MESSAGE_PAGE_SIZE, + currentMessagesRequestId: 0, }); }, /** 设置当前联系人并加载相关数据 */ @@ -616,6 +618,7 @@ export const useWeChatStore = create()( pendingMessages = []; const state = useWeChatStore.getState(); + const newRequestId = Date.now(); // 切换联系人时清空当前消息,等待重新加载 set({ currentMessages: [], @@ -623,6 +626,7 @@ export const useWeChatStore = create()( currentMessagesHasMore: true, currentMessagesPageSize: DEFAULT_MESSAGE_PAGE_SIZE, isLoadingAiChat: false, + currentMessagesRequestId: newRequestId, }); const params: any = {}; @@ -643,7 +647,10 @@ export const useWeChatStore = create()( set({ aiQuoteMessageContent: result }); }); // 注意:会话列表的未读数清零在MessageList组件的onContactClick中处理 - set({ currentContract: contract }); + set({ + currentContract: contract, + currentMessagesRequestId: newRequestId, + }); updateConfig({ id: contract.id, config: { chat: true }, @@ -656,8 +663,10 @@ export const useWeChatStore = create()( loadChatMessages: async (Init: boolean, pageOverride?: number) => { const state = useWeChatStore.getState(); const contact = state.currentContract; + const requestIdAtStart = state.currentMessagesRequestId; + const requestedContactId = contact?.id; - if (!contact) { + if (!contact || !requestedContactId) { return; } @@ -716,22 +725,42 @@ export const useWeChatStore = create()( }); } - set(current => ({ - currentMessages: Init - ? sortedMessages - : [...sortedMessages, ...current.currentMessages], - currentGroupMembers: - Init && isGroup ? nextGroupMembers : current.currentGroupMembers, - currentMessagesPage: paginationMeta.page, - currentMessagesPageSize: paginationMeta.limit, - currentMessagesHasMore: paginationMeta.hasMore, - })); + set(current => { + if ( + current.currentMessagesRequestId !== requestIdAtStart || + !current.currentContract || + current.currentContract.id !== requestedContactId + ) { + return {}; + } + return { + currentMessages: Init + ? sortedMessages + : [...sortedMessages, ...current.currentMessages], + currentGroupMembers: + Init && isGroup + ? nextGroupMembers + : current.currentGroupMembers, + currentMessagesPage: paginationMeta.page, + currentMessagesPageSize: paginationMeta.limit, + currentMessagesHasMore: paginationMeta.hasMore, + }; + }); } catch (error) { console.error("获取聊天消息失败:", error); } finally { - set({ - messagesLoading: false, - isLoadingData: false, + set(current => { + if ( + current.currentMessagesRequestId !== requestIdAtStart || + !current.currentContract || + current.currentContract.id !== requestedContactId + ) { + return {}; + } + return { + messagesLoading: false, + isLoadingData: false, + }; }); } }, diff --git a/Touchkebao/src/store/module/websocket/msgManage.ts b/Touchkebao/src/store/module/websocket/msgManage.ts index f103f9e14..d83fce6a5 100644 --- a/Touchkebao/src/store/module/websocket/msgManage.ts +++ b/Touchkebao/src/store/module/websocket/msgManage.ts @@ -199,8 +199,6 @@ const messageHandlers: Record = { }); } }, - - // 可以继续添加更多处理器... }; //消息异步同步 const goAsyncServiceData = (message: Messages) => { From 9e101e924e33d02c97e988f4d97159bae98ab020 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Thu, 27 Nov 2025 17:00:36 +0800 Subject: [PATCH 32/49] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=9C=80=E7=BB=88?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E5=90=8C=E6=AD=A5=E5=A4=84=E7=90=86=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E7=A1=AE=E4=BF=9D=E5=9C=A8=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E5=8F=91=E9=80=81=E5=90=8E=E6=AD=A3=E7=A1=AE=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E7=8A=B6=E6=80=81=E5=92=8C=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Touchkebao/src/store/module/websocket/msgManage.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Touchkebao/src/store/module/websocket/msgManage.ts b/Touchkebao/src/store/module/websocket/msgManage.ts index d83fce6a5..32666c596 100644 --- a/Touchkebao/src/store/module/websocket/msgManage.ts +++ b/Touchkebao/src/store/module/websocket/msgManage.ts @@ -70,6 +70,15 @@ const messageHandlers: Record = { updateMessage(message.friendMessageId || message.chatroomMessageId, { sendStatus: 0, }); + // 最终消息同步处理 + dataProcessing({ + chatroomMessageId: message.chatroomMessageId, + friendMessageId: message.friendMessageId, + sendStatus: message.sendStatus, + type: "CmdSendMessageResult", + wechatAccountId: 1, + wechatTime: message.wechatTime, + }); }, // 接收消息响应 CmdReceiveMessageResp: message => { From 499b36b03b52bb591d378cd90bb0139c1a2ab593 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Thu, 27 Nov 2025 17:06:20 +0800 Subject: [PATCH 33/49] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=85=AC=E5=85=B1?= =?UTF-8?q?=E5=BF=AB=E6=8D=B7=E8=AF=AD=E9=80=89=E9=A1=B9=E5=B9=B6=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E9=83=A8=E9=97=A8=E5=BF=AB=E6=8D=B7=E8=AF=AD=E6=A0=87?= =?UTF-8?q?=E7=AD=BE=EF=BC=8C=E4=BC=98=E5=8C=96QuickWords=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E7=9A=84=E7=94=A8=E6=88=B7=E4=BD=93=E9=AA=8C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/ProfileCard/components/QuickWords/index.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/index.tsx index 8b1315a9d..2e2aa7433 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/index.tsx @@ -502,10 +502,13 @@ const QuickWords: React.FC = ({ onInsert }) => { key: QuickWordsType.PERSONAL.toString(), label: "个人快捷语", }, - + { + key: QuickWordsType.PUBLIC.toString(), + label: "公共快捷语", + }, { key: QuickWordsType.DEPARTMENT.toString(), - label: "公司快捷语", + label: "部门快捷语", }, ]} /> From 5c7940538d33c13078e5c8f710f57f1b5a9f36f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Thu, 27 Nov 2025 17:07:48 +0800 Subject: [PATCH 34/49] =?UTF-8?q?=E6=9B=B4=E6=96=B0QuickWords=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E7=9A=84=E9=BB=98=E8=AE=A4=E9=80=89=E9=A1=B9=EF=BC=8C?= =?UTF-8?q?=E5=B0=86=E6=B4=BB=E5=8A=A8=E6=A0=87=E7=AD=BE=E4=BB=8E=E5=85=AC?= =?UTF-8?q?=E5=85=B1=E5=BF=AB=E6=8D=B7=E8=AF=AD=E8=B0=83=E6=95=B4=E4=B8=BA?= =?UTF-8?q?=E4=B8=AA=E4=BA=BA=E5=BF=AB=E6=8D=B7=E8=AF=AD=EF=BC=8C=E4=BB=A5?= =?UTF-8?q?=E6=94=B9=E5=96=84=E7=94=A8=E6=88=B7=E4=BD=93=E9=AA=8C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/ProfileCard/components/QuickWords/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/index.tsx index 2e2aa7433..514f99572 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/index.tsx @@ -62,7 +62,7 @@ export interface QuickWordsProps { const QuickWords: React.FC = ({ onInsert }) => { const [activeTab, setActiveTab] = useState( - QuickWordsType.PUBLIC, + QuickWordsType.PERSONAL, ); const [keyword, setKeyword] = useState(""); const [loading, setLoading] = useState(false); From 637dcddee2855f3b944ed35d9a00ca9b1dbbd79d Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Thu, 27 Nov 2025 17:09:53 +0800 Subject: [PATCH 35/49] =?UTF-8?q?=E8=81=8A=E5=A4=A9=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E7=8A=B6=E6=80=81=20+=20=E8=A1=A8=E6=A0=BC?= =?UTF-8?q?=E5=AF=BC=E5=87=BA=E5=BA=95=E5=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/UserController.php | 42 +++-- .../chukebao/controller/DataProcessing.php | 40 +++++ .../controller/WechatFriendController.php | 94 +++------- .../common/controller/ExportController.php | 166 ++++++++++++++++++ .../cunkebao/controller/BaseController.php | 7 + 5 files changed, 256 insertions(+), 93 deletions(-) create mode 100644 Server/application/common/controller/ExportController.php diff --git a/Server/application/api/controller/UserController.php b/Server/application/api/controller/UserController.php index 209f1b0e1..ab4ce1335 100644 --- a/Server/application/api/controller/UserController.php +++ b/Server/application/api/controller/UserController.php @@ -201,40 +201,38 @@ class UserController extends BaseController * 修改密码 * @return \think\response\Json */ - public function modifyPwd() + public function modifyPwd($data = []) { - // 获取并验证参数 - $params = $this->validateModifyPwdParams(); - if (!is_array($params)) { - return $params; + + if (empty($data)) { + return json_encode(['code' => 400,'msg' => '参数缺失']); } - $authorization = trim($this->request->header('authorization', $this->authorization)); + if (!isset($data['id']) || !isset($data['pwd'])) { + return json_encode(['code' => 401,'msg' => '参数缺失']); + } + $authorization = $this->authorization; + if (empty($authorization)) { - return errorJson('缺少授权信息'); + return json_encode(['code' => 400,'msg' => '缺少授权信息']); } - $headerData = ['client:' . self::CLIENT_TYPE]; - $header = setHeader($headerData, $authorization, 'plain'); + $headerData = ['client:system']; + $header = setHeader($headerData, $authorization, 'json'); + $params = [ + 'id' => $data['id'], + 'newPw' => $data['pwd'], + ]; try { - $result = requestCurl($this->baseUrl . 'api/Account/self', $params, 'PUT', $header); + $result = requestCurl($this->baseUrl . 'api/Account/modifypw', $params, 'PUT', $header,'json'); $response = handleApiResponse($result); - if (empty($response)) { - // 获取当前用户信息 - $currentUser = CompanyAccountModel::where('token', $authorization)->find(); - if ($currentUser) { - recordUserLog($currentUser['id'], $currentUser['userName'], 'MODIFY_PASSWORD', '修改密码成功', [], 200, '修改成功'); - } - return successJson(['message' => '修改成功']); + return json_encode(['code' => 200,'msg' => '修改成功']); } - - recordUserLog(0, '', 'MODIFY_PASSWORD', '修改密码失败', $params, 500, $response); - return errorJson($response); + return json_encode(['code' => 400,'msg' => $response]); } catch (\Exception $e) { - recordUserLog(0, '', 'MODIFY_PASSWORD', '修改密码异常', $params, 500, $e->getMessage()); - return errorJson('修改密码失败:' . $e->getMessage()); + return json_encode(['code' => 400,'msg' => '修改密码失败:' . $e->getMessage()]); } } diff --git a/Server/application/chukebao/controller/DataProcessing.php b/Server/application/chukebao/controller/DataProcessing.php index 72e617ea5..863fee484 100644 --- a/Server/application/chukebao/controller/DataProcessing.php +++ b/Server/application/chukebao/controller/DataProcessing.php @@ -4,6 +4,7 @@ namespace app\chukebao\controller; use library\ResponseHelper; use app\api\model\WechatFriendModel; +use app\api\model\WechatMessageModel; use app\api\controller\MessageController; @@ -33,6 +34,7 @@ class DataProcessing extends BaseController 'CmdAllotFriend', //转让好友 {labels、wechatAccountId、wechatFriendId} 'CmdChatroomOperate', //修改群信息 {chatroomName(群名)、announce(公告)、extra(公告)、wechatAccountId、wechatChatroomId} 'CmdNewMessage', //接收消息 + 'CmdSendMessageResult', //更新消息状态 ]; if (empty($type) || empty($wechatAccountId)) { @@ -107,6 +109,44 @@ class DataProcessing extends BaseController $msg = '消息记录失败'; $codee = 400; } + break; + case 'CmdSendMessageResult': + $friendMessageId = $this->request->param('friendMessageId', 0); + $chatroomMessageId = $this->request->param('chatroomMessageId', 0); + $sendStatus = $this->request->param('sendStatus', null); + $wechatTime = $this->request->param('wechatTime', 0); + + if ($sendStatus === null) { + return ResponseHelper::error('sendStatus不能为空'); + } + + if (empty($friendMessageId) && empty($chatroomMessageId)) { + return ResponseHelper::error('friendMessageId或chatroomMessageId至少提供一个'); + } + + $messageId = $friendMessageId ?: $chatroomMessageId; + $update = [ + 'sendStatus' => (int)$sendStatus, + ]; + + if (!empty($wechatTime)) { + $update['wechatTime'] = strlen((string)$wechatTime) > 10 + ? intval($wechatTime / 1000) + : (int)$wechatTime; + } + + $affected = WechatMessageModel::where('id', $messageId)->update($update); + + if ($affected === false) { + return ResponseHelper::error('更新消息状态失败'); + } + + if ($affected === 0) { + return ResponseHelper::error('消息不存在'); + } + + $msg = '更新消息状态成功'; + break; } return ResponseHelper::success('',$msg,$codee); } diff --git a/Server/application/chukebao/controller/WechatFriendController.php b/Server/application/chukebao/controller/WechatFriendController.php index 931d8ce63..a37e31e66 100644 --- a/Server/application/chukebao/controller/WechatFriendController.php +++ b/Server/application/chukebao/controller/WechatFriendController.php @@ -23,7 +23,7 @@ class WechatFriendController extends BaseController $total = $query->count(); $list = $query->page($page, $limit)->select(); - // 提取所有好友ID + // 提取所有好友ID $friendIds = array_column($list, 'id'); $aiTypeData = []; @@ -67,7 +67,7 @@ class WechatFriendController extends BaseController $friend = Db::table('s2_wechat_friend') ->where(['id' => $friendId, 'isDeleted' => 0]) ->find(); - + if (empty($friend)) { return ResponseHelper::error('好友不存在'); } @@ -78,11 +78,11 @@ class WechatFriendController extends BaseController $friend['createTime'] = !empty($friend['createTime']) ? date('Y-m-d H:i:s', $friend['createTime']) : ''; $friend['updateTime'] = !empty($friend['updateTime']) ? date('Y-m-d H:i:s', $friend['updateTime']) : ''; $friend['passTime'] = !empty($friend['passTime']) ? date('Y-m-d H:i:s', $friend['passTime']) : ''; - + // 获取AI类型设置 $aiTypeSetting = FriendSettings::where('friendId', $friendId)->find(); $friend['aiType'] = $aiTypeSetting ? $aiTypeSetting['type'] : 0; - + return ResponseHelper::success(['detail' => $friend]); } @@ -183,87 +183,38 @@ class WechatFriendController extends BaseController if (empty($accountId)) { return ResponseHelper::error('请先登录'); } - + // 直接使用operatorAccountId查询添加好友任务记录 $query = Db::table('s2_friend_task') ->where('operatorAccountId', $accountId) ->order('createTime desc'); - + // 如果指定了状态筛选 if ($status !== '' && $status !== null) { $query->where('status', $status); } - + $total = $query->count(); $tasks = $query->page($page, $limit)->select(); - - // 提取所有任务的phone和wechatId,用于查询好友信息(获取通过时间) - $taskPhones = []; - $taskWechatIds = []; - foreach ($tasks as $task) { - if (!empty($task['phone'])) { - $taskPhones[] = $task['phone']; - } - if (!empty($task['wechatId'])) { - $taskWechatIds[] = $task['wechatId']; - } - } - - // 查询好友信息,获取通过时间 - $friendPassTimeMap = []; - if (!empty($taskPhones) || !empty($taskWechatIds)) { - // 分别通过phone和wechatId查询,确保都能匹配到 - $friendsByPhone = []; - $friendsByWechatId = []; - - if (!empty($taskPhones)) { - $friendsByPhone = Db::table('s2_wechat_friend') - ->where('accountId', $accountId) - ->where('isDeleted', 0) - ->where('phone', 'in', $taskPhones) - ->field('phone,wechatId,passTime,nickname') - ->select(); - } - - if (!empty($taskWechatIds)) { - $friendsByWechatId = Db::table('s2_wechat_friend') - ->where('accountId', $accountId) - ->where('isDeleted', 0) - ->where('wechatId', 'in', $taskWechatIds) - ->field('phone,wechatId,passTime,nickname') - ->select(); - } - - // 合并结果并构建映射表(优先使用phone作为key) - $allFriends = array_merge($friendsByPhone, $friendsByWechatId); - foreach ($allFriends as $friend) { - // 使用phone作为key(如果存在) - if (!empty($friend['phone'])) { - $friendPassTimeMap[$friend['phone']] = [ - 'passTime' => $friend['passTime'] ?? 0, - 'nickname' => $friend['nickname'] ?? '', - ]; - } - // 同时使用wechatId作为key(如果存在且phone为空) - if (!empty($friend['wechatId']) && empty($friend['phone'])) { - $friendPassTimeMap[$friend['wechatId']] = [ - 'passTime' => $friend['passTime'] ?? 0, - 'nickname' => $friend['nickname'] ?? '', - ]; - } - } - } - + + // 处理任务数据 $list = []; foreach ($tasks as $task) { - $taskKey = !empty($task['phone']) ? $task['phone'] : ($task['wechatId'] ?? ''); - $friendInfo = isset($friendPassTimeMap[$taskKey]) ? $friendPassTimeMap[$taskKey] : null; - + // 提取所有任务的phone、wechatId,用于查询好友信息(获取通过时间) + $friendInfo = Db::table('s2_wechat_friend') + ->where(['isDeleted' => 0, 'ownerWechatId' => $task['wechatId']]) + ->where(function ($query) use ($task) { + $query->whereLike('phone', '%'.$task['phone'].'%')->whereOr('alias', $task['phone'])->whereOr('wechatId', $task['phone']); + })->field('phone,wechatId,alias,passTime,nickname')->find(); + + + $item = [ 'taskId' => $task['id'] ?? 0, 'phone' => $task['phone'] ?? '', 'wechatId' => $task['wechatId'] ?? '', + 'alias' => $task['alias'] ?? '', // 添加者信息 'adder' => [ 'avatar' => $task['wechatAvatar'] ?? '', // 添加者头像 @@ -276,6 +227,7 @@ class WechatFriendController extends BaseController 'status' => [ 'code' => $task['status'] ?? 0, // 状态码:0执行中,1执行成功,2执行失败 'text' => $this->getTaskStatusText($task['status'] ?? 0), // 状态文本 + 'extra' => '' ], // 时间信息 'time' => [ @@ -299,10 +251,10 @@ class WechatFriendController extends BaseController 'labels' => !empty($task['labels']) ? explode(',', $task['labels']) : [], // 标签 ] ]; - + $list[] = $item; } - + return ResponseHelper::success(['list' => $list, 'total' => $total]); } @@ -319,7 +271,7 @@ class WechatFriendController extends BaseController 1 => '执行成功', 2 => '执行失败', ]; - + return isset($statusMap[$status]) ? $statusMap[$status] : '未知状态'; } } \ No newline at end of file diff --git a/Server/application/common/controller/ExportController.php b/Server/application/common/controller/ExportController.php new file mode 100644 index 000000000..aea58f157 --- /dev/null +++ b/Server/application/common/controller/ExportController.php @@ -0,0 +1,166 @@ + 需要在请求结束时清理的临时文件 + */ + protected static $tempFiles = []; + + /** + * 导出 Excel(支持指定列插入图片) + * + * @param string $fileName 输出文件名(可不带扩展名) + * @param array $headers 列定义,例如 ['name' => '姓名', 'phone' => '电话'] + * @param array $rows 数据行,需与 $headers 的 key 对应 + * @param array $imageColumns 需要渲染为图片的列 key 列表 + * @param string $sheetName 工作表名称 + * + * @throws Exception + */ + public static function exportExcelWithImages( + $fileName, + array $headers, + array $rows, + array $imageColumns = [], + $sheetName = 'Sheet1' + ) { + if (empty($headers)) { + throw new Exception('导出列定义不能为空'); + } + if (empty($rows)) { + throw new Exception('导出数据不能为空'); + } + + $excel = new PHPExcel(); + $sheet = $excel->getActiveSheet(); + $sheet->setTitle($sheetName); + + $columnKeys = array_keys($headers); + + // 写入表头 + foreach ($columnKeys as $index => $key) { + $columnLetter = self::columnLetter($index); + $sheet->setCellValue($columnLetter . '1', $headers[$key]); + $sheet->getColumnDimension($columnLetter)->setAutoSize(true); + } + + // 写入数据与图片 + foreach ($rows as $rowIndex => $rowData) { + $excelRow = $rowIndex + 2; // 数据从第 2 行开始 + foreach ($columnKeys as $colIndex => $key) { + $columnLetter = self::columnLetter($colIndex); + $cell = $columnLetter . $excelRow; + $value = isset($rowData[$key]) ? $rowData[$key] : ''; + + if (in_array($key, $imageColumns, true) && !empty($value)) { + $imagePath = self::resolveImagePath($value); + if ($imagePath) { + $drawing = new PHPExcel_Worksheet_Drawing(); + $drawing->setPath($imagePath); + $drawing->setCoordinates($cell); + $drawing->setOffsetX(5); + $drawing->setOffsetY(5); + $drawing->setHeight(60); + $drawing->setWorksheet($sheet); + $sheet->getRowDimension($excelRow)->setRowHeight(60); + } else { + $sheet->setCellValue($cell, $value); + } + } else { + $sheet->setCellValue($cell, $value); + } + } + } + + $safeName = preg_replace('/[^\w\-]/', '_', $fileName ?: 'export_' . date('Ymd_His')); + if (stripos($safeName, '.xlsx') === false) { + $safeName .= '.xlsx'; + } + + if (ob_get_length()) { + ob_end_clean(); + } + + header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + header('Cache-Control: max-age=0'); + header('Content-Disposition: attachment;filename="' . $safeName . '"'); + + $writer = PHPExcel_IOFactory::createWriter($excel, 'Excel2007'); + $writer->save('php://output'); + + self::cleanupTempFiles(); + exit; + } + + /** + * 根据列序号生成 Excel 列字母 + * + * @param int $index + * @return string + */ + protected static function columnLetter($index) + { + $letters = ''; + do { + $letters = chr($index % 26 + 65) . $letters; + $index = intval($index / 26) - 1; + } while ($index >= 0); + + return $letters; + } + + /** + * 将远程或本地图片路径转换为可用的本地文件路径 + * + * @param string $path + * @return string|null + */ + protected static function resolveImagePath($path) + { + if (empty($path)) { + return null; + } + + if (preg_match('/^https?:\/\//i', $path)) { + $tempFile = tempnam(sys_get_temp_dir(), 'export_img_'); + $stream = @file_get_contents($path); + if ($stream === false) { + return null; + } + file_put_contents($tempFile, $stream); + self::$tempFiles[] = $tempFile; + return $tempFile; + } + + if (file_exists($path)) { + return $path; + } + + return null; + } + + /** + * 清理所有临时文件 + */ + protected static function cleanupTempFiles() + { + foreach (self::$tempFiles as $file) { + if (file_exists($file)) { + @unlink($file); + } + } + self::$tempFiles = []; + } +} \ No newline at end of file diff --git a/Server/application/cunkebao/controller/BaseController.php b/Server/application/cunkebao/controller/BaseController.php index 26bd79dba..f3875d549 100644 --- a/Server/application/cunkebao/controller/BaseController.php +++ b/Server/application/cunkebao/controller/BaseController.php @@ -3,6 +3,7 @@ namespace app\cunkebao\controller; use app\api\controller\AccountController; +use app\api\controller\UserController; use app\common\service\ClassTableService; use library\ResponseHelper; use think\Controller; @@ -148,6 +149,12 @@ class BaseController extends Controller $res = Db::name('users')->where(['id' => $userId, 'companyId' => $companyId])->update($data); if (!empty($res)) { + if ($user['typeId'] == 1 && !empty($user['s2_accountId'])) { + $UserController = new UserController(); + $UserController->modifyPwd(['id' => $user['s2_accountId'],'pwd' => $passWord]); + } + + return ResponseHelper::success('密码修改成功'); } else { return ResponseHelper::error('密码修改失败'); From e658a32244fab2a0988e2f4d27e9d70187cef0ce Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Thu, 27 Nov 2025 18:10:23 +0800 Subject: [PATCH 36/49] 1 --- Server/application/chukebao/controller/DataProcessing.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Server/application/chukebao/controller/DataProcessing.php b/Server/application/chukebao/controller/DataProcessing.php index 863fee484..af0d0d1b2 100644 --- a/Server/application/chukebao/controller/DataProcessing.php +++ b/Server/application/chukebao/controller/DataProcessing.php @@ -138,11 +138,11 @@ class DataProcessing extends BaseController $affected = WechatMessageModel::where('id', $messageId)->update($update); if ($affected === false) { - return ResponseHelper::error('更新消息状态失败'); + return ResponseHelper::success('','更新消息状态失败'); } if ($affected === 0) { - return ResponseHelper::error('消息不存在'); + return ResponseHelper::success('','消息不存在'); } $msg = '更新消息状态成功'; From a03c162962d534c39c60f3919ad5f6b75dc2e7e8 Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Fri, 28 Nov 2025 15:32:02 +0800 Subject: [PATCH 37/49] 111 --- .../chukebao/controller/DataProcessing.php | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/Server/application/chukebao/controller/DataProcessing.php b/Server/application/chukebao/controller/DataProcessing.php index af0d0d1b2..887fb2eff 100644 --- a/Server/application/chukebao/controller/DataProcessing.php +++ b/Server/application/chukebao/controller/DataProcessing.php @@ -2,6 +2,7 @@ namespace app\chukebao\controller; +use app\api\model\WechatChatroomModel; use library\ResponseHelper; use app\api\model\WechatFriendModel; use app\api\model\WechatMessageModel; @@ -77,15 +78,31 @@ class DataProcessing extends BaseController if(empty($toAccountId)){ return ResponseHelper::error('参数缺失'); } - - $friend = WechatFriendModel::where(['id' => $wechatFriendId,'wechatAccountId' => $wechatAccountId])->find(); - if(empty($friend)){ - return ResponseHelper::error('好友不存在'); + if(empty($wechatFriendId) && empty($wechatChatroomId)){ + return ResponseHelper::error('参数缺失'); } - $friend->accountId = $toAccountId; - $friend->updateTime = time(); - $friend->save(); - $msg = '好友转移成功'; + + + if (!empty($wechatFriendId)){ + $dsta = WechatFriendModel::where(['id' => $wechatFriendId,'wechatAccountId' => $wechatAccountId])->find(); + $msg = '好友转移成功'; + if(empty($ddta)){ + return ResponseHelper::error('好友不存在'); + } + } + + + if (!empty($wechatChatroomId)){ + $dsta = WechatChatroomModel::where(['id' => $wechatChatroomId,'wechatAccountId' => $wechatAccountId])->find(); + $msg = '群聊转移成功'; + if(empty($ddta)){ + return ResponseHelper::error('群聊不存在'); + } + } + + $dsta->accountId = $toAccountId; + $dsta->updateTime = time(); + $dsta->save(); break; case 'CmdNewMessage': if(empty($friendMessage) && empty($chatroomMessage)){ @@ -107,7 +124,7 @@ class DataProcessing extends BaseController $msg = '消息记录成功'; }else{ $msg = '消息记录失败'; - $codee = 400; + $codee = 200; } break; case 'CmdSendMessageResult': From ce47856c81c38e8767c0b3a8e270490e934c333d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Fri, 28 Nov 2025 15:33:32 +0800 Subject: [PATCH 38/49] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E7=BB=84=E4=BB=B6=EF=BC=8C=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E7=8A=B6=E6=80=81=E7=9A=84=E8=A7=86=E8=A7=89?= =?UTF-8?q?=E5=8F=8D=E9=A6=88=EF=BC=8C=E6=94=AF=E6=8C=81=E6=A0=B9=E6=8D=AE?= =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E5=8F=B7=E8=BF=9B=E8=A1=8C=E4=BC=9A=E8=AF=9D?= =?UTF-8?q?=E7=AD=9B=E9=80=89=EF=BC=8C=E5=B9=B6=E9=87=8D=E6=9E=84=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E9=80=BB=E8=BE=91=E4=BB=A5=E6=8F=90=E5=8D=87=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E4=BD=93=E9=AA=8C=E5=92=8C=E4=BB=A3=E7=A0=81=E5=8F=AF?= =?UTF-8?q?=E8=AF=BB=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mine/wechat-accounts/detail/index.tsx | 17 ++- Touchkebao/src/api/request.ts | 16 +- .../components/toContract/index.tsx | 51 ++++++- .../components/QuickWords/index.tsx | 7 +- .../SidebarMenu/MessageList/com.module.scss | 31 ++++ .../SidebarMenu/MessageList/index.tsx | 138 +++++++++++++++--- .../src/pages/pc/ckbox/weChat/index.tsx | 6 +- .../src/store/module/websocket/msgManage.ts | 2 +- .../src/store/module/websocket/websocket.ts | 30 ---- Touchkebao/src/utils/dbAction/contact.ts | 6 +- Touchkebao/src/utils/dbAction/message.ts | 2 + 11 files changed, 226 insertions(+), 80 deletions(-) diff --git a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx index 3be617b58..a5a3b29dc 100644 --- a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx +++ b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx @@ -21,7 +21,11 @@ import { } from "@ant-design/icons"; import Layout from "@/components/Layout/Layout"; import style from "./detail.module.scss"; -import { getWechatAccountDetail, getWechatFriends, transferWechatFriends } from "./api"; +import { + getWechatAccountDetail, + getWechatFriends, + transferWechatFriends, +} from "./api"; import DeviceSelection from "@/components/DeviceSelection"; import { DeviceSelectionItem } from "@/components/DeviceSelection/data"; @@ -36,7 +40,9 @@ const WechatAccountDetail: React.FC = () => { const [accountInfo, setAccountInfo] = useState(null); const [showRestrictions, setShowRestrictions] = useState(false); const [showTransferConfirm, setShowTransferConfirm] = useState(false); - const [selectedDevices, setSelectedDevices] = useState([]); + const [selectedDevices, setSelectedDevices] = useState( + [], + ); const [inheritInfo, setInheritInfo] = useState(true); const [transferLoading, setTransferLoading] = useState(false); const [searchQuery, setSearchQuery] = useState(""); @@ -216,7 +222,7 @@ const WechatAccountDetail: React.FC = () => { await transferWechatFriends({ wechatId: id, devices: selectedDevices.map(device => device.id), - inherit: inheritInfo + inherit: inheritInfo, }); Toast.show({ @@ -608,10 +614,7 @@ const WechatAccountDetail: React.FC = () => {
    同步原有信息
    - + {inheritInfo ? "是" : "否"} diff --git a/Touchkebao/src/api/request.ts b/Touchkebao/src/api/request.ts index 6394d22ef..adfa7ea50 100644 --- a/Touchkebao/src/api/request.ts +++ b/Touchkebao/src/api/request.ts @@ -28,10 +28,20 @@ instance.interceptors.request.use((config: any) => { instance.interceptors.response.use( (res: AxiosResponse) => { - const { code, success, msg } = res.data || {}; - if (code === 200 || success) { - return res.data.data ?? res.data; + const payload = res.data || {}; + const { code, success, msg } = payload; + const hasBizCode = typeof code === "number"; + const hasBizSuccess = typeof success === "boolean"; + const bizSuccess = hasBizCode + ? code === 200 + : hasBizSuccess + ? success + : undefined; + + if (bizSuccess === true || (!hasBizCode && !hasBizSuccess)) { + return payload.data ?? payload; } + Toast.show({ content: msg || "接口错误", position: "top" }); if (code === 401) { localStorage.removeItem("token"); diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/toContract/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/toContract/index.tsx index 1b4d86f77..6249df709 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/toContract/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/toContract/index.tsx @@ -6,11 +6,14 @@ import { WechatFriendAllot, WechatFriendRebackAllot, } from "@/pages/pc/ckbox/weChat/api"; +import { dataProcessing } from "@/api/ai"; import { useCurrentContact } from "@/store/module/weChat/weChat"; import { ContactManager } from "@/utils/dbAction/contact"; import { MessageManager } from "@/utils/dbAction/message"; import { useUserStore } from "@/store/module/user"; import { useWeChatStore } from "@/store/module/weChat/weChat"; +import { useMessageStore } from "@weChatStore/message"; +import { useContactStore } from "@weChatStore/contacts"; const { TextArea } = Input; const { Option } = Select; @@ -37,6 +40,8 @@ const ToContract: React.FC = ({ const clearCurrentContact = useWeChatStore( state => state.clearCurrentContact, ); + const removeSessionById = useMessageStore(state => state.removeSessionById); + const deleteContact = useContactStore(state => state.deleteContact); const [visible, setVisible] = useState(false); const [selectedTarget, setSelectedTarget] = useState(null); const [comment, setComment] = useState(""); @@ -79,6 +84,12 @@ const ToContract: React.FC = ({ notifyReceiver: true, comment: comment.trim(), }); + dataProcessing({ + type: "CmdAllotFriend", + wechatChatroomId: currentContact.id, + toAccountId: selectedTarget as number, + wechatAccountId: currentContact.wechatAccountId, + }); } else { await WechatFriendAllot({ wechatFriendId: currentContact.id, @@ -86,6 +97,12 @@ const ToContract: React.FC = ({ notifyReceiver: true, comment: comment.trim(), }); + dataProcessing({ + type: "CmdAllotFriend", + wechatFriendId: currentContact.id, + toAccountId: selectedTarget as number, + wechatAccountId: currentContact.wechatAccountId, + }); } } @@ -97,7 +114,10 @@ const ToContract: React.FC = ({ const currentUserId = useUserStore.getState().user?.id || 0; const contactType = "chatroomId" in currentContact ? "group" : "friend"; - // 1. 从会话列表数据库删除 + // 1. 立即从Store中删除会话(更新UI) + removeSessionById(currentContact.id, contactType); + + // 2. 从会话列表数据库删除 await MessageManager.deleteSession( currentUserId, currentContact.id, @@ -105,11 +125,19 @@ const ToContract: React.FC = ({ ); console.log("✅ 已从会话列表删除"); - // 2. 从联系人数据库删除 + // 3. 从联系人数据库删除 await ContactManager.deleteContact(currentContact.id); console.log("✅ 已从联系人数据库删除"); - // 3. 清空当前选中的联系人(关闭聊天窗口) + // 4. 从联系人Store中删除(更新联系人列表UI) + try { + await deleteContact(currentContact.id); + console.log("✅ 已从联系人列表Store删除"); + } catch (error) { + console.error("从联系人Store删除失败:", error); + } + + // 5. 清空当前选中的联系人(关闭聊天窗口) clearCurrentContact(); message.success("转接成功,已清理本地数据"); @@ -151,7 +179,10 @@ const ToContract: React.FC = ({ const currentUserId = useUserStore.getState().user?.id || 0; const contactType = "chatroomId" in currentContact ? "group" : "friend"; - // 1. 从会话列表数据库删除 + // 1. 立即从Store中删除会话(更新UI) + removeSessionById(currentContact.id, contactType); + + // 2. 从会话列表数据库删除 await MessageManager.deleteSession( currentUserId, currentContact.id, @@ -159,11 +190,19 @@ const ToContract: React.FC = ({ ); console.log("✅ 已从会话列表删除"); - // 2. 从联系人数据库删除 + // 3. 从联系人数据库删除 await ContactManager.deleteContact(currentContact.id); console.log("✅ 已从联系人数据库删除"); - // 3. 清空当前选中的联系人(关闭聊天窗口) + // 4. 从联系人Store中删除(更新联系人列表UI) + try { + await deleteContact(currentContact.id); + console.log("✅ 已从联系人列表Store删除"); + } catch (error) { + console.error("从联系人Store删除失败:", error); + } + + // 5. 清空当前选中的联系人(关闭聊天窗口) clearCurrentContact(); message.success("转回成功,已清理本地数据"); diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/index.tsx index 514f99572..7c812bb8f 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/index.tsx @@ -502,13 +502,10 @@ const QuickWords: React.FC = ({ onInsert }) => { key: QuickWordsType.PERSONAL.toString(), label: "个人快捷语", }, - { - key: QuickWordsType.PUBLIC.toString(), - label: "公共快捷语", - }, + { key: QuickWordsType.DEPARTMENT.toString(), - label: "部门快捷语", + label: "公司快捷语", }, ]} /> diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/com.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/com.module.scss index 77c9db244..802ee819f 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/com.module.scss +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/com.module.scss @@ -188,6 +188,37 @@ text-align: center; } +// 加载容器样式 +.loadingContainer { + height: 100%; + display: flex; + align-items: flex-start; + justify-content: center; + min-height: 400px; + position: relative; + padding-top: 40px; + + :global(.ant-spin-container) { + width: 100%; + } + + :global(.ant-spin-spinning) { + position: relative; + } + + :global(.ant-spin-text) { + color: #1890ff; + font-size: 14px; + margin-top: 12px; + } +} + +.loadingContent { + width: 100%; + height: 100%; + opacity: 0.6; +} + // 骨架屏样式 .skeletonContainer { padding: 10px; 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 0e39a94b4..2d1876237 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 @@ -1,5 +1,14 @@ import React, { useEffect, useState, useRef } from "react"; -import { List, Avatar, Badge, Modal, Input, message, Skeleton } from "antd"; +import { + List, + Avatar, + Badge, + Modal, + Input, + message, + Skeleton, + Spin, +} from "antd"; import { UserOutlined, TeamOutlined, @@ -74,6 +83,7 @@ const MessageList: React.FC = () => { const contextMenuRef = useRef(null); const previousUserIdRef = useRef(null); const loadRequestRef = useRef(0); + const autoClickRef = useRef(false); // 右键菜单事件处理 const handleContextMenu = (e: React.MouseEvent, session: ChatSession) => { @@ -370,6 +380,7 @@ const MessageList: React.FC = () => { previousUserIdRef.current = currentUserId; setHasLoadedOnce(false); setSessionState([]); + autoClickRef.current = false; // 重置自动点击标记 }, [currentUserId, setHasLoadedOnce, setSessionState]); // 初始化加载会话列表 @@ -383,7 +394,7 @@ const MessageList: React.FC = () => { const requestId = ++loadRequestRef.current; const initializeSessions = async () => { - // setLoading(true); + setLoading(true); try { const cachedSessions = @@ -416,7 +427,7 @@ const MessageList: React.FC = () => { } } finally { if (!isCancelled && loadRequestRef.current === requestId) { - // setLoading(false); + setLoading(false); } } }; @@ -447,25 +458,99 @@ const MessageList: React.FC = () => { // 根据客服和搜索关键词筛选会话 useEffect(() => { - let filtered = [...sessions]; + const filterSessions = async () => { + let filtered = [...sessions]; - // 根据当前选中的客服筛选 - if (currentCustomer && currentCustomer.id !== 0) { - filtered = filtered.filter(v => v.wechatAccountId === currentCustomer.id); + // 根据当前选中的客服筛选 + if (currentCustomer && currentCustomer.id !== 0) { + filtered = filtered.filter( + v => v.wechatAccountId === currentCustomer.id, + ); + } + + // 根据搜索关键词进行模糊匹配(支持搜索昵称、备注名、微信号) + if (searchKeyword.trim()) { + const keyword = searchKeyword.toLowerCase(); + + // 如果搜索关键词可能是微信号,需要从联系人表补充 wechatId + const sessionsNeedingWechatId = filtered.filter( + v => !v.wechatId && v.type === "friend", + ); + + // 批量从联系人表获取 wechatId + if (sessionsNeedingWechatId.length > 0) { + const contactPromises = sessionsNeedingWechatId.map(session => + ContactManager.getContactByIdAndType( + currentUserId, + session.id, + session.type, + ), + ); + const contacts = await Promise.all(contactPromises); + + // 补充 wechatId 到会话数据 + contacts.forEach((contact, index) => { + if (contact && contact.wechatId) { + const session = sessionsNeedingWechatId[index]; + const sessionIndex = filtered.findIndex( + s => s.id === session.id && s.type === session.type, + ); + if (sessionIndex !== -1) { + filtered[sessionIndex] = { + ...filtered[sessionIndex], + wechatId: contact.wechatId, + }; + } + } + }); + } + + filtered = filtered.filter(v => { + const nickname = (v.nickname || "").toLowerCase(); + const conRemark = (v.conRemark || "").toLowerCase(); + const wechatId = (v.wechatId || "").toLowerCase(); + return ( + nickname.includes(keyword) || + conRemark.includes(keyword) || + wechatId.includes(keyword) + ); + }); + } + + setFilteredSessions(filtered); + }; + + filterSessions(); + }, [sessions, currentCustomer, searchKeyword, currentUserId]); + + // 渲染完毕后自动点击第一个聊天记录 + useEffect(() => { + // 只在以下条件满足时自动点击: + // 1. 不在加载状态 + // 2. 有过滤后的会话列表 + // 3. 当前没有选中的联系人 + // 4. 还没有自动点击过 + // 5. 不在搜索状态(避免搜索时自动切换) + if ( + !loading && + filteredSessions.length > 0 && + !currentContract && + !autoClickRef.current && + !searchKeyword.trim() + ) { + // 延迟一点时间确保DOM已渲染 + const timer = setTimeout(() => { + const firstSession = filteredSessions[0]; + if (firstSession) { + autoClickRef.current = true; + onContactClick(firstSession); + } + }, 100); + + return () => clearTimeout(timer); } - - // 根据搜索关键词进行模糊匹配 - if (searchKeyword.trim()) { - const keyword = searchKeyword.toLowerCase(); - filtered = filtered.filter(v => { - const nickname = (v.nickname || "").toLowerCase(); - const conRemark = (v.conRemark || "").toLowerCase(); - return nickname.includes(keyword) || conRemark.includes(keyword); - }); - } - - setFilteredSessions(filtered); - }, [sessions, currentCustomer, searchKeyword]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [loading, filteredSessions, currentContract, searchKeyword]); // ==================== WebSocket消息处理 ==================== @@ -735,11 +820,20 @@ const MessageList: React.FC = () => {
    ); + // 渲染加载中状态(带旋转动画) + const renderLoading = () => ( +
    + +
    {renderSkeleton()}
    +
    +
    + ); + return (
    {loading ? ( - // 加载状态:显示骨架屏 - renderSkeleton() + // 加载状态:显示加载动画和骨架屏 + renderLoading() ) : ( <> {
    ) : (
    -
    - -

    欢迎使用触客宝

    -

    选择一个联系人开始聊天

    -
    +
    )} diff --git a/Touchkebao/src/store/module/websocket/msgManage.ts b/Touchkebao/src/store/module/websocket/msgManage.ts index 32666c596..a7b11732c 100644 --- a/Touchkebao/src/store/module/websocket/msgManage.ts +++ b/Touchkebao/src/store/module/websocket/msgManage.ts @@ -157,7 +157,7 @@ const messageHandlers: Record = { CmdNotify: async (message: WebSocketMessage) => { console.log("通知消息", message); // 在这里添加具体的处理逻辑 - if (message.notify == "Auth failed") { + if (["Auth failed", "Kicked out"].includes(message.notify)) { // 避免重复弹窗 if ((window as any).__CKB_AUTH_FAILED_SHOWN__) { return; diff --git a/Touchkebao/src/store/module/websocket/websocket.ts b/Touchkebao/src/store/module/websocket/websocket.ts index 59a2fcf35..912daf48b 100644 --- a/Touchkebao/src/store/module/websocket/websocket.ts +++ b/Touchkebao/src/store/module/websocket/websocket.ts @@ -351,36 +351,6 @@ export const useWebSocketStore = createPersistStore( _handleMessage: (event: MessageEvent) => { try { const data = JSON.parse(event.data); - // console.log("收到WebSocket消息:", data); - - // 处理特定的通知消息 - if (data.cmdType === "CmdNotify") { - // 处理Auth failed通知 - if (data.notify === "Auth failed" || data.notify === "Kicked out") { - // console.error(`WebSocket ${data.notify},断开连接`); - // Toast.show({ - // content: `WebSocket ${data.notify},断开连接`, - // position: "top", - // }); - - // 禁用自动重连 - if (get().config) { - set({ - config: { - ...get().config!, - autoReconnect: false, - }, - }); - } - - // 停止客服状态查询定时器 - get()._stopAliveStatusTimer(); - - // 断开连接 - get().disconnect(); - return; - } - } const currentState = get(); const newMessage: WebSocketMessage = { diff --git a/Touchkebao/src/utils/dbAction/contact.ts b/Touchkebao/src/utils/dbAction/contact.ts index 661d827e7..77371a2b6 100644 --- a/Touchkebao/src/utils/dbAction/contact.ts +++ b/Touchkebao/src/utils/dbAction/contact.ts @@ -40,6 +40,7 @@ export class ContactManager { /** * 搜索联系人 + * 支持搜索昵称、备注名、微信号 */ static async searchContacts( userId: number, @@ -52,8 +53,11 @@ export class ContactManager { return contacts.filter(contact => { const nickname = (contact.nickname || "").toLowerCase(); const conRemark = (contact.conRemark || "").toLowerCase(); + const wechatId = (contact.wechatId || "").toLowerCase(); return ( - nickname.includes(lowerKeyword) || conRemark.includes(lowerKeyword) + nickname.includes(lowerKeyword) || + conRemark.includes(lowerKeyword) || + wechatId.includes(lowerKeyword) ); }); } catch (error) { diff --git a/Touchkebao/src/utils/dbAction/message.ts b/Touchkebao/src/utils/dbAction/message.ts index e34041b98..6c5346709 100644 --- a/Touchkebao/src/utils/dbAction/message.ts +++ b/Touchkebao/src/utils/dbAction/message.ts @@ -231,6 +231,8 @@ export class MessageManager { "phone", "region", "extendFields", + "wechatId", // 添加wechatId比较 + "alias", // 添加alias比较 ]; for (const field of fieldsToCompare) { From 5087190816d39a31b07685fd11b670088a719f8d Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Fri, 28 Nov 2025 15:46:21 +0800 Subject: [PATCH 39/49] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Server/application/common/TaskServer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/application/common/TaskServer.php b/Server/application/common/TaskServer.php index 2e205fdf5..50d2d6346 100644 --- a/Server/application/common/TaskServer.php +++ b/Server/application/common/TaskServer.php @@ -82,7 +82,7 @@ class TaskServer extends Server if ($current_worker_id == 1) { // 每60秒检查一次自动问候规则 Timer::add(60, function () use ($adapter) { - $adapter->handleAutoGreetings(); + //$adapter->handleAutoGreetings(); }); } From 9e76d39707109f218afc722b82654bd5fed38a81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Fri, 28 Nov 2025 15:49:35 +0800 Subject: [PATCH 40/49] =?UTF-8?q?=E7=A7=BB=E9=99=A4ToContract=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E4=B8=AD=E7=9A=84=E4=B8=80=E9=94=AE=E8=BD=AC=E5=9B=9E?= =?UTF-8?q?=E6=8C=89=E9=92=AE=EF=BC=8C=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E4=BB=A5=E6=94=B9=E5=96=84=E5=8F=AF=E8=AF=BB=E6=80=A7=E5=92=8C?= =?UTF-8?q?=E7=BB=B4=E6=8A=A4=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/MessageEnter/components/toContract/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/toContract/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/toContract/index.tsx index 6249df709..2d0e4c0e9 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/toContract/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/toContract/index.tsx @@ -248,9 +248,9 @@ const ToContract: React.FC = ({ width: "100%", }} > - + */}
    -
    +
    setShowExportPopup(true)} + > 导出
    @@ -1023,6 +1082,153 @@ const WechatAccountDetail: React.FC = () => {
    + {/* 朋友圈导出弹窗 */} + setShowExportPopup(false)} + bodyStyle={{ borderRadius: "16px 16px 0 0" }} + > +
    +
    +

    导出朋友圈

    + +
    + +
    + {/* 关键词搜索 */} +
    + + setExportKeyword(e.target.value)} + allowClear + /> +
    + + {/* 类型筛选 */} +
    + +
    +
    setExportType(undefined)} + > + 全部 +
    +
    setExportType(4)} + > + 文本 +
    +
    setExportType(1)} + > + 图片 +
    +
    setExportType(3)} + > + 视频 +
    +
    +
    + + {/* 开始时间 */} +
    + + setShowStartTimePicker(true)} + /> + setShowStartTimePicker(false)} + onConfirm={val => { + setExportStartTime(val); + setShowStartTimePicker(false); + }} + /> +
    + + {/* 结束时间 */} +
    + + setShowEndTimePicker(true)} + /> + setShowEndTimePicker(false)} + onConfirm={val => { + setExportEndTime(val); + setShowEndTimePicker(false); + }} + /> +
    +
    + +
    + + +
    +
    +
    + {/* 好友详情弹窗 */} {/* Removed */} diff --git a/Server/application/common/controller/ExportController.php b/Server/application/common/controller/ExportController.php index aea58f157..8d2a8079b 100644 --- a/Server/application/common/controller/ExportController.php +++ b/Server/application/common/controller/ExportController.php @@ -26,6 +26,11 @@ class ExportController extends Controller * @param array $rows 数据行,需与 $headers 的 key 对应 * @param array $imageColumns 需要渲染为图片的列 key 列表 * @param string $sheetName 工作表名称 + * @param array $options 额外选项: + * - imageWidth(图片宽度,默认100) + * - imageHeight(图片高度,默认100) + * - imageColumnWidth(图片列宽,默认15) + * - titleRow(标题行内容,支持多行文本数组) * * @throws Exception */ @@ -34,7 +39,8 @@ class ExportController extends Controller array $headers, array $rows, array $imageColumns = [], - $sheetName = 'Sheet1' + $sheetName = 'Sheet1', + array $options = [] ) { if (empty($headers)) { throw new Exception('导出列定义不能为空'); @@ -43,22 +49,114 @@ class ExportController extends Controller throw new Exception('导出数据不能为空'); } + // 默认选项 + $imageWidth = isset($options['imageWidth']) ? (int)$options['imageWidth'] : 100; + $imageHeight = isset($options['imageHeight']) ? (int)$options['imageHeight'] : 100; + $imageColumnWidth = isset($options['imageColumnWidth']) ? (float)$options['imageColumnWidth'] : 15; + $rowHeight = isset($options['rowHeight']) ? (int)$options['rowHeight'] : ($imageHeight + 10); + $excel = new PHPExcel(); $sheet = $excel->getActiveSheet(); $sheet->setTitle($sheetName); $columnKeys = array_keys($headers); + $totalColumns = count($columnKeys); + $lastColumnLetter = self::columnLetter($totalColumns - 1); - // 写入表头 + // 定义特定列的固定宽度(如果未指定则使用默认值) + $columnWidths = isset($options['columnWidths']) ? $options['columnWidths'] : []; + + // 检查是否有标题行 + $titleRow = isset($options['titleRow']) ? $options['titleRow'] : null; + $dataStartRow = 1; // 数据开始行(表头行) + + // 如果有标题行,先写入标题行 + if ($titleRow && is_array($titleRow) && !empty($titleRow)) { + $dataStartRow = 2; // 数据从第2行开始(第1行是标题,第2行是表头) + + // 合并标题行单元格(从第一列到最后一列) + $titleRange = 'A1:' . $lastColumnLetter . '1'; + $sheet->mergeCells($titleRange); + + // 构建标题内容(支持多行) + $titleContent = ''; + if (is_array($titleRow)) { + $titleContent = implode("\n", $titleRow); + } else { + $titleContent = (string)$titleRow; + } + + // 写入标题 + $sheet->setCellValue('A1', $titleContent); + + // 设置标题行样式 + $sheet->getStyle('A1')->applyFromArray([ + 'font' => ['bold' => true, 'size' => 16], + 'alignment' => [ + 'horizontal' => \PHPExcel_Style_Alignment::HORIZONTAL_CENTER, + 'vertical' => \PHPExcel_Style_Alignment::VERTICAL_CENTER, + 'wrap' => true + ], + 'fill' => [ + 'type' => \PHPExcel_Style_Fill::FILL_SOLID, + 'color' => ['rgb' => 'FFF8DC'] // 浅黄色背景 + ], + 'borders' => [ + 'allborders' => [ + 'style' => \PHPExcel_Style_Border::BORDER_THIN, + 'color' => ['rgb' => '000000'] + ] + ] + ]); + $sheet->getRowDimension(1)->setRowHeight(80); // 标题行高度 + } + + // 写入表头并设置列宽 + $headerRow = $dataStartRow; foreach ($columnKeys as $index => $key) { $columnLetter = self::columnLetter($index); - $sheet->setCellValue($columnLetter . '1', $headers[$key]); - $sheet->getColumnDimension($columnLetter)->setAutoSize(true); + $sheet->setCellValue($columnLetter . $headerRow, $headers[$key]); + + // 如果是图片列,设置固定列宽 + if (in_array($key, $imageColumns, true)) { + $sheet->getColumnDimension($columnLetter)->setWidth($imageColumnWidth); + } elseif (isset($columnWidths[$key])) { + // 如果指定了该列的宽度,使用指定宽度 + $sheet->getColumnDimension($columnLetter)->setWidth($columnWidths[$key]); + } else { + // 否则自动调整 + $sheet->getColumnDimension($columnLetter)->setAutoSize(true); + } } + // 设置表头样式 + $headerRange = 'A' . $headerRow . ':' . $lastColumnLetter . $headerRow; + $sheet->getStyle($headerRange)->applyFromArray([ + 'font' => ['bold' => true, 'size' => 11], + 'alignment' => [ + 'horizontal' => \PHPExcel_Style_Alignment::HORIZONTAL_CENTER, + 'vertical' => \PHPExcel_Style_Alignment::VERTICAL_CENTER, + 'wrap' => true + ], + 'fill' => [ + 'type' => \PHPExcel_Style_Fill::FILL_SOLID, + 'color' => ['rgb' => 'FFF8DC'] + ], + 'borders' => [ + 'allborders' => [ + 'style' => \PHPExcel_Style_Border::BORDER_THIN, + 'color' => ['rgb' => '000000'] + ] + ] + ]); + $sheet->getRowDimension($headerRow)->setRowHeight(30); // 增加表头行高以确保文本完整显示 + // 写入数据与图片 + $dataRowStart = $dataStartRow + 1; // 数据从表头行下一行开始 foreach ($rows as $rowIndex => $rowData) { - $excelRow = $rowIndex + 2; // 数据从第 2 行开始 + $excelRow = $dataRowStart + $rowIndex; // 数据行 + $maxRowHeight = $rowHeight; // 记录当前行的最大高度 + foreach ($columnKeys as $colIndex => $key) { $columnLetter = self::columnLetter($colIndex); $cell = $columnLetter . $excelRow; @@ -67,21 +165,79 @@ class ExportController extends Controller if (in_array($key, $imageColumns, true) && !empty($value)) { $imagePath = self::resolveImagePath($value); if ($imagePath) { - $drawing = new PHPExcel_Worksheet_Drawing(); - $drawing->setPath($imagePath); - $drawing->setCoordinates($cell); - $drawing->setOffsetX(5); - $drawing->setOffsetY(5); - $drawing->setHeight(60); - $drawing->setWorksheet($sheet); - $sheet->getRowDimension($excelRow)->setRowHeight(60); + // 获取图片实际尺寸并等比例缩放 + $imageSize = @getimagesize($imagePath); + if ($imageSize) { + $originalWidth = $imageSize[0]; + $originalHeight = $imageSize[1]; + + // 计算等比例缩放后的尺寸 + $ratio = min($imageWidth / $originalWidth, $imageHeight / $originalHeight); + $scaledWidth = $originalWidth * $ratio; + $scaledHeight = $originalHeight * $ratio; + + // 确保不超过最大尺寸 + if ($scaledWidth > $imageWidth) { + $scaledWidth = $imageWidth; + $scaledHeight = $originalHeight * ($imageWidth / $originalWidth); + } + if ($scaledHeight > $imageHeight) { + $scaledHeight = $imageHeight; + $scaledWidth = $originalWidth * ($imageHeight / $originalHeight); + } + + $drawing = new PHPExcel_Worksheet_Drawing(); + $drawing->setPath($imagePath); + $drawing->setCoordinates($cell); + + // 居中显示图片(Excel列宽1单位≈7像素,行高1单位≈0.75像素) + $cellWidthPx = $imageColumnWidth * 7; + $cellHeightPx = $maxRowHeight * 0.75; + $offsetX = max(2, ($cellWidthPx - $scaledWidth) / 2); + $offsetY = max(2, ($cellHeightPx - $scaledHeight) / 2); + + $drawing->setOffsetX((int)$offsetX); + $drawing->setOffsetY((int)$offsetY); + $drawing->setWidth((int)$scaledWidth); + $drawing->setHeight((int)$scaledHeight); + $drawing->setWorksheet($sheet); + + // 更新行高以适应图片(留出一些边距) + $neededHeight = (int)($scaledHeight / 0.75) + 10; + if ($neededHeight > $maxRowHeight) { + $maxRowHeight = $neededHeight; + } + } else { + // 如果无法获取图片尺寸,使用默认尺寸 + $drawing = new PHPExcel_Worksheet_Drawing(); + $drawing->setPath($imagePath); + $drawing->setCoordinates($cell); + $drawing->setOffsetX(5); + $drawing->setOffsetY(5); + $drawing->setWidth($imageWidth); + $drawing->setHeight($imageHeight); + $drawing->setWorksheet($sheet); + } } else { - $sheet->setCellValue($cell, $value); + $sheet->setCellValue($cell, ''); } } else { $sheet->setCellValue($cell, $value); + // 设置文本对齐和换行 + $style = $sheet->getStyle($cell); + $style->getAlignment()->setVertical(\PHPExcel_Style_Alignment::VERTICAL_CENTER); + $style->getAlignment()->setWrapText(true); + // 根据列类型设置水平对齐 + if (in_array($key, ['date', 'postTime'])) { + $style->getAlignment()->setHorizontal(\PHPExcel_Style_Alignment::HORIZONTAL_CENTER); + } else { + $style->getAlignment()->setHorizontal(\PHPExcel_Style_Alignment::HORIZONTAL_LEFT); + } } } + + // 设置行高 + $sheet->getRowDimension($excelRow)->setRowHeight($maxRowHeight); } $safeName = preg_replace('/[^\w\-]/', '_', $fileName ?: 'export_' . date('Ymd_His')); diff --git a/Server/application/cunkebao/config/route.php b/Server/application/cunkebao/config/route.php index c34fcd076..056667dab 100644 --- a/Server/application/cunkebao/config/route.php +++ b/Server/application/cunkebao/config/route.php @@ -38,6 +38,7 @@ Route::group('v1/', function () { Route::get('getWechatInfo', 'app\cunkebao\controller\wechat\GetWechatController@getWechatInfo'); Route::get('overview', 'app\cunkebao\controller\wechat\GetWechatOverviewV1Controller@index'); // 获取微信账号概览数据 Route::get('moments', 'app\cunkebao\controller\wechat\GetWechatMomentsV1Controller@index'); // 获取微信朋友圈 + Route::get('moments/export', 'app\cunkebao\controller\wechat\GetWechatMomentsV1Controller@export'); // 导出微信朋友圈 Route::get('count', 'app\cunkebao\controller\DeviceWechat@count'); Route::get('device-count', 'app\cunkebao\controller\DeviceWechat@deviceCount'); // 获取有登录微信的设备数量 Route::put('refresh', 'app\cunkebao\controller\DeviceWechat@refresh'); // 刷新设备微信状态 diff --git a/Server/application/cunkebao/controller/wechat/GetWechatMomentsV1Controller.php b/Server/application/cunkebao/controller/wechat/GetWechatMomentsV1Controller.php index b34ef7ff1..2057a44d1 100644 --- a/Server/application/cunkebao/controller/wechat/GetWechatMomentsV1Controller.php +++ b/Server/application/cunkebao/controller/wechat/GetWechatMomentsV1Controller.php @@ -2,6 +2,7 @@ namespace app\cunkebao\controller\wechat; +use app\common\controller\ExportController; use app\common\model\Device as DeviceModel; use app\common\model\DeviceUser as DeviceUserModel; use app\common\model\DeviceWechatLogin as DeviceWechatLoginModel; @@ -145,6 +146,164 @@ class GetWechatMomentsV1Controller extends BaseController } } + /** + * 导出朋友圈数据到Excel + * + * @return void + */ + public function export() + { + try { + $wechatId = $this->request->param('wechatId/s', ''); + if (empty($wechatId)) { + return ResponseHelper::error('wechatId不能为空'); + } + + // 权限校验:只能查看当前账号可访问的微信 + $accessibleWechatIds = $this->getAccessibleWechatIds(); + if (!in_array($wechatId, $accessibleWechatIds, true)) { + return ResponseHelper::error('无权查看该微信的朋友圈', 403); + } + + // 获取对应的微信账号ID + $accountId = Db::table('s2_wechat_account') + ->where('wechatId', $wechatId) + ->value('id'); + + if (empty($accountId)) { + return ResponseHelper::error('微信账号不存在或尚未同步', 404); + } + + $query = Db::table('s2_wechat_moments') + ->where('wechatAccountId', $accountId); + + // 关键词搜索 + if ($keyword = trim((string)$this->request->param('keyword', ''))) { + $query->whereLike('content', '%' . $keyword . '%'); + } + + // 类型筛选 + $type = $this->request->param('type', ''); + if ($type !== '' && $type !== null) { + $query->where('type', (int)$type); + } + + // 时间筛选 + $startTime = $this->request->param('startTime', ''); + $endTime = $this->request->param('endTime', ''); + if ($startTime || $endTime) { + $start = $startTime ? strtotime($startTime) : 0; + $end = $endTime ? strtotime($endTime) : time(); + if ($start && $end && $end < $start) { + return ResponseHelper::error('结束时间不能早于开始时间'); + } + $query->whereBetween('createTime', [$start ?: 0, $end ?: time()]); + } + + // 获取所有数据(不分页) + $moments = $query->order('createTime', 'desc')->select(); + + if (empty($moments)) { + return ResponseHelper::error('暂无数据可导出'); + } + + // 定义表头 + $headers = [ + 'date' => '日期', + 'postTime' => '投放时间', + 'functionCategory' => '作用分类', + 'content' => '朋友圈文案', + 'selfReply' => '自回评内容', + 'displayForm' => '朋友圈展示形式', + 'image1' => '配图1', + 'image2' => '配图2', + 'image3' => '配图3', + 'image4' => '配图4', + 'image5' => '配图5', + 'image6' => '配图6', + 'image7' => '配图7', + 'image8' => '配图8', + 'image9' => '配图9', + ]; + + // 格式化数据 + $rows = []; + foreach ($moments as $moment) { + $resUrls = $this->decodeJson($moment['resUrls'] ?? null); + $imageUrls = is_array($resUrls) ? $resUrls : []; + + // 格式化日期和时间 + $createTime = !empty($moment['createTime']) + ? (is_numeric($moment['createTime']) ? $moment['createTime'] : strtotime($moment['createTime'])) + : 0; + $date = $createTime ? date('Y年m月d日', $createTime) : ''; + $postTime = $createTime ? date('H:i', $createTime) : ''; + + // 判断展示形式 + $displayForm = ''; + if (!empty($moment['content']) && !empty($imageUrls)) { + $displayForm = '文字+图片'; + } elseif (!empty($moment['content'])) { + $displayForm = '文字'; + } elseif (!empty($imageUrls)) { + $displayForm = '图片'; + } + + $row = [ + 'date' => $date, + 'postTime' => $postTime, + 'functionCategory' => '', // 暂时放空 + 'content' => $moment['content'] ?? '', + 'selfReply' => '', // 暂时放空 + 'displayForm' => $displayForm, + ]; + + // 分配图片到配图1-9列 + for ($i = 1; $i <= 9; $i++) { + $imageKey = 'image' . $i; + $row[$imageKey] = isset($imageUrls[$i - 1]) ? $imageUrls[$i - 1] : ''; + } + + $rows[] = $row; + } + + // 定义图片列(配图1-9) + $imageColumns = ['image1', 'image2', 'image3', 'image4', 'image5', 'image6', 'image7', 'image8', 'image9']; + + // 生成文件名 + $fileName = '朋友圈投放_' . date('Ymd_His'); + + // 调用导出方法,优化图片显示效果 + ExportController::exportExcelWithImages( + $fileName, + $headers, + $rows, + $imageColumns, + '朋友圈投放', + [ + 'imageWidth' => 120, // 图片宽度(像素) + 'imageHeight' => 120, // 图片高度(像素) + 'imageColumnWidth' => 18, // 图片列宽(Excel单位) + 'rowHeight' => 130, // 行高(像素) + 'columnWidths' => [ // 特定列的固定宽度 + 'date' => 15, // 日期列宽 + 'postTime' => 12, // 投放时间列宽 + 'functionCategory' => 15, // 作用分类列宽 + 'content' => 40, // 朋友圈文案列宽(自动调整可能不够) + 'selfReply' => 30, // 自回评内容列宽 + 'displayForm' => 18, // 朋友圈展示形式列宽 + ], + 'titleRow' => [ // 标题行内容(第一行) + '朋友圈投放', + '我能提供什么价值? (40%) 有谁正在和我合作 (20%) 如何和我合作? (20%) 你找我合作需要付多少钱? (20%)' + ] + ] + ); + } catch (\Exception $e) { + return ResponseHelper::error('导出失败:' . $e->getMessage(), 500); + } + } + /** * 格式化朋友圈数据 * From 0244871fc1fa672ed9c9ca0bca110f6474223f08 Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Fri, 28 Nov 2025 16:04:29 +0800 Subject: [PATCH 42/49] =?UTF-8?q?=E5=A5=BD=E5=8F=8B/=E7=BE=A4=E8=81=8A?= =?UTF-8?q?=E8=BF=81=E7=A7=BB=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chukebao/controller/DataProcessing.php | 14 ++++---- .../Adapters/ChuKeBao/Adapter.php | 33 ++++++++++--------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/Server/application/chukebao/controller/DataProcessing.php b/Server/application/chukebao/controller/DataProcessing.php index 887fb2eff..fdde86b7a 100644 --- a/Server/application/chukebao/controller/DataProcessing.php +++ b/Server/application/chukebao/controller/DataProcessing.php @@ -84,25 +84,25 @@ class DataProcessing extends BaseController if (!empty($wechatFriendId)){ - $dsta = WechatFriendModel::where(['id' => $wechatFriendId,'wechatAccountId' => $wechatAccountId])->find(); + $data = WechatFriendModel::where(['id' => $wechatFriendId,'wechatAccountId' => $wechatAccountId])->find(); $msg = '好友转移成功'; - if(empty($ddta)){ + if(empty($data)){ return ResponseHelper::error('好友不存在'); } } if (!empty($wechatChatroomId)){ - $dsta = WechatChatroomModel::where(['id' => $wechatChatroomId,'wechatAccountId' => $wechatAccountId])->find(); + $data = WechatChatroomModel::where(['id' => $wechatChatroomId,'wechatAccountId' => $wechatAccountId])->find(); $msg = '群聊转移成功'; - if(empty($ddta)){ + if(empty($data)){ return ResponseHelper::error('群聊不存在'); } } - $dsta->accountId = $toAccountId; - $dsta->updateTime = time(); - $dsta->save(); + $data->accountId = $toAccountId; + $data->updateTime = time(); + $data->save(); break; case 'CmdNewMessage': if(empty($friendMessage) && empty($chatroomMessage)){ diff --git a/Server/extend/WeChatDeviceApi/Adapters/ChuKeBao/Adapter.php b/Server/extend/WeChatDeviceApi/Adapters/ChuKeBao/Adapter.php index 247684bcc..d89bf8eae 100644 --- a/Server/extend/WeChatDeviceApi/Adapters/ChuKeBao/Adapter.php +++ b/Server/extend/WeChatDeviceApi/Adapters/ChuKeBao/Adapter.php @@ -307,7 +307,7 @@ class Adapter implements WeChatServiceInterface $conf = array_merge($task_info['reqConf'], ['task_name' => $task_info['name'], 'tags' => $tags]); - $this->createFriendAddTask($accountId, $task['phone'], $conf); + $this->createFriendAddTask($accountId, $task['phone'], $conf, $task['remark']); $friendAddTaskCreated = true; $task['processed_wechat_ids'] = $task['processed_wechat_ids'] . ',' . $wechatId; // 处理失败任务用,用于过滤已处理的微信号 break; @@ -951,28 +951,29 @@ class Adapter implements WeChatServiceInterface } // 创建添加好友任务/执行添加 - public function createFriendAddTask(int $wechatAccountId, string $phone, array $conf) + public function createFriendAddTask(int $wechatAccountId, string $phone, array $conf, $remark = '') { if (empty($wechatAccountId) || empty($phone) || empty($conf)) { return; } - switch ($conf['remarkType']) { - case 'phone': - $remark = $phone . '-' . $conf['task_name']; - break; - case 'nickname': - $remark = ''; - break; - case 'source': - $remark = $conf['task_name']; - break; - default: - $remark = ''; - break; + if (empty($remark)){ + switch ($conf['remarkType']) { + case 'phone': + $remark = $phone . '-' . $conf['task_name']; + break; + case 'nickname': + $remark = ''; + break; + case 'source': + $remark = $conf['task_name']; + break; + default: + $remark = ''; + break; + } } - $tags = []; if (!empty($conf['tags'])) { if (is_array($conf['tags'])) { From 23de3cac7e8a05aa15ec00fab6a4c6108bbd2089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Fri, 28 Nov 2025 16:32:23 +0800 Subject: [PATCH 43/49] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E6=8F=90=E7=A4=BA=E6=A0=8F=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E6=B6=88=E6=81=AF=E5=88=97=E8=A1=A8=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E7=9A=84=E5=90=8C=E6=AD=A5=E9=80=BB=E8=BE=91=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E9=80=90=E9=A1=B5=E5=90=8C=E6=AD=A5=E5=B9=B6=E5=8D=B3?= =?UTF-8?q?=E6=97=B6=E6=9B=B4=E6=96=B0UI=EF=BC=8C=E6=8F=90=E5=8D=87?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E4=BD=93=E9=AA=8C=E5=92=8C=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E5=8F=AF=E8=AF=BB=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SidebarMenu/MessageList/com.module.scss | 38 ++ .../SidebarMenu/MessageList/index.tsx | 439 +++++++++--------- Touchkebao/src/utils/dbAction/message.ts | 15 +- 3 files changed, 276 insertions(+), 216 deletions(-) diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/com.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/com.module.scss index 802ee819f..5d0fb1f1d 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/com.module.scss +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/com.module.scss @@ -242,3 +242,41 @@ align-items: center; margin-bottom: 8px; } + +// 同步状态提示栏样式 +.syncStatusBar { + height: 50px; + display: flex; + align-items: center; + justify-content: center; + border-bottom: 1px solid #f0f0f0; + background-color: #fafafa; + padding: 0 16px; + flex-shrink: 0; +} + +.syncStatusContent { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + justify-content: space-between; + padding: 0px 20px; +} + +.syncStatusText { + font-size: 14px; + color: #666; +} + +.syncButton { + color: green; + cursor: pointer; + font-size: 14px; + &:hover { + color: green; + } + &:active { + transform: scale(0.98); + } +} 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 2d1876237..1e57ed746 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 @@ -1,20 +1,13 @@ import React, { useEffect, useState, useRef } from "react"; -import { - List, - Avatar, - Badge, - Modal, - Input, - message, - Skeleton, - Spin, -} from "antd"; +import { List, Avatar, Badge, Modal, Input, message } from "antd"; import { UserOutlined, TeamOutlined, PushpinOutlined, DeleteOutlined, EditOutlined, + LoadingOutlined, + CheckCircleOutlined, } from "@ant-design/icons"; import styles from "./com.module.scss"; import { @@ -47,14 +40,13 @@ const MessageList: React.FC = () => { // Store状态 const { - loading, hasLoadedOnce, - setLoading, setHasLoadedOnce, sessions, setSessions: setSessionState, } = useMessageStore(); const [filteredSessions, setFilteredSessions] = useState([]); + const [syncing, setSyncing] = useState(false); // 同步状态 // 右键菜单相关状态 const [contextMenu, setContextMenu] = useState<{ @@ -306,70 +298,104 @@ const MessageList: React.FC = () => { // ==================== 数据加载 ==================== - // 与服务器同步数据 + // 与服务器同步数据(优化版:逐页同步,立即更新UI) const syncWithServer = async () => { if (!currentUserId) return; + setSyncing(true); // 开始同步,显示同步状态栏 + try { - // 获取会话列表数据(分页获取所有数据) - let allMessages: any[] = []; let page = 1; const limit = 500; let hasMore = true; + let totalProcessed = 0; + let successCount = 0; + let failCount = 0; - // 分页获取会话列表 + // 分页获取会话列表,每页成功后立即同步 while (hasMore) { - const result: any = await getMessageList({ - page, - limit, - }); + try { + const result: any = await getMessageList({ + page, + limit, + }); - if (!result || !Array.isArray(result) || result.length === 0) { - hasMore = false; - break; - } + if (!result || !Array.isArray(result) || result.length === 0) { + hasMore = false; + break; + } - allMessages = [...allMessages, ...result]; + // 立即处理这一页的数据 + const friends = result.filter( + (msg: any) => msg.dataType === "friend" || !msg.chatroomId, + ); + const groups = result + .filter((msg: any) => msg.dataType === "group" || msg.chatroomId) + .map((msg: any) => ({ + ...msg, + chatroomAvatar: msg.chatroomAvatar || msg.avatar || "", + })); - if (result.length < limit) { - hasMore = false; - } else { + // 立即同步这一页到数据库(会触发UI更新) + // 分页同步时跳过删除检查,避免误删其他页的会话 + await MessageManager.syncSessions( + currentUserId, + { + friends, + groups, + }, + { skipDelete: true }, + ); + + totalProcessed += result.length; + successCount++; + + // 判断是否还有下一页 + if (result.length < limit) { + hasMore = false; + } else { + page++; + } + } catch (error) { + // 忽略单页失败,继续处理下一页 + console.error(`第${page}页同步失败:`, error); + failCount++; + + // 如果连续失败太多,停止同步 + if (failCount >= 3) { + console.warn("连续失败次数过多,停止同步"); + break; + } + + // 继续下一页 page++; + if (page > 100) { + // 防止无限循环 + hasMore = false; + } } } - // 分离好友和群聊数据 - const friends = allMessages.filter( - (msg: any) => msg.dataType === "friend" || !msg.chatroomId, - ); - const groups = allMessages - .filter((msg: any) => msg.dataType === "group" || msg.chatroomId) - .map((msg: any) => { - // 确保群聊数据包含正确的头像字段 - // 如果接口返回的是 avatar 字段,需要映射到 chatroomAvatar - return { - ...msg, - chatroomAvatar: msg.chatroomAvatar || msg.avatar || "", - }; - }); - - // 执行增量同步 - const syncResult = await MessageManager.syncSessions(currentUserId, { - friends, - groups, - }); - - // 同步后验证数据 - const verifySession = await MessageManager.getUserSessions(currentUserId); - console.log("同步后的会话数据示例:", verifySession[0]); - console.log( - `会话同步完成: 新增${syncResult.added}, 更新${syncResult.updated}, 删除${syncResult.deleted}`, + `会话同步完成: 成功${successCount}页, 失败${failCount}页, 共处理${totalProcessed}条数据`, ); - - // 会话管理器会在有变更时触发订阅回调 } catch (error) { console.error("同步服务器数据失败:", error); + } finally { + setSyncing(false); // 同步完成,更新状态栏 + } + }; + + // 手动触发同步的函数 + const handleManualSync = async () => { + if (syncing) return; // 如果正在同步,不重复触发 + setSyncing(true); + try { + await syncWithServer(); + } catch (error) { + console.error("手动同步失败:", error); + } finally { + setSyncing(false); } }; @@ -394,8 +420,6 @@ const MessageList: React.FC = () => { const requestId = ++loadRequestRef.current; const initializeSessions = async () => { - setLoading(true); - try { const cachedSessions = await MessageManager.getUserSessions(currentUserId); @@ -404,6 +428,7 @@ const MessageList: React.FC = () => { return; } + // 有缓存数据立即显示 if (cachedSessions.length > 0) { setSessionState(cachedSessions); } @@ -411,12 +436,18 @@ const MessageList: React.FC = () => { const needsFullSync = cachedSessions.length === 0 || !hasLoadedOnce; if (needsFullSync) { - await syncWithServer(); - if (isCancelled || loadRequestRef.current !== requestId) { - return; - } - setHasLoadedOnce(true); + // 不等待同步完成,让它在后台进行,第一页数据同步后会立即更新UI + syncWithServer() + .then(() => { + if (!isCancelled && loadRequestRef.current === requestId) { + setHasLoadedOnce(true); + } + }) + .catch(error => { + console.error("同步失败:", error); + }); } else { + // 后台同步 syncWithServer().catch(error => { console.error("后台同步失败:", error); }); @@ -425,10 +456,6 @@ const MessageList: React.FC = () => { if (!isCancelled) { console.error("初始化会话列表失败:", error); } - } finally { - if (!isCancelled && loadRequestRef.current === requestId) { - setLoading(false); - } } }; @@ -526,13 +553,11 @@ const MessageList: React.FC = () => { // 渲染完毕后自动点击第一个聊天记录 useEffect(() => { // 只在以下条件满足时自动点击: - // 1. 不在加载状态 - // 2. 有过滤后的会话列表 - // 3. 当前没有选中的联系人 - // 4. 还没有自动点击过 - // 5. 不在搜索状态(避免搜索时自动切换) + // 1. 有过滤后的会话列表 + // 2. 当前没有选中的联系人 + // 3. 还没有自动点击过 + // 4. 不在搜索状态(避免搜索时自动切换) if ( - !loading && filteredSessions.length > 0 && !currentContract && !autoClickRef.current && @@ -550,7 +575,7 @@ const MessageList: React.FC = () => { return () => clearTimeout(timer); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [loading, filteredSessions, currentContract, searchKeyword]); + }, [filteredSessions, currentContract, searchKeyword]); // ==================== WebSocket消息处理 ==================== @@ -796,156 +821,146 @@ const MessageList: React.FC = () => { } }; - // 渲染骨架屏 - const renderSkeleton = () => ( -
    - {Array(8) - .fill(null) - .map((_, index) => ( -
    - -
    -
    - - -
    - -
    -
    - ))} -
    - ); - - // 渲染加载中状态(带旋转动画) - const renderLoading = () => ( -
    - -
    {renderSkeleton()}
    -
    + // 渲染同步状态提示栏 + const renderSyncStatusBar = () => ( +
    + {syncing ? ( +
    + + 同步中... + +
    + ) : ( +
    + + + 同步完成 + + + 同步 + +
    + )}
    ); return (
    - {loading ? ( - // 加载状态:显示加载动画和骨架屏 - renderLoading() - ) : ( - <> - ( - onContactClick(session)} - onContextMenu={e => handleContextMenu(e, session)} - > -
    - - - ) : ( - - ) - } - /> - -
    -
    -
    - {session.conRemark || - session.nickname || - session.wechatId} -
    -
    - {formatWechatTime(session?.lastUpdateTime)} -
    -
    -
    - {messageFilter(session.content)} -
    + {/* 同步状态提示栏 */} + {renderSyncStatusBar()} + + ( + onContactClick(session)} + onContextMenu={e => handleContextMenu(e, session)} + > +
    + + + ) : ( + + ) + } + /> + +
    +
    +
    + {session.conRemark || session.nickname || session.wechatId} +
    +
    + {formatWechatTime(session?.lastUpdateTime)}
    - - )} - /> - - {/* 右键菜单 */} - {contextMenu.visible && contextMenu.session && ( -
    -
    handleTogglePin(contextMenu.session!)} - > - - {(contextMenu.session.config as any)?.top ? "取消置顶" : "置顶"} -
    -
    handleEditRemark(contextMenu.session!)} - > - - 修改备注 -
    -
    handleDelete(contextMenu.session!)} - > - - 删除 +
    + {messageFilter(session.content)} +
    - )} + + )} + locale={{ + emptyText: + filteredSessions.length === 0 && !syncing ? "暂无会话" : null, + }} + /> - {/* 修改备注Modal */} - - setEditRemarkModal({ - visible: false, - session: null, - remark: "", - }) - } - okText="保存" - cancelText="取消" + {/* 右键菜单 */} + {contextMenu.visible && contextMenu.session && ( +
    +
    handleTogglePin(contextMenu.session!)} > - - setEditRemarkModal(prev => ({ - ...prev, - remark: e.target.value, - })) - } - placeholder="请输入备注" - maxLength={20} - /> - - + + {(contextMenu.session.config as any)?.top ? "取消置顶" : "置顶"} +
    +
    handleEditRemark(contextMenu.session!)} + > + + 修改备注 +
    +
    handleDelete(contextMenu.session!)} + > + + 删除 +
    +
    )} + + {/* 修改备注Modal */} + + setEditRemarkModal({ + visible: false, + session: null, + remark: "", + }) + } + okText="保存" + cancelText="取消" + > + + setEditRemarkModal(prev => ({ + ...prev, + remark: e.target.value, + })) + } + placeholder="请输入备注" + maxLength={20} + /> +
    ); }; diff --git a/Touchkebao/src/utils/dbAction/message.ts b/Touchkebao/src/utils/dbAction/message.ts index 6c5346709..0d8f71d33 100644 --- a/Touchkebao/src/utils/dbAction/message.ts +++ b/Touchkebao/src/utils/dbAction/message.ts @@ -259,6 +259,8 @@ export class MessageManager { * 增量同步会话数据 * @param userId 用户ID * @param serverData 服务器数据 + * @param options 同步选项 + * @param options.skipDelete 是否跳过删除检查(用于分页增量同步) * @returns 同步结果统计 */ static async syncSessions( @@ -267,6 +269,9 @@ export class MessageManager { friends?: ContractData[]; groups?: weChatGroup[]; }, + options?: { + skipDelete?: boolean; // 是否跳过删除检查(用于分页增量同步) + }, ): Promise<{ added: number; updated: number; @@ -323,10 +328,12 @@ export class MessageManager { } } - // 检查删除 - for (const localSession of localSessions) { - if (!serverSessionMap.has(localSession.serverId)) { - toDelete.push(localSession.serverId); + // 检查删除(仅在非增量同步模式下执行) + if (!options?.skipDelete) { + for (const localSession of localSessions) { + if (!serverSessionMap.has(localSession.serverId)) { + toDelete.push(localSession.serverId); + } } } From e4a8975baa2bc8ab2d5a125c0a568f1561b217db Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Fri, 28 Nov 2025 16:44:08 +0800 Subject: [PATCH 44/49] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mobile/scenarios/plan/list/index.tsx | 32 +++++++++++-------- .../plan/new/steps/BasicSettings.tsx | 4 ++- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/Cunkebao/src/pages/mobile/scenarios/plan/list/index.tsx b/Cunkebao/src/pages/mobile/scenarios/plan/list/index.tsx index d6da5a659..59613c07e 100644 --- a/Cunkebao/src/pages/mobile/scenarios/plan/list/index.tsx +++ b/Cunkebao/src/pages/mobile/scenarios/plan/list/index.tsx @@ -369,13 +369,15 @@ const ScenarioList: React.FC = () => { backFn={() => navigate("/scenarios")} title={scenarioName || ""} right={ - + scenarioId !== "10" ? ( + + ) : null } /> @@ -424,13 +426,15 @@ const ScenarioList: React.FC = () => {
    {searchTerm ? "没有找到匹配的计划" : "暂无计划"}
    - + {scenarioId !== "10" && ( + + )}
    ) : ( <> diff --git a/Cunkebao/src/pages/mobile/scenarios/plan/new/steps/BasicSettings.tsx b/Cunkebao/src/pages/mobile/scenarios/plan/new/steps/BasicSettings.tsx index 76d460c85..6c61b6da3 100644 --- a/Cunkebao/src/pages/mobile/scenarios/plan/new/steps/BasicSettings.tsx +++ b/Cunkebao/src/pages/mobile/scenarios/plan/new/steps/BasicSettings.tsx @@ -242,7 +242,9 @@ const BasicSettings: React.FC = ({
    ) : (
    - {sceneList.map(scene => { + {sceneList + .filter(scene => scene.id !== 10) + .map(scene => { const selected = formData.scenario === scene.id; return (