# 阿里询盘管理后台 设计文档 - 日期: 2026-07-13(修订 v3:纳入二次评审发现) - 状态: 修订完成,待用户审查 - 关联: 现有 Chrome 插件位于 `d:\alibb\` - 修订说明: v2 修正了 spec review 发现的 4 个致命问题(host_permissions 运行时不可改 / messageId 丢失 / 队列存储布局 / 淘汰丢数据)与 8 个重要问题(询盘时间源、批量写入、alarms 周期、时区、索引、HTTP 推送、behavior 类型、外键可空)。 - v3 修正二次评审发现:① **早返回逻辑反转**(失败批次永不重试 → inquiries 表新增 `has_failed_batches` 列 + `messages_count` 改存实际插入数);② **IndexedDB 写入上下文**(content script 的 IDB 属页面源,须由 SW 落盘)+ 双存储一致性写入顺序;③ **MySQL 8 默认 max_allowed_packet 是 64MB 非 4MB**;④ 批量行数 1000/500 统一为 **500**;⑤ direction ENUM 补 `'system'`;⑥ §8.6 补 behavior null 强制转换 / country 来源替换 / translation 双重 JSON.parse。 ## 1. 概述 ### 1.1 目标 把现有 Chrome 插件(抓取阿里国际站询盘/客户/聊天)的「本地 CSV 导出」升级为完整链路: > 插件自动推送 → 自建后端 → MySQL → 管理后台(查看 / 筛选 / 导出 / 统计) ### 1.2 使用模式(已确认) - **主账号统一抓取**:登录阿里主管理员账号(可看到所有业务员的询盘),**一个人操作插件**一次性抓取全部询盘。 - **后台单管理员**:现阶段就一个管理员账号,统览全部数据。 - 询盘记录里的 `manager`(负责人)字段天然标识业务员,后台按此字段统计/筛选,**不需要业务员各自登录或各自推送**。 - **数据时效 = 上次手动抓取的快照**:插件靠用户在阿里页点「开始抓取」触发,**不是实时同步**;后台看到的是最近一次抓取的数据。后续可考虑定时自动抓(需浏览器保持阿里页打开)。 ### 1.3 MVP 范围 **纳入**:插件自动推送;后端入库(三实体幂等);管理后台(列表/详情/导出/统计)。 **不纳入(后续)**:多业务员登录/权限;跟进管理/CRM;客户合并/标签;聊天全文搜索;复杂报表;增量推送;**后台数据编辑/删除(MVP 只读,错误数据需重抓或直接改库)**。 ## 2. 背景:现有插件能力 `d:\alibb\` 的 Chrome MV3 插件已实现并验证:列表/详情/行为数据抓取;聊天 API 直拉(`querySummary.htm` → `getOpMessages.htm` 游标循环,单询盘实测 8000+ 条);chrome.storage 状态持久化。 **本方案对抓取主流程增量扩展,但必须改动消息映射**: - 现有 `apiMsgToChat()` 只产出 `{sender, time, original, translation, direction}`,**丢弃了 messageId 和毫秒时间戳**。 - 本方案要求扩展为:`{messageId, sendTime(ISO UTC), sender, content, direction, translation}`,并从 `querySummary.htm` 响应顺带提取询盘权威时间 `gmtCreate`/`gmtModified`、买家国家等。 - 因此 §8 的插件改造**不是纯增量**,包含对 `apiMsgToChat` 与 `fetchChatByApi` 的字段扩展。 ## 3. 需求 ### 3.1 功能需求 | ID | 需求 | |---|---| | F1 | 插件每抓完一个询盘(主体 + 全量聊天)→ 自动 POST 到后端 | | F2 | 后端按 客户/询盘/聊天 三表存储,重复推送幂等无副作用 | | F3 | 后台列表:分页 + 筛选(国家/业务员/状态/时间范围)+ 关键词搜索(询价单号/买家名/公司)+ 排序 | | F4 | 后台详情:客户基本信息 + 行为数据卡片 + 完整聊天(虚拟滚动,左右气泡按方向) | | F5 | 导出:按当前筛选导出 CSV(**仅列表字段,不含聊天**;聊天在详情页单独导出) | | F6 | 统计:总数、按国家 Top10、按业务员分布、按月趋势(基于询盘 gmtCreate) | ### 3.2 非功能需求 | ID | 需求 | |---|---| | NF1 可靠 | 推送不丢(队列持久化 + 重试 + 淘汰策略不丢 pending);入库幂等(唯一键 upsert) | | NF2 简单好维护 | TypeScript;Prisma schema 单一数据定义源;前后端共享类型 | | NF3 稳定 | 单操作者低并发;Docker 部署;MySQL 每日备份 | | NF4 安全 | 正式 HTTPS;插件 Bearer token;管理员 JWT(带过期 + 刷新) | ## 4. 技术栈 | 层 | 选型 | 理由 | |---|---|---| | 后端 | **Node.js 18 + TypeScript + Express + Prisma** | 和插件同语言;Prisma schema 直观;TS 类型安全 | | 数据库 | **MySQL 8** | 事务 + 唯一约束保证幂等 | | 前端 | **Vue3 + TS + Element Plus + Vite + Pinia + Vue Router** | 管理后台组件成熟 | | 部署 | **Docker Compose + Nginx** | 一键起 | | 插件 | 现有 MV3 + 推送模块(chrome.alarms + 队列 + optional permissions) | 见 §8 | **决策记录:为何 Node 不选 Python**:用户优先级是"简单 + 好维护 + 稳定 + 好实现(主导搭建)",Node 全栈与插件同语言、可复用数据转换逻辑、搭建排错最快;Python 数据生态优势对 MVP 简单统计是过度设计,且引入前后端两语言复杂度。后续复杂分析可加 Python 微服务。 ## 5. 架构与数据流 ### 5.1 组件拓扑 ``` [操作者电脑 · 登录阿里主账号] Chrome 插件 ① 抓取(保留) → 扩展消息映射(messageId/sendTime ISO/gmtCreate) ② 每抓完一条 → 经 SW 写入【待推队列】(payload 存扩展 IndexedDB,queue_index 存 chrome.storage.local;content script 不能直接写扩展 IDB,见 §8.2) ③ chrome.alarms(1 分钟)唤醒 SW → 消费队列 → POST /api/ingest ↓ 失败指数退避重试,成功才出队 [Linux 服务器 · Docker Compose] Nginx(client_max_body_size 50m)─┬─ /api/* → Backend └─ /* → Frontend 静态 MySQL 8(数据卷 + 每日 mysqldump) ↓ 幂等写入(唯一键: inquiry_id / customer_id / message_id) [管理员浏览器] 登录(JWT)→ 列表/详情/导出/统计 ``` ### 5.2 数据流时序(/api/ingest 处理) 1. zod 校验 payload 结构。 2. **幂等早返回**:若 `inquiry_id` 已存在 **且 `has_failed_batches=false`** 且 `messages_count` + `last_send_time` 均未变 → 直接 200 返回,跳过消息批写(避免无效重写几千条)。`has_failed_batches` 必须参与判断,否则上次失败的批次会被早返回永远跳过(详见 R6)。 3. 一个事务 upsert customer + inquiry(customer_id 可空时跳过 customer)。 4. 聊天消息用**原生批量** `INSERT INTO chat_messages (...) VALUES (...),(...)... ON DUPLICATE KEY UPDATE content=VALUES(content), send_time=VALUES(send_time), ...`,**分批**(每批 500 行,见 §10)执行,每批独立事务;失败批次记入失败列表,不回滚整条询盘。累计 `actualInserted` = 各批实际落盘行数之和。 5. 更新 inquiry:`messages_count = actualInserted`(实际插入数,**非 payload 长度**)、`last_send_time = max(sendTime)`、`has_failed_batches = (失败批次>0)`。用实际插入数才能让"失败重推时 count 不一致 → 不早返回 → 失败批次自然补写"(v2 用 payload 长度是逻辑反转)。 6. 返回 `{ok, inserted: actualInserted, failedBatches}`。 ## 6. 数据模型 四张表。关系:customers 1→多 inquiries 1→多 chat_messages。 ### 6.1 customers(客户) | 字段 | 类型 | 说明 | |---|---|---| | id | BIGINT PK auto | | | customer_id | VARCHAR(64) **UNIQUE** | 阿里 customerId(加密串) | | name | VARCHAR(128) | | | country | VARCHAR(64) **INDEX** | 国家(统计/筛选用) | | email / company | VARCHAR | | | register_time | DATETIME NULL | UTC,可空 | | avatar_url | VARCHAR(512) | | | buyer_tags / buyer_features | VARCHAR(255) | 逗号分隔 | | behavior_product_views / valid_inquiries / valid_rfq / login_days / spam_inquiries | INT **NULL** | 可空(抓不到时 null,不存 0 以区分"无数据"与"真为 0") | | behavior_top_industry | VARCHAR(255) | | | created_at / updated_at | DATETIME | UTC | ### 6.2 inquiries(询盘) | 字段 | 类型 | 说明 | |---|---|---| | id | BIGINT PK auto | | | inquiry_id | VARCHAR(32) **UNIQUE** | 询价单号 | | customer_id | VARCHAR(64) **NULL** INDEX | FK→customers.customer_id,**可空**(抓不到 customerId 时留空) | | manager | VARCHAR(128) **INDEX** | 业务员名 | | create_time | DATETIME | UTC,源自 API `gmtCreate` | | update_time | DATETIME | UTC,源自 API `gmtModified`(缺失回落 gmtCreate) | | status | VARCHAR(64) | | | msg_preview | VARCHAR(512) | | | is_flagged | BOOLEAN | | | messages_count | INT DEFAULT 0 | 冗余,供幂等早返回 | | last_send_time | DATETIME NULL | 冗余,供幂等早返回 | | has_failed_batches | BOOLEAN DEFAULT FALSE | 上次推送是否有失败批次;为真则关闭早返回、强制重试(v3 新增) | | created_at / updated_at | DATETIME | UTC | ### 6.3 chat_messages(聊天明细) | 字段 | 类型 | 说明 | |---|---|---| | id | BIGINT PK auto | | | message_id | VARCHAR(64) **UNIQUE** | 阿里 messageId(幂等键,全局唯一) | | inquiry_id | VARCHAR(32) **INDEX** FK→inquiries.inquiry_id | | | sender | VARCHAR(128) | | | direction | ENUM('incoming','outgoing','system') | 插件 apiMsgToChat 在 messageType 非 send/rec 时兜底产出 'system'(样本暂未出现,保留以防插入失败) | | send_time | DATETIME | UTC,源自毫秒戳 | | content | **MEDIUMTEXT** | 原文(已解码实体)。卡片消息体量待实测确认(样本中为 URL ~500 字符);MEDIUMTEXT 上限 16MB 留足余量 | | translation | TEXT | 翻译,从 `extInfo.icbuData.extend.extParams.inputTranslateInfo.translate` 提取(可空) | | created_at | DATETIME | UTC | > **索引**:`UNIQUE(message_id)`、`INDEX(inquiry_id, send_time, message_id)`(游标分页复合键)。 ### 6.4 users(管理员账号) | 字段 | 类型 | |---|---| | id PK / username UNIQUE / password_hash (bcrypt) / created_at | > **bootstrap**:后端启动时若 users 表空,根据 `ADMIN_USERNAME` / `ADMIN_PASSWORD` 环境变量 seed 一条记录。推送 token 写 `.env`(`INGEST_TOKEN`),不建表。 ### 6.5 时间来源与时区(修订) - **询盘 create_time / update_time**:用 `querySummary.htm` 返回的 `gmtCreate` / `gmtModified` 毫秒戳 → `new Date(ms).toISOString()`。**不再用"最早消息兜底"**(语义错:询盘创建 ≠ 首条消息)。列表页那个只有时分的文本时间不入库,仅必要时回退展示。 - **消息 send_time**:用 API 毫秒戳 → ISO UTC。 - 所有 DATETIME 存 UTC;前端用 dayjs 转本地时区展示。 ## 7. API 设计 基地址 `/api`,JSON。 | 方法 | 路径 | 鉴权 | 说明 | |---|---|---|---| | POST | `/api/ingest` | Bearer token | 插件推送。zod 校验。限流 60/min/token。幂等早返回(见 §5.2) | | POST | `/api/auth/login` | - | `{username,password}` → `{token, expiresIn}` | | POST | `/api/auth/refresh` | JWT | 刷新 token | | GET | `/api/inquiries` | JWT | 列表,query: page,size,country,manager,status,dateFrom,dateTo,q(搜 inquiry_id/msg_preview/customer.name),sort | | GET | `/api/inquiries/:id` | JWT | 详情 + 客户 + 行为 | | GET | `/api/inquiries/:id/messages` | JWT | **游标分页**:query `afterSendTime` + `afterMessageId`(默认空=从最早)、size;`WHERE inquiry_id=? AND (send_time, message_id) > (?, ?) ORDER BY send_time, message_id LIMIT ?`(复合游标,避免同时间戳消息丢失) | | GET | `/api/inquiries/:id/messages/export` | JWT | 单询盘聊天导出(stream) | | GET | `/api/stats/overview` | JWT | 总数/国家 Top10/业务员/月趋势 | | GET | `/api/export` | JWT | **仅列表字段**(不含聊天),按筛选 stream 导出 | ### 7.1 `/api/ingest` payload 结构 ```jsonc { "inquiry": { "inquiryId": "13776617920", "manager": "cara Hu", "createTime": "2026-03-13T10:11:50Z", // 来自 gmtCreate, ISO UTC "updateTime": "2026-07-13T10:11:50Z", // 来自 gmtModified "status": "洽谈中", "msgPreview": "...", "isFlagged": false }, "customer": { // customerId 为空时整个 customer 省略 "customerId": "259b4e6f...", "name": "Carlos Loayza Marin", "country": "PE", "email": null, "company": "World Shop", "registerTime": "2025-06-23T00:00:00Z", "avatarUrl": "...", "buyerTags": "TM沟通", "buyerFeatures": "...", "behavior": { "productViews": null, "validInquiries": null, "validRfq": 39 } // 捕不到为 null }, "messages": [ { "messageId": "4198482017852", "sendTime": "2026-07-13T02:11:50Z", "sender": "cara Hu", "direction": "outgoing", "content": "hello...", "translation": "你好..." } ] } ``` - 空字符串数字字段在插件侧 coerce 为 null;后端 zod 接受 `number | null`。 ## 8. 插件改造(在 `d:\alibb\` 基础上) ### 8.1 配置页 popup 增「后端设置」:服务器 URL、Token、启用开关。存 chrome.storage.local。 **首次保存 URL 时**,调用 `chrome.permissions.request({origins:[serverUrl]})`(需在用户点击手势中触发,会弹 Chrome 授权框)——因为 host_permissions 运行时不可改,见 §8.5。 ### 8.2 待推队列(修订:IndexedDB 存 payload + 不丢 pending) - **payload 默认存扩展 IndexedDB**(`idb` 库),每条一个 record(key=id);chrome.storage.local 只存轻量 `queue_index`(有序 id 列表)+ 每项元数据 `{id, retries, nextRetry, status}`。避免 chrome.storage.local 在 GB 级的读写性能问题。 - **写入上下文(关键,v3)**:content script 的 `indexedDB` 是**页面源**(`message.alibaba.com`)的,不是扩展的;只有 service worker / popup / options 才能访问扩展自己的 IndexedDB。因此正确链路是:**content.js 抓完一条 → `chrome.runtime.sendMessage` 把 payload 传给 SW → SW 落盘到扩展 IndexedDB**。绝不能让 content.js 直接写 IDB(会写到页面的库里)。 - **双存储一致性(v3)**:IndexedDB 与 chrome.storage 之间无跨库事务。写入顺序固定为**先写 IDB payload,再追加 queue_index**(保证 index 永不引用不存在的 payload);删除反之(先标 success → 删 IDB → 移 index)。SW 启动时跑一次 GC,清理 IDB 中无 index 引用的孤儿 payload。 - manifest 声明 `"unlimited_storage"`(IndexedDB 容量也受其约束)。 - **不丢未推数据**:推送成功 → 立即从 IndexedDB + queue_index 删除该队列项;`pending/failed` 永不淘汰。触顶(默认 500 条 pending)→ **暂停抓取**(content.js 每抓一条前向 SW 查队列深度)+ popup 红字提示"队列拥堵,请检查后端"。 ### 8.3 推送 worker(修订:alarms 1 分钟) - 用 **`chrome.alarms`**(`periodInMinutes: 1`),**不用 setInterval**(MV3 SW 会休眠)。 - 注:打包/正式版 alarms 最短 1 分钟(开发版可临时 30s)。 - alarm 触发 → 取最多 N(默认 3)条 pending 且到 nextRetry 的项 → POST → 成功则删除该队列项 / 失败 `retries++`、`nextRetry = now + min(2^retries, 3600)s`,超 8 次标 failed 保留。 - **吞吐量(v3)**:1 分钟 × 3 条 = 180 条/小时 ≈ 4320 条/天。全量几千条要好几天才推完,这是异步后蹲、与"慢抓"主题一致;**抓完 ≠ 入库**,popup 的"已推/待推"计数要让使用者意识到。瓶颈在 worker 的 3/min,服务器限流 60/min(§7)永远用不到;日后想加速可提高每 tick 的 N。 ### 8.4 状态展示 popup 显示「已推 N / 待推 M / 失败 K」,失败可查看/重试。 ### 8.5 manifest 改动(修订:optional permissions) ```jsonc { "permissions": ["storage", "activeTab", "alarms", "unlimitedStorage"], "optional_host_permissions": ["http://*/*", "https://*/*"], "host_permissions": [ "*://message.alibaba.com/*", "*://alicrm.alibaba.com/*", "*://onetalk.alibaba.com/*" ] } ``` - 后端服务器地址**不写死在 host_permissions**(运行时不可改,且占位符非法会拒载)。 - 用 `optional_host_permissions: ["http://*/*","https://*/*"]` + 用户在配置页通过 `chrome.permissions.request({origins:[serverUrl]})` **运行时授权**具体后端地址。 - 替代:若插件私有/不上架,可直接 `host_permissions: ["*://*/*"]` 一劳永逸(但上架审核会问)。 ### 8.6 抓取侧字段扩展(修订) - `fetchChatByApi()` 在调 `querySummary.htm` 后,**提取 `gmtCreate`/`gmtModified`/`country` 等字段**并随 payload 返回。**country 改用 `querySummary.contact.countryCode`**(样本中 `"countryCode":"PE"`,比 DOM 的 `detailCountry` 可靠),是**替换**而非新增字段。 - `apiMsgToChat()` 扩展产出 `{messageId: String(m.messageId), sendTime: new Date(m.sendTime).toISOString(), sender, content, direction, translation}`,其中 translation 从 `m.extInfo.icbuData.extend.extParams.inputTranslateInfo.translate` 解析(失败留空)。**注意双重 JSON.parse(v3)**:`extend` 本身是 JSON 字符串,`extParams.inputTranslateInfo` 又是嵌套 JSON 字符串,需 `JSON.parse(JSON.parse(extend).extParams.inputTranslateInfo).translate` 两层解析,直接对象访问会拿到 undefined。 - **behavior 数字字段强制转 null(v3)**:`productViews/validInquiries/validRfq/loginDays/spamInquiries` 在 content.js 现为字符串('' 或数字串),payload 序列化时空串与抓不到的值都要 coerce 为 `null`,匹配后端 zod 的 `number | null`。 ## 9. 后台前端 | 路由 | 页面 | 要点 | |---|---|---| | `/login` | 登录 | | | `/inquiries` | 列表 | Element Plus 表格 + 筛选栏(国家/业务员/状态/时间)+ 搜索 + 排序 + 分页 + 导出按钮 | | `/inquiries/:id` | 详情 | 客户信息+行为卡片 + 完整聊天(虚拟滚动,游标加载) | | `/stats` | 统计 | 数字卡片 + 国家 Top10 饼图 + 业务员柱状图 + 月趋势折线(ECharts) | 性能:聊天用虚拟滚动(vue-virtual-scroller);列表分页默认 20/页。 ## 10. 可靠性与安全 | # | 措施 | |---|---| | 幂等 | 唯一键 upsert + 早返回(inquiry 存在 **且 has_failed_batches=false** 且 count/lastSendTime 未变则跳过) | | 队列不丢 | 独立 key 持久化 + 只淘汰 success + 触顶暂停抓取 | | 批量写入 | 原生 `INSERT ON DUPLICATE KEY` 分批 500 行/批(见 §5.2),独立事务,失败批次隔离 | | 鉴权 | 插件 Bearer token(env);管理员 JWT(access 短期 + refresh) | | 密码 | bcrypt | | CORS | `/api/ingest` 用 token 鉴权,`Access-Control-Allow-Origin: *` 不做 origin 校验;后台 API 限前端域名 | | XSS | Vue 默认转义,禁用 v-html 渲染用户数据 | | body limit | Express `limit:50mb`;**nginx `client_max_body_size 50m`** | | MySQL | `max_allowed_packet` 设为 128MB(**MySQL 8 服务端默认 64MB,非 4MB**;4MB 是老版本/客户端库默认值,易误传);部署前 `SHOW VARIABLES LIKE 'max_allowed_packet'` 实测确认。单批 500 行(含 MEDIUMTEXT 时 500 行 × 极端 64KB ≈ 32MB,留余量) | | 备份 | 每日 mysqldump → gzip,保留 14 天,异地复制 | ## 11. 部署 ### 11.1 docker-compose(结构) ```yaml services: mysql: # mysql:8, 卷, healthcheck, max_allowed_packet 调大 backend: # Node API, depends_on mysql, .env nginx: # 反代 + 静态 + client_max_body_size 50m; 正式加 HTTPS cron: # 备份 sidecar(或宿主 crontab),每日 mysqldump ``` ### 11.2 测试环境(无域名) - 公网 IP + 端口,后端 HTTP。 - **扩展 SW 的 fetch 起源于 `chrome-extension://` 上下文,不受网页 mixed-content 规则约束**,只要 optional_host_permissions 授权了该 HTTP URL,推送正常。 - **私网 IP(192.168/10.x)注意**:Chrome 的 Private Network Access 会要求预检;优先用公网 IP 测试。 ### 11.3 正式环境(有域名) - 域名 + Nginx + Let's Encrypt(自动续期)+ 全程 HTTPS。 ## 12. 项目结构 ``` d:\alibb\ # 现有插件(增量改造:推送模块 + 字段扩展) d:\alibb-server\ backend\ prisma\schema.prisma + src\{routes,services,middleware,utils} + Dockerfile frontend\ Vue3 工程 + Dockerfile docker-compose.yml .env.example (DATABASE_URL / INGEST_TOKEN / JWT_SECRET / JWT_REFRESH_SECRET / ADMIN_USERNAME / ADMIN_PASSWORD) ``` ## 13. 分期 **MVP**:后端骨架(Express+Prisma+MySQL)+ `/api/ingest`(校验+幂等+批量)+ auth/JWT + 列表/详情/消息/统计/导出 API + 前端四件套 + 插件推送模块 + Docker + 测试部署 + 备份。 **后续**:增量推送、聊天全文搜索、跟进管理、客户合并/标签、复杂报表、多管理员、存量 CSV 导入。 ## 14. 风险与待确认 - **R1 payload 体量**:单询盘几千条约 2-5MB;body limit 50MB + nginx 50m + max_allowed_packet 64MB。>1万条极端询盘 MVP 后改分批推送。 - **R2 增量**:MVP 全量重推 + 早返回优化(未变则跳过);日抓量大后做真增量(插件记 last sendTime)。 - **R3 manager 重名**:字符串统计,同名合并;后续加 employees 表。 - **R4 测试环境网络**:公网 IP+HTTP 推送无混合内容问题(扩展 fetch 独立上下文);私网 IP 需注意 Private Network Access 预检。已删除"自签 HTTPS"复杂方案。 - **R5 customerId 缺失**:customer_id 可空,无 customerId 的询盘只存 inquiry+messages,customer 留空。 - **R6 早返回的边界**:幂等早返回条件为 `has_failed_batches=false` 且 `messages_count + last_send_time` 未变。 - **失败批次重试(v3 修正)**:早返回已加入 `has_failed_batches`——上次有批次失败时强制不走早返回,确保失败批次被补写。inquiries 表新增 `has_failed_batches` 列;`messages_count` 存实际插入数(payload 长度会让重推 count 恒定一致,反而触发早返回——这是 v2 的逻辑反转)。 - **内容修订**:同 messageId 内容(如 translation)被上游修订时,早返回仍会跳过更新。MVP 接受(重推主场景是新增消息)。 - **R7 首次全量回填耗时**:首次把现有几千询盘全抓入库,按"每询盘几千条聊天 × API 拉取 + 推送"估算需 **数小时**,且**浏览器必须一直打开阿里页**不能关。要求插件支持**分批抓取 + 可中断续抓**(基于 queue/inquiryId 进度持久化,中断后重开继续,而非重头来)。日常增量抓取不受此影响。