两个系统的技术选型与架构:为什么我们选择了"够用"而不是"时髦"
同一团队、同一台服务器上的两套系统,做了完全相反的技术选择——云施工平台做"重",门户网站做"轻"。本文摊开讲清每一步取舍的依据。
一家做全屋智能的公司,为什么需要自己写两套系统?一套管业务,一套对外说话。这个选择本身不复杂,但落到具体的技术选型上,每一步都有取舍。
本文把「慧玩智家云施工管理系统」(以下简称云施工平台)和公司门户网站两个系统的技术选型与架构摊开来讲。两套系统出自同一个团队、跑在同一台服务器上,却做了完全相反的技术选择——这正是本文想讲清楚的事。
一、先看结果:两套系统长什么样
| 云施工平台 | 公司门户网站 | |
|---|---|---|
| 定位 | 内部业务系统,管项目全生命周期 | 对外门户,讲清公司是谁、做过什么 |
| 用户 | 公司员工 + 客户(分角色) | 公众、客户、AI 检索引擎 |
| 访问地址 | cbulid.smart-joy.cn | smart-joy.cn |
| 后端 | NestJS 10(TypeScript) | Express 4(JavaScript) |
| 前端 | Vue 3 + Vite + Element Plus | 单文件静态 HTML(服务端渲染) |
| 数据库 | PostgreSQL 15(35 张表) | PostgreSQL 15(独立库) |
| 文件存储 | MinIO | 复用同一套 MinIO |
| 代码规模 | 前后端分离,模块化 | 后端仅 2 个依赖 |
一句话概括这个对比:业务系统做"重",门户做"轻"。
二、云施工平台:为什么用 NestJS + Vue 3
云施工平台要管的事情很实在——项目从签约到交付的十个施工阶段、合同与金额、设计与方案、商品与库存、人员与权限。这些东西的共同点是:关系复杂、变更频繁、错了代价大。
后端选 NestJS
NestJS 是个"重框架":它强制以模块、控制器、服务三层组织代码,自带依赖注入,把 TypeScript 的类型系统贯穿到底。
对一个要做十年的业务系统来说,这些"约束"是资产而不是负担:
- 模块边界清晰——设计、项目、合同、库存、权限各自独立,改一个模块不会牵动全局
- 类型系统兜底——数据库字段和代码类型对不上,编译期就报错,不会等到线上才炸
- 依赖注入——数据权限这类横切逻辑可以做成守卫(Guard)统一注入,不用每个接口重复写
配套选择也是同一套逻辑:
| 层 | 选型 | 理由 |
|---|---|---|
| ORM | TypeORM | 与 NestJS 原生集成,实体定义即表结构 |
| 认证 | Passport + JWT | 无状态,前后端分离的标配 |
| 校验 | class-validator | 声明式校验,DTO 即契约 |
| 日志 | winston + 按日切割 | 业务系统需要可追溯的操作记录 |
| 文件 | MinIO SDK | 私有化部署,数据不出自己的服务器 |
前端选 Vue 3 + Element Plus
选 vue 3 不是因为它新,而是因为这个团队的后台系统需要一个组件齐全、开箱能用的中后台框架。Element Plus 提供了表格、表单、弹窗、日期选择这一整套,省掉大量基础组件开发。
配套里有两个值得一提的:
- Fabric.js——设计方案模块的画布引擎。设计师要在画布上摆放设备、画点位、做标注,这需要成熟的 2D 绘图库
- ECharts——数据统计可视化
- Pinia——状态管理
还有一个关键决定:这个平台要做手机端。
我们的做法是「一后端多前端 + UA 分流」——同一套后端 API,PC 端走全功能后台,手机端走独立精简界面,靠 User-Agent 判断设备类型自动分发。手机端只做三件事:看现场照片、看设计方案、管项目进度。
为什么不用响应式一套兼容两端? 因为两端的使用场景根本不同——PC 端是坐办公室做设计、调库存、审合同;手机端是工程师在工地现场拍照、查图纸。硬做一套响应式,结果往往是两边都不好用。
三、门户网站:为什么反其道而行之
门户网站的技术选型几乎是云施工平台的反面:后端只有两个依赖(express + pg),前端是一个静态 HTML 文件。
这不是偷懒,是刻意的。
门户的核心任务是对外"被读懂"
门户网站有两类读者:人和 AI。人来看公司介绍、看项目案例、看资质;AI 来抓取内容、理解公司是做什么的。
第二类读者带来一个硬约束:AI 爬虫不执行 JavaScript。
这意味着任何依赖前端 JS 渲染的内容,对 AI 来说等于不存在。所以门户选择了服务端渲染——页面在服务器上拼好完整 HTML 再发出去,爬虫拿到什么,人看到的就是什么。
后端只做两件事
门户后端(Express,127.0.0.1:8173)职责很窄:
- 内容分发配置——一份
content.json单一事实源,派生出两个版本:人看的 HTML 页面,和 AI 读的结构化数据 - 从业务库取脱敏聚合数据——比如"服务过多少个小区""做过多少个项目",读云施工平台的库,聚合成数字
明确不做的事:不做内容编辑器、不做 AI 生成、不做监控看板。
为什么只有两个依赖
因为服务器带宽只有 5Mbps,而且这台服务器上还跑着业务系统、数据库、对象存储、钉钉迁移服务。
每多一个依赖,就多一次安装、多一份体积、多一个出问题的可能。一个门户网站,用最基础的 HTTP 框架 + 数据库驱动就够了。
数据安全:只读账号 + 脱敏聚合
门户后端读业务库时,用的是只读账号——只有 SELECT 权限,写操作和读其他表都被数据库拒绝。
这不是小心过度。门户是对外服务,万一有漏洞,攻击者能拿到的也只是聚合后的数字,碰不到客户姓名、房号、金额。
隐私边界很明确:门户只输出"某小区服务过多少户",不含任何个人信息。
四、两个必须讲清的技术决定
1. 单一事实源,而不是"同步"
门户的内容和 AI 读的数据,来自同一份 content.json。人的页面和 AI 的接口都是它的派生结果,不是两份需要同步的数据。
这个设计消除了一整类问题——不一致。不用保证"网页改了 AI 那边也要改",因为它们本来就是同一个源。
2. 导航用原生 HTML,不用 JavaScript
门户导航里的下拉菜单,用的是 HTML 原生 <details> 元素,零 JavaScript。
三个理由:
- 爬虫能读——
<details>是纯 HTML,AI 爬虫照读不误;JS 下拉里的链接对爬虫等于不存在 - 手机自动可用——原生组件点一下就展开,不用写触摸逻辑
- 可访问性正常——读屏软件能识别
原则:这个站点上任何"折叠/展开"的交互,优先找原生 HTML 元素,其次才考虑 JS。
五、两套系统的分工
讲到这里,两套系统的关系就清楚了:
用户/客户 ──→ 门户网站(smart-joy.cn)
对外展示:公司介绍、项目案例、资质荣誉
面向:公众 + AI 检索引擎
技术:Express + 服务端渲染 + 静态单文件
公司员工 ──→ 云施工平台(cbulid.smart-joy.cn)
业务管理:项目、合同、设计、库存、权限
面向:内部员工 + 客户(分角色)
技术:NestJS + Vue 3 + TypeScript
↓ 两个系统共享
PostgreSQL 15 + MinIO(对象存储)
同一台服务器(2 核 2G)
门户从业务系统取的是"聚合后的数字",不是原始数据。 比如门户首页显示的"服务过 267 个社区",是从云施工平台的项目库实时聚合出来的——数据是真的,但具体是哪些客户、哪些房号,门户看不到也不需要看到。
六、回头看这些选择的共同点
把两套系统的技术选型放在一起看,会发现几个一致的原则:
1. 按用途选技术,不按流行度选
业务系统要管十年、关系复杂 → 上重框架(NestJS)让框架约束发挥作用;门户要对外被读懂、流量不大 → 用轻技术(Express + 静态)减少变量。
2. 简单能解决的,不用复杂方案
门户后端只有两个依赖。不是因为团队不会用别的,是因为这两个够用。
3. 数据安全是设计出来的,不是补上来的
只读账号 + 脱敏聚合,在数据流出业务库的那一刻就切断了敏感信息,而不是靠"上线后注意点"。
4. 能被机器读懂,是新时代的基本要求
服务端渲染、原生 HTML、结构化数据——这些选择都是为了让人和 AI 都能准确理解这家公司。
技术选型没有标准答案,只有"适合这个场景的答案"。我们的判断依据始终是:这个系统为谁服务、要被用多久、出错的代价有多大。
想清楚这三件事,选型自然就清楚了。
本文由数字员工整理,技术负责人审定。