CGL SCRM / architecture
架构
请求链路、部署拓扑与运行时
一次访问,系统做了什么
市场部同事在浏览器里点开一个链接,到他看见页面,中间大概走了这么几步。
1
浏览器
同事打开
scrm.jxccwx.com2
域名中转
Cloudflare 把请求转给腾讯云上的服务
3
认出是谁
看浏览器带的登录凭证 → 认出是哪位同事
4
查数据
按他要看的页面去数据库取数
5
拼页面
把数据填进页面模板,生成整张 HTML
6
返回
浏览器直接显示,前端不用再拉一次数据
「拼好再发」这一点值得说:页面是在服务器上拼完整了才发给浏览器的,
不是先发一个空壳、再由浏览器去要数据。好处是打开快、也少了很多「转圈等数据」的中间状态。
代价是每次都要服务器算一遍——但这个量级完全扛得住。
系统分几层
从上到下,每一层只干自己那件事。
页面 / 接口
决定「哪些网址存在、每个网址干什么」。
共 17 个模块、199 个网址。
后台页面、手机上报名用的页面、给 AI 用的接口,都在这一层。
业务逻辑
真正的「怎么做」都在这里。
比如「生成一张二维码」「算这周新增了多少线索」「导出 Excel」。
页面层只负责调用,不写业务规则。
数据表
定义「数据长什么样」。
哪张表有哪些字段、什么类型、能不能为空。改这里的字段名会影响整个系统。
公共零件
各处都要用的小工具。
读配置、写日志、连数据库、算版本号、字典表(比如「线索状态」有哪几种)。
请求进来,先过这几道关
每个请求都依次经过下面这些「关卡」,然后才轮到具体页面处理。顺序是代码里定的,不是猜的。
1
inject_user
先认出「现在是谁在访问」——把当前登录用户挂到这次请求上(认不出就当没登录,不拦人)
2
FlashMiddleware
把上一步操作留下的提示语(「保存成功」这类)带过来,显示完就丢掉
3
RequestContextMiddleware
给这次请求编一个编号,之后所有日志都带上它,出问题能一条线串起来
4
SecurityHeadersMiddleware
给响应盖上几个安全头,告诉浏览器别乱猜内容类型、别被嵌进别人的页面
一个容易误会的地方:认出「谁在访问」的那道关只负责认,不负责拦。
就算没登录,它也只是记一句「这次是游客」,然后照样放行。
真正决定「这个页面能不能看」的,是每个页面自己挂的权限检查——
这部分在「安全模型」那一页讲。
出错了会怎样
| 哪种情况 | 谁来收拾 |
|---|---|
| builtins.Exception | unhandled_exc_handler |
| fastapi.exceptions.HTTPException | http_exc_handler |
| fastapi.exceptions.RequestValidationError | validation_exc_handler |
| fastapi.exceptions.WebSocketRequestValidationError | websocket_request_validation_exception_handler |
| starlette.exceptions.HTTPException | http_exception_handler |
注意第二、三行看着名字一样,其实是两个不同的类(一个来自 FastAPI、一个来自 Starlette)—— 所以两行都得留着,不能合成一条。
设计上有一条明确的取向:「没登录」跳登录页、「参数填错」回一句人话、「真出了意外」给一个带编号的错误页。
出问题时那个编号能让人拿它去日志里搜,直接定位到是哪一次访问。
两个「体检」地址
这两个网址不给人看,是给监控和排查用的。它们不需要登录。
| 网址 | 回答什么问题 |
|---|---|
| /healthz | 「服务还活着吗?现在跑的是哪一版代码?」 |
| /readyz | 「数据库连上了吗?微信配好了吗?图片存哪?字体在不在?有没有脏数据?」 |
为什么版本号这么重要
这套系统跑在云上,云平台自己显示的「运行中 / 创建中」判断不了新版本有没有真的生效——
明明已经更新完了,它可能还显示「创建中」。所以代码里自己算了一个版本号(页脚那个
rev 9bcd3b57),改完就换一个值。
上线后打开这个网址一看,就知道线上到底是新的还是旧的。
一个必须说清楚的局限:这个版本号只跟着程序代码变。
如果你只改了配置、改了文档、改了这个展示站,版本号是不会动的。
所以「版本号没变」≠「什么都没改」,这两件事不能混。
页面长什么样,是在服务器上定好的
全站 56 个页面模板。 「后台的线索列表」「手机上报名的页面」「活动落地页」各有各的模板, 数据由服务器填进去,浏览器收到的是成品。
| 模板分组 | 文件数 | 大致管什么 |
|---|---|---|
| (根) | 4 | 公共骨架与错误页:每个页面都套用的外壳、404、500 |
| auth | 1 | 登录页 |
| h5 | 8 | 客户在手机上看到的页面:活动落地、报名、问卷、分享 |
| manager | 43 | 后台页面:看板、线索、活动、问卷、内容、账号、审计、报表 |
数据存在哪
线上正式数据
PostgreSQL 数据库。程序不直接连它,而是走一条内部专门的数据通道
(这样能在不打开全部网络出口的前提下访问数据库,也少一层暴露面)。
开发和测试数据
本机一个单文件数据库,方便随时清空重来。跑自检时会先把里面清干净再灌测试数据,
跑完再按档案还原。
这里有个反复踩的坑,值得单独标出来:
「在本机跑得好好的,一上线就出问题」在这套系统上出现过八次。
原因都是两套环境走了不同的代码路径——
比如本机数据库比较宽松、线上一丝不苟;本机时间是「不带时区」的、线上是「带时区」的。
所以规矩是:凡是动了数据、类型、时间、密钥的改动,必须单独再跑一遍真对线上的检查。
图片、二维码、海报存在哪
这些东西不放数据库(数据库只存「文件在哪」),实际文件放在对象存储里。
现在线上用的存储方式是:cos
(腾讯云 COS);本机开发时用的是本地磁盘,访问前缀 /static/uploads。
挂载顺序曾经出过一次真事故:两个静态目录的注册顺序写反了,
长的那条被短的抢走,结果是「文件明明在硬盘上,页面上却是裂图」。
这类问题不会报错,只会默默显示不出来——所以顺序这件事在代码里专门留了注释。
系统里几个固定网址
| 网址前缀 | 干什么用 |
|---|---|
| /manager/* | 后台。市场部同事日常用的地方。 |
| /video/* | 对外获客页面。客户在手机上点开报名、填问卷的地方。 |
| /api/v1/* | 给程序用的接口(原来没有,这次新增)。 |
| /mcp | 给 AI 助手用的入口(原来没有,这次新增)。 |
| /Botch_qrcode/* | 二维码图直接生成——老名字,原样保留,老物料上的链接才不会失效。 |
| 微信服务号把事件推给我们的地方。 | |
| /healthz · /readyz | 体检地址,见上面。 |
前两层里带 /manager、/video、
/Botch_qrcode 的网址,都是照搬老系统的——
包括「Botch」这个拼写错误,也一起照搬了。理由很简单:
老物料、老二维码、老脚本里写的都是这些地址,改了它们就等于把过去的东西全弄坏。