稀土掘金技术社区 04月10日 11:21
CTO:通电全司,前端入关!
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

文章讲述了一场由前端团队主导,旨在接管公司后端服务的技术变革。面对API网关的性能瓶颈,前端团队提议使用Node.js替代Java后端,引发了一系列技术挑战和团队内部的激烈讨论。最终,在CTO的调停下,前端团队获得了后端架构的掌控权,后端开发人员也开始学习前端技术,实现了技术栈的融合。文章详细介绍了Node.js实现后端服务的过程,并对技术选型和团队协作进行了深入探讨。

🚀 导火索:由于后端API网关在促销活动中出现性能瓶颈,前端团队提议使用Node.js替代Java后端,以提升系统响应速度。

⚡️ 闪电入关:前端团队在凌晨上线了Node.js服务,但很快遭遇了单线程性能瓶颈和JWT令牌解密问题,后端团队随即展开反击。

💥 技术巷战:围绕Node.js、Java、数据库、缓存、架构等多个层面,前端与后端团队展开了激烈的技术辩论,暴露了各自的技术优劣势。

🤝 全栈和解:在CTO的调停下,前端团队获得了后端架构师的权限,后端开发人员开始学习前端技术,最终实现了技术栈的融合,并强调了技术选型不应是语言宗教。

原创 VeryCool 2025-04-10 08:32 重庆

关注更多AI编程资讯请去AI Coding专区:https://juejin.cn/aicoding


"2025 年 3 月 8 日,公司 CTO 一纸和平通电发出,200 前端再度入关,全面接管公司后端服务,公司近代史上规模最大的一次前后端混战也随之即将结束。"

3月8日夜,整个公司都在为妇女节活动做着最后的冲刺,但我们开发团队却暗流涌动,最终我们在前端 Leader 的带领下全面夺权,发动闪电战,全面接管后端服务,将公司技术架构推向全栈。

兵变导火索

公司的核心系统主要使用 Java 编写,后端 API 网关是用 Spring Boot 开发的。每次流量激增时,API 网关的响应速度都变得迟钝,出现大量超时和 5xx 错误,有很深的隐患。

那天中午,公司发布了一个 3.8 运营活动,并进行大规模的促销活动。随着流量暴增,API 网关开始出现了瓶颈——请求的处理时间变得越来越长。很快,线上系统发生了 「500 错误」,大多数客户的请求都未能及时响应,导致大量订单丢失和客户流失。

闪电入关

CTO 正急的焦头烂额,这时候作为应届生的我站出来了:「" 我提议,我们应该采用Node.js来替代目前的后端 API 网关。"」

所有人都转过来看着我,我继续说道:「“Node.js 的非阻塞异步 I/O 能处理更多的请求,而且启动速度快,内存占用少。我们可以利用它的优势,提升系统的响应能力,特别是在高并发的情况下。”」

「"Node.js 真的能处理我们的高并发请求吗?它的单线程模型会不会导致性能问题?"」 CTO 提出了自己的疑问。

我微笑着回应道:「“我理解你们的担忧,Node.js 确实在某些场景下会遇到单线程性能瓶颈,但它的异步非阻塞 I/O 设计让它能够非常高效地处理大量并发请求。如果处理的是 I/O 密集型的任务,它能够比 Java 更好地表现。”」

CTO 眉头紧锁:「“可是我们没有时间开发了”」

这时,我们前端 Leader 拿出我们前端内部早就开发好的 Node 服务演示给 CTO 看,等待了三年的夺权机会,终于来了。

技术巷战

前端 Node.js 服务随即在凌晨三点上线。前端团队用早已备好的脚本将流量逐步切至新系统,监控面板上的错误率曲线如悬崖般骤降。然而,胜利的欢呼尚未持续半小时,服务器 CPU 突然飙升至 90%——Node.js 的单线程弱点在密集的 JWT 令牌解密计算中暴露无遗。

后端团队的反击来得迅猛。后端 Leader 在钉钉群甩出一张火焰图:「"Node.js 连线程池都没有,硬扛加密计算就是自杀!"」 配图是 Java 线程池优雅调度的监控曲线。  前端立刻回击:「"你们的阻塞式 IO 把数据库连接池拖垮了,我们是在替你们擦屁股!"」

群聊瞬间沦为战场。后端 Leader 突然 @全体成员:「"缓存雪崩了!Redis 集群扛不住促销流量!"」

我秒回一个邪笑表情:「"Redis?我们有 localStorage!"」

后端团队炸锅:「"浏览器存储能和服务器缓存比?"」

「"至少不会因为线程阻塞让整个集群挂掉!"」 我们 Leader 补刀,顺带贴了张 Chrome DevTools 截图,「"看好了,会话存储热更新,零延迟!"」

战火蔓延到数据库层。

后端 Leader 冷笑:「"订单事务你们拿头保证?MySQL 的 ACID 特性是摆设?"」

我们 Leader 不甘示弱:「"MongoDB 的文档模型天然适合促销活动,嵌套式订单结构一把梭!"」

「"事务一致性呢?"」

「"用乐观锁和 Two-Phase Commit 照样玩得转!"」

「"那分库分表呢?"」

「"Sharding Key 配哈希路由,我们写 React 时早把树形结构玩透了!"」

正当数据库战局陷入胶着,运维组突然在群里甩出 Kubernetes 监控图:「"Node 服务内存泄漏把 Pod 挤爆了!说好的前端轻量化呢?"」

前端迅速反击:「"你们硬塞的 APM 监控探针占了 200MB 内存,真当 V8 引擎是收破烂的?"」

「"那用 Go 重写啊!"」 有人起哄。

「"不如用 Rust 把内存安全写到基因里?"」 我们反手贴上 Deno 的 Benchmark 数据。

战火突然转向架构层。

后端 Leader 祭出杀手锏:「"你们搞 BFF 层用 GraphQL,知不知道昨天促销查询穿透缓存八万次?"」

「"总比你们 RESTful 接口让客户端连环调用来得好!"」 前端亮出 Apollo 的自动持久化查询, 「"知道什么叫查询预编译吗?"」

「"那 N+1 问题呢?"」

「"DataLoader 批处理请求是摆设?要不你们教教 MyBatis 怎么拼 SQL?"」

这时 DevOps 突然参战:「"前端 Docker 镜像为什么有 2.3GB?"」

「"因为 node_modules 里藏着你们后端写的垃圾 SDK!"」 我们甩出 webpack-bundle-analyzer 图谱,「"看看这个 soap-client 占了 37MB!"」

「"那你们还启六个 PM2 进程?"」

「"不然怎么学你们 Java 用垂直分治假装高可用?"」

硝烟中忽然杀出测试组:「"全链路压测时前端 SSR 把登录服务打挂了!"」

「"还不是因为你们 Auth 服务用 Swagger 生成器连缓存头都没加!"」 我们祭出 Fiddler 抓包记录,「"302 跳转十连击很有趣?"」

整个晋西北,都乱成了一锅粥,什么TM的精锐?我就不信这个邪,我们前端打的就是精锐!

有人给前端 Node 服务提 issue 标注 「"建议改用 Java"」

我们立刻在 Spring Boot 仓库回敬 「"推荐重写为 TypeScript"」

全栈和解

CTO 眼见事态难以控制,随即说道:「“半个小时以前,刚刚通过武装调停的决议”」

并发表《和平宣言》:「“前端 Leader 兼任后端架构师,全面接管后端服务;后端开发从头开始学 HTML”」

随即端起咖啡杯冷笑:「“全栈不是语言宗教,是缝合怪艺术。”」

Node 实现后端服务

1. 初始化项目

首先,初始化一个新的 Node.js 项目并安装必要的依赖。

    ounter(lineounter(lineounter(lineounter(lineounter(lineounter(linemkdir node-api-servercd node-api-servernpm init -ynpm install express dotenv mongoose cors helmet morgan body-parser joi

    2. 项目结构

      ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(linenode-api-server/├── src/│   ├── controllers/   # 控制器层,处理业务逻辑│   ├── middlewares/   # 中间件,处理请求前后逻辑│   ├── models/        # 数据模型│   ├── routes/        # 路由层,定义接口路径│   ├── services/      # 服务层,处理与数据库交互等│   ├── utils/         # 工具类│   └── app.js         # 应用主文件├── .env               # 环境变量配置├── package.json└── server.js          # 入口文件

      3. 配置 .env 文件

        ounter(lineounter(lineounter(lineounter(linePORT=3000MONGO_URI=mongodb:

        4. 创建 server.js 文件

        这是项目的入口文件,负责启动 Express 应用。

          ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineconst express = require('express');const mongoose = require('mongoose');const dotenv = require('dotenv');const cors = require('cors');const helmet = require('helmet');const morgan = require('morgan');const bodyParser = require('body-parser');

          dotenv.config();
          const app = express();

          app.use(helmet()); app.use(cors()); app.use(morgan('dev')); app.use(bodyParser.json()); 

          mongoose.connect(process.env.MONGO_URI, {  useNewUrlParser: true,  useUnifiedTopology: true,})  .then(() => console.log('MongoDB Connected'))  .catch((err) => console.error('MongoDB connection error: ', err));

          const userRoutes = require('./src/routes/userRoutes');app.use('/api/users', userRoutes);

          app.use((req, res) => {  res.status(404).json({ message: 'Not Found' });});

          app.use((err, req, res, next) => {  console.error(err);  res.status(500).json({ message: 'Internal Server Error' });});

          const PORT = process.env.PORT || 3000;app.listen(PORT, () => {  console.log(`Server running on port ${PORT}`);});

          5. 创建 src/routes/userRoutes.js 文件

            ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineconst express = require('express');const router = express.Router();const userController = require('../controllers/userController');

            router.post('/register', userController.register);

            router.post('/login', userController.login);

            router.get('/:id', userController.getUserInfo);
            module.exports = router;

            6. 创建 src/controllers/userController.js 文件

            控制器层,负责处理业务逻辑。

              ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineconst User = require('../models/userModel');const bcrypt = require('bcrypt');const jwt = require('jsonwebtoken');

              exports.register = async (req, res) => {  try {    const { username, password } = req.body;
                      const userExists = await User.findOne({ username });    if (userExists) {      return res.status(400).json({ message: 'Username already exists' });    }
                      const hashedPassword = await bcrypt.hash(password, 10);
                      const newUser = new User({ username, password: hashedPassword });    await newUser.save();
                  res.status(201).json({ message: 'User created successfully' });  } catch (error) {    res.status(500).json({ message: 'Server error', error });  }};

              exports.login = async (req, res) => {  try {    const { username, password } = req.body;
                  const user = await User.findOne({ username });    if (!user) {      return res.status(404).json({ message: 'User not found' });    }
                  const validPassword = await bcrypt.compare(password, user.password);    if (!validPassword) {      return res.status(400).json({ message: 'Invalid password' });    }
                  const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '1h' });
                  res.status(200).json({ message: 'Login successful', token });  } catch (error) {    res.status(500).json({ message: 'Server error', error });  }};

              exports.getUserInfo = async (req, res) => {  try {    const user = await User.findById(req.params.id);    if (!user) {      return res.status(404).json({ message: 'User not found' });    }
                  res.status(200).json({ username: user.username });  } catch (error) {    res.status(500).json({ message: 'Server error', error });  }};

              7. 创建 src/models/userModel.js 文件

              Mongoose 数据模型定义层。

                ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineconst mongoose = require('mongoose');
                const userSchema = new mongoose.Schema({  username: {    type: String,    required: true,    unique: true,    minlength: 3,    maxlength: 50,  },  password: {    type: String,    required: true,    minlength: 6,  },}, { timestamps: true });
                module.exports = mongoose.model('User', userSchema);

                8. 创建 src/middlewares/authMiddleware.js 文件

                一个验证用户身份的中间件,可以用于需要身份认证的接口。

                  ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineconst jwt = require('jsonwebtoken');

                  module.exports = (req, res, next) => {  const token = req.header('Authorization');  if (!token) {    return res.status(401).json({ message: 'No token, authorization denied' });  }
                    try {    const decoded = jwt.verify(token, process.env.JWT_SECRET);    req.user = decoded;    next();  } catch (error) {    res.status(401).json({ message: 'Token is not valid' });  }};

                  9. 启动服务

                    ounter(lineounter(lineounter(linenode server.js

                    10. 测试接口

                      「POST」 /api/users/register - 注册用户
                      「POST」 /api/users/login - 登录并返回 JWT token
                      「GET」 /api/users/:id - 获取用户信息

                    后记

                    夕阳透过落地窗洒在 Git 提交记录上,那些 "紧急修复" 的 commit message 里,夹杂着一条 Node 服务调用 Java 方法的日志:

                    // 真香警告:线程池其实挺好用的

                    而某个 Spring Boot 配置文件的注释栏赫然写着:

                    @TODO 考虑移植到Deno

                    点击关注公众号,“技术干货” 及时达!

                    阅读原文

                    跳转微信打开

                    Fish AI Reader

                    Fish AI Reader

                    AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

                    FishAI

                    FishAI

                    鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

                    联系邮箱 441953276@qq.com

                    相关标签

                    Node.js 前后端 技术变革 团队协作
                    相关文章