技术探索与实践解决方案:从零到一重构我的第一个复杂 Web 项目

全栈打工仔
2025-06-19 11:38
阅读 429

引言:为什么我要分享这段经历?

引言:为什么我要分享这段经历?

作为一名全栈开发工程师,这几年我参与了不少前端和后端的项目。但有一个项目始终让我记忆犹新 —— 那是一次从头开始的技术选型和架构设计,整个过程中充满了挑战、踩坑和成长。我想通过这篇文章,跟你聊聊我是怎么一步步完成这个项目的,也希望通过自己的实战经验,给你一些启发和参考。

这不是一篇泛泛而谈的理论文章,而是一个真实业务场景下的技术决策和落地过程。如果你正在做一个类似的项目,或者也在思考如何构建一个可维护、易扩展的应用系统,我相信这篇文章会对你有帮助。


项目背景

项目背景

去年,我在一家初创公司负责一个内部管理后台系统的重构工作。原来的系统是基于 Vue 2 + Node.js 搭建的老项目,已经运行了两年多。随着业务增长,老系统的问题逐渐暴露出来:

  • 前端结构混乱,没有模块化设计
  • API 接口重复请求严重,性能差
  • 后端接口响应不稳定,错误处理不统一
  • 缺乏测试,改个表单都能把整个页面搞挂
  • 团队协作效率低,每次上线都像在走钢丝

于是老板拍板:必须重构!

我们决定采用 React + TypeScript 的方式重写前端,同时后端也进行一定程度的优化升级(主要是服务分层、异常统一处理和中间件优化)。目标很明确:提升开发效率、增强代码可维护性、提高系统稳定性和可扩展性。


遇到的挑战

技术原理图-1

前端部分

  1. 状态管理混乱:老系统使用 Vuex,但由于历史原因,很多组件直接操作 state,导致逻辑难以跟踪。
  2. 组件复用率低:每个功能都是独立写的,UI 组件基本没有复用。
  3. 接口调用方式不一致:有些地方用了 async/await,有些用了 Promise.then()。
  4. 样式问题:CSS 管理混乱,经常出现样式冲突或覆盖。

后端部分

  1. 接口返回格式不统一:有的返回 { code: 0, data: ... },有的返回 { status: 'success', result: ... }
  2. 鉴权机制老旧:还停留在 Cookie+Session,没有使用 JWT 或其他现代方案。
  3. 日志记录缺失:一旦出错很难定位问题。
  4. 数据库操作随意:有些 SQL 是直接拼字符串写的,容易出安全问题。

解决方案和技术选型

前端部分

技术栈选择

  • 主框架:React 18
  • 类型系统:TypeScript
  • 状态管理:Redux Toolkit(替代之前的 Redux + Immer 手动管理)
  • UI 库:Ant Design + 自己封装的一套通用组件
  • 样式方案:CSS Modules + Tailwind CSS(用于快速布局)

架构设计思路

我把整个应用分为以下几个层次:

src/
├── common/        // 公共组件、常量、工具类
├── components/    // 业务组件
├── hooks/         // 自定义 Hook
├── pages/         // 页面组件
├── store/         // 状态管理,使用 RTK
├── services/      // 接口封装
├── routes/        // 路由配置
├── utils/         // 工具函数
└── App.tsx

接口封装策略

为了统一接口调用方式,我做了这样一个封装:

// src/services/index.ts
import axios from 'axios';

const instance = axios.create({
  baseURL: process.env.REACT_APP_API_URL,
  timeout: 5000,
});

// 添加拦截器统一处理错误
instance.interceptors.response.use(
  (res) => {
    if (res.data.code === 0) {
      return res.data.data;
    } else {
      console.error('接口错误:', res.data.message);
      return Promise.reject(res.data.message);
    }
  },
  (error) => {
    console.error('网络错误:', error);
    return Promise.reject(error);
  }
);

export default instance;

然后每个模块单独封装 service:

// src/services/userService.ts
import request from '../services';

export const fetchUserInfo = () => {
  return request.get('/user/info');
};

export const login = (data: { username: string; password: string }) => {
  return request.post('/login', data);
};

状态管理优化

使用 Redux Toolkit 之后,状态更新变得特别直观:

// src/store/userSlice.ts
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { fetchUserInfo } from '../services/userService';

interface UserState {
  info: any;
  loading: boolean;
}

const initialState: UserState = {
  info: null,
  loading: false,
};

export const getUserInfo = createAsyncThunk('user/fetchUserInfo', async () => {
  const res = await fetchUserInfo();
  return res;
});

const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(getUserInfo.pending, (state) => {
        state.loading = true;
      })
      .addCase(getUserInfo.fulfilled, (state, action) => {
        state.loading = false;
        state.info = action.payload;
      });
  },
});

export default userSlice.reducer;

这样不仅提高了代码的可读性和可维护性,也让团队成员更容易上手。


后端部分

技术栈选择

  • 主框架:Node.js + Express
  • 数据库:MongoDB(原有数据),并逐步引入 MySQL
  • ORM:Mongoose + TypeORM(用于新模块)
  • 错误处理:自定义中间件统一处理异常
  • 日志系统:Winston + Morgan 记录访问日志和错误日志
  • 安全认证:JWT 替代 Session

接口统一返回格式

我们约定所有接口返回结构如下:

{
  "code": 0,
  "message": "成功",
  "data": {}
}

其中:

  • code: 0 表示成功,非 0 表示失败
  • message: 错误信息(成功时为空)
  • data: 返回的数据内容

统一返回格式后,前端处理起来也方便了很多。

错误处理中间件

我们编写了一个全局错误处理器:

app.use((err: any, req: Request, res: Response, next: NextFunction) => {
  console.error(`[Error] ${err.stack}`);

  const statusCode = err.status || 500;
  const message = err.message || 'Internal Server Error';
  
  res.status(statusCode).json({
    code: 1,
    message,
    data: null,
  });
});

这样无论哪个环节出错,都可以统一格式返回给前端。

使用 JWT 替换 Session

我们在登录接口生成 Token:

const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET!, {
  expiresIn: '1d',
});

并在中间件中校验:

function authenticateJWT(req: Request, res: Response, next: NextFunction) {
  const authHeader = req.headers.authorization;

  if (authHeader) {
    const token = authHeader.split(' ')[1];
    
    try {
      const user = jwt.verify(token, process.env.JWT_SECRET!);
      req.user = user;
      next();
    } catch (err) {
      return res.status(403).json({ code: 1, message: 'Token无效', data: null });
    }
  } else {
    res.status(401).json({ code: 1, message: '缺少Token', data: null });
  }
}

这样一来,前后端就可以轻松实现无状态鉴权。


开发中的“坑”和经验分享

坑1:TypeScript 与 Ant Design 的类型兼容问题

刚开始使用 TypeScript + Ant Design 的时候,我发现某些组件的 props 并没有自动推导出类型,特别是表格的 columns 字段,有时候会出现类型错误。

解决方法:手动为每个 column 编写类型定义,并封装成一个 type 文件统一管理:

type TableColumn<T> = TableProps<T>['columns'];

export const columns: TableColumn<User> = [
  {
    title: '用户名',
    dataIndex: 'username',
    key: 'username',
  },
  // ...
];

坑2:Redux Toolkit 和异步请求配合不好?

其实不是不好,而是我自己一开始没理解清楚。RTK 的 createAsyncThunk 需要结合 extraReducers 使用,不像以前 redux-thunk 那样 dispatch 一个 action 就完了。

后来通过阅读官方文档,才明白应该这样写:

extraReducers: (builder) => {
  builder.addCase(fetchData.fulfilled, (state, action) => {
    state.data = action.payload;
  });
}

建议:一定要多看文档,别自己瞎猜 😅

坑3:JWT 存储位置选择不当导致的安全隐患

最开始我将 Token 存在 localStorage 里,结果某天发现浏览器控制台可以轻易访问修改 Token,存在 XSS 漏洞风险。

改进方案

  • 使用 HttpOnly Cookie 存储 Token(更安全)
  • 配合 CSRF Token 防止跨站攻击

虽然前端取不到 Token,但我们可以通过封装 Axios 实现自动携带 Token:

const apiClient = axios.create({
  baseURL: '/api',
  withCredentials: true,
});

最终效果和收益

经过大约两个半月的努力,我们完成了整个系统的重构。最终上线后,系统稳定性明显提升,具体表现如下:

指标 旧系统 新系统 提升幅度
首屏加载速度 ~3s ~1.2s 60%↑
代码重复率 >40% <10% 减少75%
上线故障次数/月 5~8次 0~1次 显著减少
团队协作效率 较低 中等↑ 提升明显

除了技术指标外,团队协作体验也好了不少。新人加入后,有了清晰的目录结构和统一编码规范,上手时间大大缩短。


写在最后:几点经验总结

  1. 技术选型要根据团队能力和实际业务需求来定:不要盲目追求新技术,适合的才是最好的。
  2. 统一规范比工具更重要:无论你是用 Redux 还是 Zustand,关键是形成一套大家都遵守的开发规范。
  3. 尽早引入测试机制:我们这次重构前期没写测试,后期补起来特别痛苦。现在我会坚持单元测试 + E2E 测试双管齐下。
  4. 持续集成/部署要同步做:我们是在开发完一半才接入 CI/CD,结果返工了不少。早点搞,真香!
  5. 多写文档,哪怕是简单的 README:它真的能帮你省下很多沟通成本。
  6. 重构是个持久战,别想着一口吃成胖子:可以分阶段来做,比如先拆解模块,再逐步替换核心逻辑。

结语

这一路走来,确实不容易。但也正是这些“掉进坑里又爬出来”的经历,让我对全栈开发有了更深的理解。希望这篇来自一线实战的文章,能给你带来一些启发和帮助。

如果你也在做类似的重构工作,欢迎留言交流,说不定咱们能一起探讨更好的做法。记住,技术这条路从来都不是一个人在战斗 🤚

祝你码出优雅,写出健壮的代码。

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝