前言
做技术选型最怕两件事:第一,选了一个"看起来很酷"但生态不成熟的技术;第二,选了一个"大家都用"但完全不适合自己场景的技术。
最近在规划一套企业级 SaaS 平台,花了不少时间做技术选型对比。这篇文章把每个技术选型的理由、优势、踩过的坑都写出来,希望能给正在做类似选型的人一个参考。
一句话总结:这套技术栈不是追求"最新最炫",而是在成熟度、性能、团队上手成本、长期维护之间找平衡。
前端技术栈
Vue 3.5:Composition API 的成熟态
Vue 3.5 不是大版本跳跃,但有几个实打实的升级:
defineModel 正式转正。以前父子组件双向绑定要写一堆 props + emit 模板代码,现在一行搞定:
<!-- 以前 -->
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const value = computed({
get: () => props.modelValue,
set: (v) => emit('update:modelValue', v)
})
</script>
<!-- Vue 3.5 -->
<script setup>
const model = defineModel()
</script>
这个变化让表单组件的代码量减少 60% 以上。
响应式性能提升 30-50%。底层响应式系统做了优化,大量数据渲染时感知明显。对于企业后台那种"一屏展示几百条数据"的场景,这个优化很关键。
SSR 性能大幅提升。如果你的前端需要做 SSR(比如 SEO 要求高的对外页面),3.5 的 SSR 水合速度有显著提升。
为什么不选 React? React 生态更大没错,但对企业后台项目来说,Vue + Element Plus 是开箱即用的组合。React 需要额外选型 UI 库(Ant Design or MUI?)、状态管理(Zustand? Redux?)、路由(React Router?),选型成本更高。而且国内团队对 Vue 的熟悉度普遍更高。
TypeScript 6.0:类型安全的新基准
TypeScript 6.0 是 2026 年的桥接版本,连接 TS 5.9 和即将到来的 Go 重写版 TS 7.0。
三个核心变化直接影响新项目:
strict默认 true — 以前tsconfig.json里要手动开strictNullChecks、noImplicitAny,现在开箱即严。新项目不用再纠结"要不要开严格模式",默认就是最严的。types默认 [] — 不再自动扫描@types目录。这意味着你npm install @types/lodash后必须在types数组里手动声明,否则不生效。看起来麻烦,但减少了无用的类型检查,构建速度更快。- 废弃
moduleResolution: node— 新项目直接用bundler或node16。node模式是历史包袱,不支持条件导出和子路径导出。
踩坑提醒:NestJS 10.3 的 @nestjs/cli 可能还没完全适配 TS 6.0。初始化项目时如果遇到编译错误,临时锁定 typescript: 5.7 即可,等 NestJS 正式版更新。
Vite 6:构建工具的事实标准
Vite 已经不需要太多解释了。Rollup + esbuild 的组合让冷启动速度和热更新速度都碾压 Webpack。
Vite 6 的亮点:
- 更好的 SSR 支持
- 更小的打包体积(tree-shaking 优化)
- 原生支持 CSS 嵌套语法
- 更快的模块热替换(HMR)
为什么不用 Webpack? Webpack 生态确实大,但配置文件写起来像天书。一个简单的项目 webpack.config.js 动不动就上百行。Vite 的配置通常只需要 20 行。对于不需要 Webpack 特殊 loader 的项目,没有理由回去受罪。
Element Plus 2.13:企业后台的成熟选择
Element Plus 是 Element UI 的 Vue 3 版本,在国内企业后台领域几乎是默认选择。
为什么选它:
- 组件覆盖全面(表格、表单、树形控件、日期选择器、穿梭框等)
- 中文文档完善
- 主题定制简单(SCSS 变量覆盖)
- 表格组件性能优化好(虚拟滚动支持)
- 与 Vue 3 Composition API 无缝配合
常用组件:el-table(数据列表)、el-form(表单录入)、el-tree(组织架构)、el-date-picker(时间筛选)、el-upload(文件上传)。
替代方案:Ant Design Vue(风格更现代,但组件数量略少)、Naive UI(轻量,但企业级组件不够丰富)。
Pinia 3.0:状态管理的正道
Vuex 已经是过去式了。Pinia 是 Vue 官方推荐的状态管理库,API 简洁,TypeScript 支持一流。
Pinia 的核心优势:
// 定义 store
export const useDataStore = defineStore('data', {
state: () => ({
items: [],
loading: false,
pagination: { page: 1, total: 0 }
}),
actions: {
async fetchData(params) {
this.loading = true
const data = await api.get(params)
this.items = data.items
this.pagination.total = data.total
this.loading = false
}
}
})
// 组件中使用
const store = useDataStore()
await store.fetchData({ page: 1 })
console.log(store.items)
不需要 mutation、不需要 action 的 commit 模式、不需要 namespace。就是简单的 state + getters + actions。
为什么不用 Vuex? Vuex 5 的提案也是向 Pinia 看齐。官方都已经放弃了,没有理由继续用 Vuex。
Vue Router 4:路由的稳定选择
Vue Router 4 是 Vue 3 的官方路由,API 稳定,TypeScript 支持完善。
常用特性:
- 动态路由(根据权限加载菜单)
- 路由守卫(登录验证、权限检查)
- 路由懒加载(按模块分割代码)
scrollBehavior(页面切换时滚动到顶部)
// 路由守卫示例
router.beforeEach(async (to) => {
if (to.meta.requiresAuth) {
const userStore = useUserStore()
if (!userStore.isLoggedIn) {
return { path: '/login', query: { redirect: to.fullPath } }
}
}
})
Vue-i18n 10:多语言的刚需
覆盖东南亚市场的 SaaS 平台,多语言是硬需求。
Vue-i18n 10 的亮点:
- Composition API 支持(
useI18n()) - 按需加载语言包(减小首屏体积)
- 支持复数、日期、数字格式化
- 与 Vue DevTools 集成
// 使用示例
const { t } = useI18n()
// zh-CN
// t('order.status.pending') → '待处理'
// vi (越南语)
// t('order.status.pending') → 'Đang chờ xử lý'
// th (泰语)
// t('order.status.pending') → 'รอดำเนินการ'
语言文件管理建议:按模块拆分,不要把所有翻译塞进一个大文件。
Tailwind CSS 3.4:原子化样式的力量
Tailwind CSS 是一种原子化的 CSS 框架,通过组合 utility class 来构建界面。
为什么在 Element Plus 项目里还用 Tailwind? Element Plus 负责组件级样式,Tailwind 负责布局和微调。两者不冲突:
<template>
<div class="flex gap-4 p-6">
<el-card class="flex-1">
<h2 class="text-xl font-bold mb-4">数据列表</h2>
<el-table :data="items">...</el-table>
</el-card>
<div class="w-80">
<el-card>
<h3 class="text-lg font-semibold mb-3">筛选条件</h3>
<!-- 筛选表单 -->
</el-card>
</div>
</div>
</template>
flex、gap-4、p-6、text-xl 这些 Tailwind class 用来做布局,组件内部样式还是 Element Plus 管。
注意事项:Tailwind 在构建时会扫描你的文件,如果配置不当可能漏掉动态 class。确保 tailwind.config.js 的 content 数组覆盖所有 Vue 文件路径。
后端技术栈
NestJS 10.3:企业级 Node.js 框架
这是整套技术栈里最大的"升级"——从 Express 到 NestJS。
为什么 Express 不够用?
Express + 三层架构(routes → services → utils)对于单用途的接口站完全没问题。但一个企业级 SaaS 平台要处理订单管理、文档生成、财务对账、智能调度、异常预警等十几个模块。如果继续用 Express,随着代码增长,必然面临几个问题:
- 文件组织混乱:Express 没有约定,新项目 A 把路由放
routes/,新项目 B 放api/,三个月后没人知道代码在哪 - 依赖注入靠自觉:Express 没有内置 DI,共享服务要靠全局变量或手动传递
- 可测试性差:Express 的路由直接操作 req/res,单元测试需要 mock 整个 HTTP 层
- 中间件顺序靠记忆:
app.use()的顺序决定了一切,写错一个就出问题
NestJS 的解决方案:
src/
├── modules/
│ ├── order/ ← 订单模块
│ │ ├── order.controller.ts
│ │ ├── order.service.ts
│ │ ├── order.module.ts
│ │ ├── dto/
│ │ │ ├── create-order.dto.ts
│ │ │ └── query-order.dto.ts
│ │ └── entities/
│ │ └── order.entity.ts
│ ├── billing/ ← 财务模块
│ ├── document/ ← 文档模块
│ └── auth/ ← 认证模块
├── common/ ← 共享代码
│ ├── guards/
│ ├── interceptors/
│ ├── filters/
│ └── decorators/
├── config/ ← 配置
└── main.ts
每个模块是独立的,有清晰的边界。依赖注入是内置的。单元测试不需要 mock HTTP 层。
Express 迁移到 NestJS 的映射:
| Express | NestJS |
|---|---|
routes/ |
*.controller.ts |
services/ |
*.service.ts |
app.use(middleware) |
@UseGuards() / @UseInterceptors() |
| 手动 DI | @Injectable() 自动注入 |
如果你已经习惯了 Express 的分层架构,迁移到 NestJS 不会有认知负担。
注意事项:NestJS 10.3 不包含 @nestjs/schedule,定时任务需要额外安装 node-cron 或 @nestjs/schedule 包。
TypeORM 0.3:ORM 的成熟选择
TypeORM 是 TypeScript 生态中最成熟的 ORM 框架之一,NestJS 官方推荐。
核心功能:
// 实体定义
@Entity()
export class Order {
@PrimaryGeneratedColumn()
id: number
@Column({ length: 50 })
orderNo: string
@Column({ type: 'enum', enum: OrderStatus })
status: OrderStatus
@ManyToOne(() => Customer)
customer: Customer
@OneToMany(() => OrderItem, item => item.order)
items: OrderItem[]
@CreateDateColumn()
createdAt: Date
}
// 查询
const orders = await orderRepository
.createQueryBuilder('order')
.leftJoinAndSelect('order.customer', 'customer')
.where('order.status = :status', { status: OrderStatus.PENDING })
.orderBy('order.createdAt', 'DESC')
.take(20)
.getMany()
为什么不用 Prisma? Prisma 的 DX(开发体验)确实好,schema 语言清晰,迁移命令好用。但有两个问题:
- MySQL 5.7 支持不够好 — 虽然新项目用 MySQL 8.0,但如果要兼容旧环境,TypeORM 更灵活
- NestJS 官方集成 — TypeORM 是 NestJS 官方推荐的 ORM,文档和示例更完善
注意事项:TypeORM 0.3 的 API 和 0.2 有 Breaking Changes(比如 getConnection() 改为 DataSource)。老项目升级要注意。
MySQL2 3.9:MySQL 驱动
MySQL2 是 Node.js 生态中最流行的 MySQL 驱动,TypeORM 默认使用它。
关键特性:
- Promise API(不用 callback 地狱)
- 连接池管理
- Prepared Statements(防 SQL 注入)
- 流式查询(大数据量不撑爆内存)
// 流式查询示例(百万级数据导出)
const query = connection
.query('SELECT * FROM orders WHERE created_at > ?', [startDate])
.stream({ highWaterMark: 10 })
query.on('data', (row) => {
// 逐行处理,不会一次性加载到内存
processRow(row)
})
query.on('end', () => {
console.log('导出完成')
})
MySQL 8.0:数据库的现代标准
MySQL 5.7 已经在 2023 年 10 月停止官方支持(EOL)。新项目没有理由用 5.7。
MySQL 8.0 的核心优势:
- CTE(Common Table Expression) — 用
WITH子句写递归查询,不用嵌套子查询 - 窗口函数 —
ROW_NUMBER()、RANK()、LAG()等,分析查询不需要自连接 - JSON 数据类型增强 — 支持 JSON 路径查询、JSON 函数,半结构化数据可以直接存 JSON 列
- 原子 DDL —
ALTER TABLE操作是原子的,不会中途失败导致表损坏 - 性能提升 — 读性能提升 2 倍+,写性能提升 1.5 倍+
-- CTE 示例:查询每个客户最近一笔订单
WITH RankedOrders AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY created_at DESC) as rn
FROM orders
)
SELECT * FROM RankedOrders WHERE rn = 1;
迁移建议:如果现有系统用 MySQL 5.7,升级到 8.0 前注意检查:
- 保留字变化(
RANK、GROUPS、PERSIST在 8.0 是保留字) utf8改为utf8mb4(5.7 的utf8是伪 UTF-8,不支持 emoji)- 默认认证插件从
mysql_native_password改为caching_sha2_password
缓存与任务队列
Redis 8:缓存的标准答案
Redis 8 是最新版本(2025 年发布),但 Redis 7.x 已经非常成熟。两者在缓存场景下差异不大。
在这套技术栈中的用途:
- 接口缓存 — 高频查询结果缓存,减少数据库压力
- 会话存储 — 分布式部署时,用户 session 存在 Redis 而不是内存
- 分布式锁 — 防止并发操作导致的数据不一致
- Bull 任务队列 — 基于 Redis 的异步任务系统
// 接口缓存示例
const cacheKey = `list:${JSON.stringify(query)}`
const cached = await redis.get(cacheKey)
if (cached) return JSON.parse(cached)
const data = await service.query(query)
await redis.setex(cacheKey, 300, JSON.stringify(data)) // 缓存 5 分钟
return data
注意事项:Redis 8 引入了新的 ACL 权限系统,如果从旧版本升级,注意检查用户权限配置。
Bull 4.15:基于 Redis 的任务队列
Bull 是 Node.js 生态中最成熟的 Redis 任务队列。
企业 SaaS 中的应用场景:
- 文档生成 — 批量导入后异步生成 PDF,不阻塞用户操作
- 邮件发送 — 完成后自动发送邮件,失败自动重试
- 数据同步 — 定时从第三方系统拉取数据,异步处理
- 异常预警 — 超时未处理时自动触发预警通知
// 任务队列定义
const queue = new Bull('document-queue', {
redis: { host: 'localhost', port: 6379 }
})
// 添加任务
await queue.add(
{ recordId: 'REC-001', type: 'generate-pdf' },
{
attempts: 3, // 失败重试 3 次
backoff: { type: 'exponential', delay: 5000 }, // 指数退避
delay: 1000 * 60 * 5 // 延迟 5 分钟执行
}
)
// 处理任务
queue.process(async (job) => {
const { recordId, type } = job.data
if (type === 'generate-pdf') {
return await documentService.generatePDF(recordId)
}
})
为什么不用 RabbitMQ? RabbitMQ 功能更强大,支持消息确认、死信队列、多种 Exchange 类型。但对于企业 SaaS 的规模,Redis + Bull 已经够用,而且不需要额外部署和维护一个消息中间件。
AI 能力
Qdrant 1.12:向量数据库
Qdrant 是这套技术栈里"最有未来感"的组件。它不是传统数据库,而是向量数据库,专门处理高维向量(embedding)的相似度搜索。
在企业 SaaS 中的应用:
- 自然语言查询 — 用户输入"上周发往深圳的电子产品订单",系统通过语义理解匹配相关数据
- 智能客服 — 用户提问后系统理解意图并返回对应结果
- 相似记录推荐 — 基于历史数据的向量表示,推荐相似记录
// 存储向量
await qdrantClient.upsert('records', {
points: [{
id: 'REC-001',
vector: [0.12, -0.34, 0.56, ...], // 1024 维向量
payload: {
recordNo: 'REC-001',
customer: '某某贸易公司',
route: 'A 地 → B 地',
status: '处理中'
}
}]
})
// 语义搜索
const results = await qdrantClient.search('records', {
vector: queryEmbedding,
limit: 10,
filter: {
must: [{ key: 'status', match: { value: '处理中' } }]
}
})
为什么选 Qdrant 而不是其他?
| 方案 | 优势 | 劣势 |
|---|---|---|
| Qdrant | 开源、Rust 编写性能强、过滤查询好 | 社区相对小 |
| Milvus | 功能最全、分布式支持好 | 部署复杂 |
| Pinecone | 托管服务省心 | 闭源、按量付费 |
| Weaviate | GraphQL 接口友好 | 资源占用大 |
对于本地部署 + 开源优先的场景,Qdrant 是最佳选择。性能强、部署简单、API 清晰。
向量模型推荐:BGE-M3(1024 维,中英文都强,Ollama 原生支持)。
对象存储
阿里云 OSS SDK 4.x
企业 SaaS 需要存储大量文件:PDF 文档、图片、合同扫描件、报表导出。这些文件不适合存数据库,应该用对象存储。
用途:
- 文档和单据存储
- 用户上传的附件管理
- 系统生成的报表导出文件
- 静态资源 CDN 加速
import OSS from 'ali-oss'
const client = new OSS({
region: 'oss-cn-hangzhou',
accessKeyId: process.env.OSS_ACCESS_KEY,
accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET,
bucket: 'my-app-files'
})
// 上传文件
async function uploadFile(key: string, file: Buffer) {
const result = await client.put(key, file, {
headers: {
'Content-Disposition': `attachment; filename="${encodeURIComponent(key)}"`
}
})
return result.url
}
// 生成下载链接(带签名,有效期 1 小时)
async function getDownloadUrl(key: string) {
return client.signatureUrl(key, {
expires: 3600
})
}
注意事项:
- 使用签名 URL 而不是直接暴露文件地址
- 敏感文件设置
Content-Disposition: attachment强制下载 - 定期清理过期文件(OSS 生命周期规则)
架构全景图
┌─────────────────────────────────────────────┐
│ 前端 │
│ Vue 3.5 + TypeScript 6.0 + Vite 6 │
│ Element Plus 2.13 + Tailwind CSS 3.4 │
│ Pinia 3.0 + Vue Router 4 + Vue-i18n 10 │
└──────────────┬──────────────────────────────┘
│ HTTP / WebSocket
▼
┌─────────────────────────────────────────────┐
│ 后端 │
│ NestJS 10.3 + TypeScript 6.0 │
│ TypeORM 0.3 + MySQL2 3.9 │
└────┬──────────┬──────────┬──────────┬───────┘
│ │ │ │
▼ ▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐ ┌─────────┐
│MySQL │ │ Redis │ │ Qdrant │ │阿里云OSS│
│ 8.0 │ │ 8 │ │ 1.12 │ │ SDK 4.x │
└────────┘ └───┬────┘ └────────┘ └─────────┘
│
▼
┌────────┐
│ Bull │
│ 4.15 │
└────────┘
总结
这套技术栈不是"最新最炫"的堆砌,而是经过对比分析后的务实选择:
| 技术 | 选型理由 |
|---|---|
| Vue 3.5 | Composition API 成熟,国内生态强,团队上手快 |
| TypeScript 6.0 | 严格模式默认,构建更快,类型更准 |
| Vite 6 | 冷启动快,配置简单,HMR 丝滑 |
| Element Plus | 企业后台组件齐全,中文文档完善 |
| Pinia 3.0 | API 简洁,Vuex 的官方替代品 |
| NestJS 10.3 | 模块化架构,适合大型团队协作 |
| TypeORM 0.3 | NestJS 官方推荐,成熟稳定 |
| MySQL 8.0 | 5.7 已 EOL,8.0 有 CTE/窗口函数/JSON 增强 |
| Redis 8 + Bull | 缓存 + 异步任务,一套基础设施解决两个问题 |
| Qdrant 1.12 | AI 语义搜索,为自然语言查询做准备 |
| 阿里云 OSS | 文件存储 + CDN,文档管理 |
如果你有类似的企业级项目在做技术选型,希望这份分析对你有帮助。也欢迎在评论区分享你的技术栈选择和经验。
声明: 本文基于实际项目选型经验编写,技术版本截至 2026 年 4 月。技术更新快,建议选型前确认最新版本兼容性。
评论 ({{ comments.length }})
暂无评论,来说两句吧