22 KiB
22 KiB
模块导航与拆分规范
四阶段拆分已落地:am.py 保留命令及 WSGI 兼容入口,应用工厂和业务代码位于 logistics_chat/,页面样式与脚本位于 static/。本文描述当前结构及后续结构调整约定。
先定位,再阅读
从 AGENTS.md 选择文件,以函数、常量、接口为锚点,搜索后只读取相关实现和调用方。模块移动时更新索引,不保存易失效的行号。默认遵守 Git 忽略规则,排除附件、数据库、日志、备份、虚拟环境和工作草稿。
rg --files
rg -n -F 'calc_freight' logistics_chat/pricing.py
rg -n -F '/api/human/' logistics_chat/routes static/js examples
rg -n '^(def |class | def )' scripts tests -g '*.py'
PowerShell 下把目录和 -g '*.py' 分开传给 rg。报价字典可能整行很长,搜索时加 --max-columns 200 --max-columns-preview,避免输出整份数据。
当前模块索引
| 业务范围 | 实际文件与搜索锚点 | 联动位置 |
|---|---|---|
| 应用入口与配置 | am.py:main、延迟访问的 app;logistics_chat/__init__.py:create_app;config.py:default_config、get_setting;runtime.py:serve_app |
start.bat、.env.example |
| Jenkins 灰度部署 | Jenkinsfile、Dockerfile;deploy/gray_config.py:GrayConfig;publish-gray.sh、activate-gray.sh、prepare-gray-tls.sh、renew-gray-tls.sh |
部署指南;独立持久化数据、证书和回环端口;wsgi.py 提供容器内路径 |
| 请求钩子 | logistics_chat/hooks.py:rate_limit、no_cache_html |
工厂注册钩子;限流状态在 app.extensions['rate_hits'] |
| SQLite 与维护 | logistics_chat/db.py:connect、close_connections、init_db、backup_db、save_message、last_user_question、load_context |
维护 CLI 在 scripts/;旧会话自动清理仍未启用 |
| 历史接口 | logistics_chat/history_service.py:new_chat、get_history、get_messages、del_history、clear_all;对应 routes/history.py |
static/js/chat.js:ensureConv、loadHistory、openConversation |
| 知识检索 | logistics_chat/knowledge.py:knowledge_list、search_knowledge |
knowledge.md 与代码同步,程序不读取该文档 |
| 运费与附加费 | logistics_chat/pricing.py:RATES、parse_shipping_intent、dims_to_cm、pick_rate、calc_freight、freight_reply、calc_warehouse_card、detect_surcharge、check_island |
logistics_chat/rates_extra.py 为唯一扩展数据源,根目录同名文件仅兼容转发 |
| 卡片协议 | logistics_chat/tracking.py:CARD_PREFIX、card_to_text;旧 extract_tracking_no、build_track 仅兼容旧导入 |
static/js/chat.js:fillTrackCard、addTrackCard;新订单卡片由 order_data.py 提供,历史卡片保留兼容渲染 |
| 订单测试表 | logistics_chat/order_data.py:init_order_table、import_records、lookup_order、answer_query |
SQLite order_test_data 为可编辑快照;按订单号、入仓码、Shipment ID 精确查表。将来 MCP 在取数层替换 lookup_order,不让模型拼 SQL |
| AI 与 SSE | logistics_chat/chat_service.py:create_chat_stream、sse;ai_client.py:post_completion、compress_answer、gen_followups |
routes/chat.py:stream_chat;客户端 doSend、renderError、renderFollowups |
| 人工会话 | logistics_chat/human_service.py:human_*(含转接、备注、撤回);对应 routes/human.py |
输入状态在 app.extensions['human_typing'];客户 startHumanPoll;客服 loadQueue、poll、sendReply;人工申请每天 8:00–22:00 |
| 附件 | logistics_chat/file_service.py:upload_file、cleanup_uploads;routes/files.py:uploaded_file |
扩展名/大小/目录由配置提供;客户端 addMsg,客服端 appendMsg |
| 登录、统计、订单、留言、导出 | logistics_chat/business_service.py:admin_login、admin_stats、my_orders、add_ticket、list_tickets、export_chat |
routes/admin.py、routes/business.py;登录页 static/js/admin_login.js |
| 页面 | templates/index.html、templates/admin.html、templates/admin_login.html |
先加载共享 static/css/theme.css;客户与客服脚本先加载 support.js(表情、通知、评价);客服再加载 admin_tools.js(备注、转接、历史搜索),最后加载 admin.js;保留全局事件入口 |
| 回归 | tests/test_rules.py、test_storage.py、test_api.py、test_dev_integration.py、test_config.py、test_deployment.py、test_gray_config.py、test_gray_activation.py、test_startup.py、test_maintenance_scripts.py、test_frontend.py;tests/frontend/admin.test.cjs、chat.test.cjs |
Linux 回退模拟基础设施、实际操作临时文件;模型、数据库及上传隔离;Node 验证草稿、异步切换、转接、撤回及评价;浏览器使用临时测试库 |
除标明的根目录文件外,表内缩写的 Python 文件均相对 logistics_chat/。
当前文件布局
am.py # CLI;显式请求 am.app 时创建默认 WSGI 应用
rates_extra.py # 旧导入路径的兼容转发
logistics_chat/
├── __init__.py # create_app 与显式运行时初始化
├── config.py # 根目录 .env、环境变量、稳定根路径、服务/资源配置
├── db.py # 连接、schema、历史持久化、备份
├── knowledge.py # 知识与关键词检索
├── pricing.py # 运费规则、解析、换算、附加费说明
├── rates_extra.py # 扩展报价数据
├── tracking.py # 单号识别、模拟轨迹、卡片协议
├── order_data.py # 可编辑订单测试表、精确查询、快照导入与卡片事实
├── ai_client.py # 模型 HTTP、压缩与追问
├── chat_service.py # 问答编排与 SSE 帧
├── history_service.py # 历史会话 CRUD
├── human_service.py # 人工状态与消息
├── business_service.py # 反馈、评价、统计、登录、订单、留言、历史搜索、Excel/CSV
├── file_service.py # 附件校验、保存、清理
├── hooks.py # 限流及 HTML/SSE 缓存头
├── runtime.py # Waitress / Flask 启动
└── routes/
├── __init__.py # Blueprint 注册
├── chat.py
├── history.py
├── human.py
├── admin.py
├── business.py
├── files.py
└── pages.py
templates/ # HTML/Jinja 及原有事件属性
static/css/ # theme.css 共享令牌;chat/admin/admin_login 页面样式
static/js/ # chat.js、admin.js、admin_login.js,经典脚本
scripts/ # 可独立运行的维护 CLI
tests/ # 按业务模块组织的回归
模块边界与依赖方向
- 路由只解析请求并包装响应;服务组织业务;规则模块提供确定性计算。依赖方向为
routes → services → rules / db / ai_client,下层不导入路由或根入口。 db.py统一连接、schema 和历史持久化。人工与管理业务的 SQL 放在所属服务,HTTP 路由不执行 SQL。请求及流式请求结束时关闭连接,未提交事务回滚。init_db包括客服账号、订单与留言表,首次启动只建空表;不内置账号。重复初始化保留已有数据,业务测试从真实初始化结果插入夹具,不另建缺失表。- 所有模型 HTTP 经
ai_client.post_completion。保留流式问答(5, 90)、压缩(5, 30)、追问(5, 15)三组连接/读取超时;不能因共用客户端而合并期限。流结束或取消时释放 HTTP 响应。 - 模块导入不建库、不备份、不清理附件、不访问模型。
create_app默认显式初始化运行目录、数据库、备份与旧附件清理;INITIALIZE_RUNTIME=False可禁止这些动作,此时调用方负责提供已有 schema。访问兼容入口am.app视为显式创建应用。 config.ROOT_DIR指向仓库根目录,模板/静态目录显式提供给 Flask。数据库、上传、备份、日志使用配置中的绝对路径,避免包文件移动改变数据位置。default_config用python-dotenv读取根目录.env,不修改os.environ;只读取已支持的DEEPSEEK_API_KEY,不展开${...}。显式工厂配置优先于进程环境(含空值),再使用文件值。配置在应用创建时确定,修改后重启;真实.env不纳入 Git。- 限流、输入状态由应用实例持有,不能每请求重置或跨测试应用共享。维护 CLI 和纯规则可用标准库加载,包入口延迟导入 Web 依赖。
- 单文件围绕一个职责;超过约 300–400 行时检查职责,而非按行数机械拆分。报价数据保持单一来源;兼容转发不复制字典。页面脚本按页面保留既有执行顺序和全局函数,不擅自改为 ES Modules。
- 配色、字体基线和通用控件放在
theme.css,页面布局放在对应 CSS。客户页通过body.dark覆盖主题令牌;新增可翻译文案加入chat.js的I18N,静态文案用data-i18n。调整样式后检查桌面、390px 窄屏、侧栏、聊天卡片和客服空/活动状态。 - 客户界面不附加测试/演示声明、底部免责声明、键盘说明、快捷入口副标题或附件准备说明;草稿恢复和发送成功静默处理。加载、录音、人工排队状态及操作失败反馈保留,权限校验继续由服务端执行。
- 客户查货以入仓码为主要入口,输入占位、快捷入口与查询追问均优先使用入仓码;订单卡片从
fields.inbound取得主显示号码,内部订单号放在展开详情。card.no仍保存内部订单号以兼容历史协议和上下文关联;未提供入仓码时保留准确的订单号标签,不生成替代入仓码。 - 客户附件预览由
clearAttachment管理,轨迹展开由fillTrackCard管理。客服drafts按sid保存草稿,发送成功后只清理已确认发送的内容;常用回复仅填入草稿。renderList只在展示数据变化时重绘。 - 客户文字草稿由
drafts.js的ChatDrafts.create管理,脚本在visitor.js之后、chat.js之前加载;localStorage 按访客 key 和会话分条存储,sessionStorage 仅记录当前标签页的活动会话。切换前同步保存,输入防抖及页面隐藏时保存;无持久存储时使用页面内存并提示。恢复历史的异步结果受会话版本约束,加载失败禁止继续发送,删除会话同步清除其草稿。草稿不含未发送附件,不跨设备同步。 - 人工申请由服务端以 UTC+8 判断每日 08:00–22:00 营业状态,客户手机时区不参与判断。附件清理保留
messages和human_messages引用的文件,仅删除超过 7 天的未引用临时文件;读取引用失败时停止清理。附件不存在或不属于当前游客均返回相同的私有、不可缓存 404 页面,破图在聊天中显示可读提示。 - 客户页
resetConversation统一处理新建、快捷问题和历史切换;人工路由同时绑定humanConvId与当前会话版本。旧建会话、历史、人工申请和轮询的异步结果不得覆盖新话题。三个业务快捷入口开启独立 AI 对话,联系人工入口才进入人工模式;排队/接待状态常驻输入框上方,可用「返回 AI 问答」结束人工会话。人工消息确认成功后才清空草稿,失败保留输入;服务端校验可选conv_id并拒绝已关闭会话。 - 客服工作台默认只展示会话列表和全高聊天区;统计使用原生
details收在「数据概览」。setSessionTab切换接待/排队列表,setChatView与showSessionList控制 720px 及以下的整屏导航,不结束当前会话。setToolsOpen控制默认关闭的服务资料:1200px 以上为侧栏,较窄屏为抽屉;列表入口让未选会话的手机用户也能查看留言。
后续结构调整顺序
| 阶段 | 范围 | 验收要求 |
|---|---|---|
| 1. 纯规则 | 知识、报价、运费、运单识别 | 边界结果与迁移前一致;固定时钟比较模拟轨迹;导入无运行数据副作用 |
| 2. 数据访问 | 配置、SQLite、备份、附件 | 临时目录验证 schema 兼容、事务、保留/清理时机和跨目录 CLI |
| 3. 服务/路由 | 模型、聊天、人工、应用工厂、Blueprint | URL/方法/状态/JSON/SSE 不变;保留限流、HTML/SSE 缓存头及根入口 |
| 4. 前端 | 模板与 CSS/JS | 仅迁移时对照原资源内容;主动改版时验证语法、资源 URL、加载顺序、响应式和客户/客服页交互 |
每阶段完成相关验证再推进依赖阶段。鉴权、缺表和计费的业务修复单独说明,避免在结构调整中顺带改行为。
必须保留的接口与数据约定
- SSE 字段为
t、e、card、replace、suggest、d;客户端兼容 LF/CRLF 和跨字节分段,收到d才算成功,意外 EOF 保留已收到正文并提供重试。上游缺少结束标记或因长度截断时保存中断状态;压缩、追问、流式 HTTP 响应都要关闭。首段等待 90 秒、整轮 120 秒;会话、历史及人工 JSON 请求 10 秒,上传 15 秒超时可恢复。 chat_service.create_chat_stream的订单查询使用本地order_test_data;忽略附件路径、重量、日期和三维尺寸,保留标识中的后缀,不再调用build_track生成模拟轨迹。旧模拟函数仅兼容旧导入,不进入聊天查询路径。未知单号、多条匹配和查表失败明确说明,禁止回退模型编造状态。CARD_PREFIX新增kind=order:仅包含白名单概要、申报货物汇总和订单业务状态历史,不包含姓名、地址、内部备注或操作人。order_tracking.py从order_test_data.tracking_json的已关联事实生成物流轨迹;存在有效事实时设置node_kind=logistics,nodes仅存实际节点,estimated_nodes单独展示预计节点,tracking_status只按实际节点序号推进。相同货柜/节点按最新观测修正时间,预计事实不覆盖实际事实;多柜独立展示。缺失的节点、地点和签收事实不得补造,提柜、还柜不等于签收。子单不继承父单重量或轨迹;COMPLETED 只表示头程订单完成。没有物流事实时继续展示业务状态历史。页面和模型上下文不附加测试副本声明或导入时间。订单及物流追问只在同一conv_id内复用最近单号,并重新查表取得修改后的值;新报价或其他话题不会沿用旧订单。- 订单表不提供管理页面或对外增删改接口;表数据在运行时 SQLite 内维护,导入脚本默认保留已有记录,只有显式
--replace覆盖。匿名 HTTP 查单在读取订单前返回登录引导;g.customer_tenant_id由可信客户认证适配器设置,不接受访客 key、请求参数或客户自报 Header。客户范围非空时必须传给lookup_order,并与ORDER_DATA_TENANT_ID的额外限制取交集。源库连接凭据和快照在仓库外,业务库只读,不随服务查询访问源库。 - 客户认证使用独立
customer_users、customer_tenant_grants与customer_sessions表,由customer_service.attach_customer在访客恢复之后验证并设置g.customer、单客户g.customer_tenant_id或多客户g.customer_tenant_ids;只保存密码哈希与登录 token 哈希。默认账号首次关联当前本地订单的客户范围,之后不自动扩大,普通账号仍按明确关联过滤;lookup_order的单客户参数及多客户白名单都与环境限制取交集。会话的customer_scope保存范围指纹。登录 Cookie 为 HttpOnly、SameSite=Lax,在 HTTPS 上为 Secure,绑定当前访客并在 8 小时后失效;退出、账号停用、密码或客户范围改变使旧会话失效。默认客户账号不赋予客服后台权限。 - 匿名查单返回 SSE
login_required与正文,客户页提供登录按钮;customer.js登录成功后仅在原会话版本未变化时继续原问题,保留期间输入的新草稿。侧栏可独立登录/退出,登录状态在窗口恢复焦点时刷新。首次返回订单卡片时,会话绑定客户 ID 和客户范围;历史、人工会话及后续生成同时校验访客、客户和范围。退出后清除当前可见订单内容,重新登录同一身份可恢复;匿名清空历史不删除被登录保护的会话。 - 节点导出:
scripts/export_order_tracking.py --database-env <显式环境文件> --orders-snapshot <私有订单快照> --output <新私有节点快照>。仅需离线导出时安装可选PyMySQL;脚本开启源库只读事务,只查询快照订单直接关联的入库完成、已冻结装柜和标准船节点,按客户与源订单 ID 同时匹配。随后运行scripts/import_order_test_data.py --snapshot <节点快照> --db <运行数据库> --tracking-only;只更新已有订单的tracking_json,校验源 ID,不改订单字段,默认保留已有轨迹。--replace才覆盖手工轨迹。快照不随 Git 发布;将来 MCP 接入仍返回同一事实结构,无需改变卡片协议。 - 历史卡片以
__CARD__{json}保存;原 SQLite 表、字段、角色值和历史记录继续可读。历史正文和卡片使用存库时间,卡片恢复复制操作。切换话题或历史时取消旧流和待上传附件;等待建会话期间的新草稿不能被旧发送清空。历史刷新失败保留已有列表;删除或清空成功后先同步本地列表,并使先前的刷新失效,刷新失败或迟到均不恢复已删除记录。删除会话同步移除其人工会话及消息,迟到的模型写入不能生成孤立消息。 - 人工申请使用
conv_id,返回session_id,后续用sid。状态为waiting/active/closed,消息方向为customer/agent/system。转接将 active 改为 waiting,列表只允许主动接入。备注保存在human_sessions.note;撤回将原客服消息改为系统提示并追加带recalled_id的系统事件,轮询按原消息编号更新,重复撤回不追加事件。 - 新建会话保存一条 assistant 欢迎语并返回
welcome,前端在首次用户消息之前显示。首次提问判定只统计 user 消息。服务评价用ratings保存,并汇总到管理统计。Excel 导出依赖openpyxl,原 CSV 可用?format=csv访问。 - 计费档位、单位换算和舍入保持原样。
shipping_intent_with_context用is_shipping_followup识别渠道、重量、尺寸、单独目的地回复及重试,保留报价条件;新订单、另外一票、理赔及新的完整问题形成回溯边界,不继承上一票尺寸。尺寸支持带小数和空格,换算单位后计价。中英混写的 UK/US 按 ASCII 边界识别;目的地未确认时先询问目的国家,不默认给出欧洲报价。英国使用英国渠道和默认比价组合。明确提到美国等未覆盖目的地时不套用欧洲费率,英国按方报价不套用欧洲仓价;指定国家或仓点没有匹配时calc_warehouse_card返回空。calc_freight.total仅含基础运费;附加费尚未统一并入。知识文档与knowledge_list同步维护。 - 聊天请求的会话编号仅接受 SQLite 整数范围内的 ASCII 十进制整数或空值;字符串转换前限制长度,非法 Unicode 数字和超长数字返回 SSE 输入错误,不进入模型调用。
- 上传 URL 保留
/uploads/,默认 10MB,扩展名配置保持不变。原附件保留本地、继续忽略,目录迁移不另建一份数据。 - 手机语音由
voice.js管理按住、松开、上滑取消、60 秒上限及媒体轨道释放;切换会话、离开页面或权限/转写超时取消录音与待处理请求,迟到结果不能进入新会话,已有草稿不自动提交。/api/speech/config仅返回可用性及限制,/api/speech/transcribe有独立 5MB 请求限制并以MemoryAudioRequest将音频文件保持在内存中;音频不进入 uploads 或聊天历史。百度适配通过 FFmpeg 管道转换 16 kHz 单声道 PCM,校验真实解码长度后调用百度短语音识别,Token 按应用隔离缓存。可选 DashScope 适配和 DS 文字聊天凭据独立,密钥及上游原始错误不得返回客户端。识别结果走既有 AI/人工消息入口。H5 订单详情默认折叠;可视高度变化保持最新回复可见,阅读旧消息时不强制拉到底部。 agents、orders、tickets在应用初始化时建空表;兼容已有独立 id 列的 agents 表,不预置账号或演示数据。- 游客由
visitor.js用安全随机数生成本地 key,经/api/visitor/session换取 HttpOnly、SameSite Cookie,HTTPS 时设置 Secure;浏览器存储不可用时可用 Cookie 恢复。数据库chat_history.visitor_id仅存 key 的 SHA-256,历史增删查、SSE 上下文和visitor_uploads附件索引均按归属校验。旧数据归属为空时保留,不自动授予新访客。客户端公共人工接口为/api/visitor/human/{request,poll,send,close},校验会话归属、只接受 customer 消息并去掉内部备注;原/api/human/*留给受保护客服。客服附件用/api/staff/uploads/,所有客户 JSON 与附件禁止共享缓存。 - Nginx 默认保护客服及未知路径,只有客户首页、静态资源和明确的客户 API 白名单免密码。后台仍依赖代理认证,上游只能绑定回环地址;客户账号登录和订单关联尚未实现,游客身份不能升级为订单权限。
验证与维护
先按 README 安装项目依赖。Windows 使用 .\.venv\Scripts\python.exe -m pytest -v 或同解释器的 -m unittest discover -s tests -v;Linux/macOS 使用 .venv/bin/python。业务测试依赖 Flask,不能用未安装依赖的全局 Python 运行整套测试。模型请求全部模拟,不代表真实 DeepSeek 联调。
路径、函数或资产移动后同步更新根 AGENTS.md、本文、知识资料和 README 目录树。仅改导航时检查路径、符号、命令;改业务时补充相关成功和失败分支,前端改动检查实际页面。