第 10 课:Node.js 后端企业级进阶 —— 任务管理系统后端优化与功能增强(续)

一、核心实操一:全局错误处理 ------ 统一捕获异常,简化错误处理

上一课中,我们通过try/catch捕获接口异常,但分散在每个接口中,代码冗余且易遗漏。Express 支持全局错误处理中间件,可集中捕获所有未处理的异常,统一返回错误响应,提升代码可维护性。

1. 创建全局错误处理中间件(middleware/errorHandler.js)

javascript

运行

复制代码
const logger = require('../config/logger');
const { errorResponse } = require('./response');

// 全局错误处理中间件(必须接收err, req, res, next四个参数)
const errorHandler = (err, req, res, next) => {
  // 记录错误日志(包含请求信息,便于排查)
  logger.error(`[全局错误] 请求路径:${req.method} ${req.originalUrl} | 错误信息:${err.message} | 堆栈信息:${err.stack}`);

  // 区分错误类型,返回对应状态码
  if (err.name === 'ValidationError') {
    // Mongoose数据验证错误(如模型字段必填、格式错误)
    return errorResponse(res, err.message, 400);
  } else if (err.name === 'JsonWebTokenError') {
    // JWT令牌错误(如无效令牌、令牌过期)
    return errorResponse(res, '令牌无效,请重新登录', 401);
  } else if (err.name === 'TokenExpiredError') {
    // JWT令牌过期错误
    return errorResponse(res, '令牌已过期,请重新登录', 401);
  } else if (err.statusCode) {
    // 自定义错误(携带状态码)
    return errorResponse(res, err.message, err.statusCode);
  } else {
    // 未知错误,返回500服务器错误
    return errorResponse(res, '服务器内部错误', 500);
  }
};

module.exports = errorHandler;

2. 在入口文件中注册全局错误处理中间件(app.js)

javascript

运行

复制代码
const express = require('express');
const cors = require('cors');
const connectDB = require('./config/db');
const { successResponse } = require('./middleware/response');
const logger = require('./config/logger');
const path = require('path');
const setupSwagger = require('./config/swagger');
const errorHandler = require('./middleware/errorHandler'); // 引入全局错误处理
require('dotenv').config();

connectDB();

const app = express();
const PORT = process.env.PORT || 3000;

// 跨域配置(生产环境优化)
const corsOptions = {
  origin: process.env.NODE_ENV === 'production' 
    ? process.env.FRONTEND_DOMAIN // 前端线上域名(如https://vue-task-system.com)
    : '*' // 开发环境允许所有域名
};
app.use(cors(corsOptions));

// 中间件配置
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));

// 启用Swagger文档
setupSwagger(app);

// 测试接口
app.get('/', (req, res) => {
  successResponse(res, { message: 'Vue任务管理系统后端服务运行中' }, '服务正常');
});

// 挂载路由
app.use('/api/users', require('./routes/userRoutes'));
app.use('/api/categories', require('./routes/categoryRoutes'));
app.use('/api/todos', require('./routes/todoRoutes'));

// 404接口(放在路由之后,全局错误处理之前)
app.use('*', (req, res, next) => {
  const err = new Error('接口不存在');
  err.statusCode = 404;
  next(err); // 传递给全局错误处理中间件
});

// 注册全局错误处理中间件(必须放在所有路由和中间件之后)
app.use(errorHandler);

// 启动服务
app.listen(PORT, () => {
  logger.info(`后端服务启动成功,运行在端口:${PORT} | 环境:${process.env.NODE_ENV || 'development'}`);
});

3. 简化接口错误处理(移除冗余 try/catch)

以用户登录接口为例(routes/userRoutes.js),移除try/catch,直接抛出错误:

javascript

运行

复制代码
// 2. 用户登录
router.post('/login', async (req, res, next) => {
  // 校验请求参数
  const { error } = loginSchema.validate(req.body);
  if (error) {
    const err = new Error(error.details[0].message);
    err.statusCode = 400;
    return next(err); // 传递给全局错误处理中间件
  }

  const { username, password } = req.body;
  const user = await User.findOne({ username });
  if (!user) {
    const err = new Error('用户名不存在');
    err.statusCode = 400;
    return next(err);
  }

  const isPasswordValid = await verifyPassword(password, user.password);
  if (!isPasswordValid) {
    const err = new Error('密码错误');
    err.statusCode = 400;
    return next(err);
  }

  // 成功响应(无需try/catch,异常会被全局捕获)
  successResponse(res, {
    id: user._id,
    username: user.username,
    email: user.email,
    avatar: user.avatar,
    token: generateToken(user._id)
  }, '登录成功');
});

4. 测试全局错误处理

  • 访问不存在的接口(如/api/xxx),返回接口不存在(404);
  • 传递无效 JWT 令牌,返回令牌无效,请重新登录(401);
  • 数据库查询异常(如连接失败),返回服务器内部错误(500),并在日志中记录详细堆栈信息。

二、核心实操二:缓存机制 ------Redis 优化热点数据查询

对于频繁访问但不常变化的数据(如用户信息、分类列表),每次从数据库查询会浪费资源。使用 Redis 缓存热点数据,可大幅提升接口响应速度,减轻数据库压力。

1. 安装 Redis 与依赖

(1)安装 Redis
  • 本地安装:下载地址https://redis.io/download/(Windows 用户可使用 Redis Desktop Manager);
  • 云服务:使用阿里云 Redis、腾讯云 Redis 等,无需本地安装。
(2)安装 Redis 依赖

bash

运行

复制代码
npm install redis -S

2. Redis 配置(config/redis.js)

javascript

运行

复制代码
const { createClient } = require('redis');
const logger = require('./logger');
require('dotenv').config();

// 创建Redis客户端
const redisClient = createClient({
  url: process.env.REDIS_URL || 'redis://127.0.0.1:6379', // Redis连接地址
  // 密码(若Redis设置了密码,添加以下配置)
  // password: process.env.REDIS_PASSWORD
});

// 连接Redis
redisClient.connect().then(() => {
  logger.info('Redis连接成功');
}).catch((error) => {
  logger.error(`Redis连接失败:${error.message}`);
});

// 缓存工具函数
const redis = {
  // 设置缓存(expireSeconds:过期时间,单位秒)
  set: async (key, value, expireSeconds) => {
    try {
      const data = typeof value === 'object' ? JSON.stringify(value) : value;
      await redisClient.set(key, data);
      if (expireSeconds) {
        await redisClient.expire(key, expireSeconds);
      }
      return true;
    } catch (error) {
      logger.error(`Redis设置缓存失败:key=${key} | 错误:${error.message}`);
      return false;
    }
  },

  // 获取缓存
  get: async (key) => {
    try {
      const data = await redisClient.get(key);
      if (!data) return null;
      // 尝试解析JSON(处理对象类型缓存)
      try {
        return JSON.parse(data);
      } catch (e) {
        return data;
      }
    } catch (error) {
      logger.error(`Redis获取缓存失败:key=${key} | 错误:${error.message}`);
      return null;
    }
  },

  // 删除缓存
  del: async (key) => {
    try {
      await redisClient.del(key);
      return true;
    } catch (error) {
      logger.error(`Redis删除缓存失败:key=${key} | 错误:${error.message}`);
      return false;
    }
  }
};

module.exports = redis;

3. 在接口中使用 Redis 缓存

以 "获取用户信息" 接口为例(routes/userRoutes.js),缓存用户信息:

javascript

运行

复制代码
const redis = require('../config/redis'); // 引入Redis工具

// 3. 获取当前用户信息(需要身份验证)
router.get('/me', protect, async (req, res, next) => {
  const userId = req.user.id;
  const cacheKey = `user:${userId}`;

  // 1. 先从Redis获取缓存
  const cachedUser = await redis.get(cacheKey);
  if (cachedUser) {
    logger.info(`用户${userId}从缓存获取信息`);
    return successResponse(res, cachedUser, '获取用户信息成功');
  }

  // 2. 缓存不存在,从数据库查询
  const user = await User.findById(userId).select('-password');
  if (!user) {
    const err = new Error('用户不存在');
    err.statusCode = 400;
    return next(err);
  }

  // 3. 存入Redis缓存(过期时间1小时)
  await redis.set(cacheKey, user, 3600);
  logger.info(`用户${userId}从数据库获取信息并缓存`);

  successResponse(res, user, '获取用户信息成功');
});

4. 缓存更新与删除(保证数据一致性)

当用户信息更新(如修改头像、密码)时,需要同步删除缓存,避免返回旧数据:

javascript

运行

复制代码
// 4. 上传用户头像(修改后删除缓存)
router.post('/avatar', protect, upload.single('avatar'), handleUploadError, async (req, res, next) => {
  if (!req.file) {
    const err = new Error('请选择要上传的头像');
    err.statusCode = 400;
    return next(err);
  }

  const userId = req.user.id;
  const avatarUrl = `/uploads/avatars/${req.file.filename}`;
  const cacheKey = `user:${userId}`;

  // 更新用户头像
  const user = await User.findByIdAndUpdate(
    userId,
    { avatar: avatarUrl },
    { new: true }
  ).select('-password');

  if (!user) {
    const err = new Error('用户不存在');
    err.statusCode = 400;
    return next(err);
  }

  // 删除Redis缓存(下次请求将从数据库获取最新数据)
  await redis.del(cacheKey);
  logger.info(`用户${userId}更新头像,删除缓存`);

  successResponse(res, { avatar: user.avatar }, '头像上传成功');
});

5. 测试缓存效果

  • 首次访问/api/users/me:从数据库查询,响应时间约 100ms,日志显示 "从数据库获取并缓存";
  • 1 小时内再次访问:从 Redis 缓存获取,响应时间约 10ms,日志显示 "从缓存获取";
  • 上传头像后访问:缓存被删除,从数据库获取最新头像信息。

三、核心实操三:接口限流 ------ 防止恶意请求攻击

生产环境中,接口可能遭受恶意请求(如暴力破解、爬虫爬取),导致服务器压力过大。使用express-rate-limit实现接口限流,限制单位时间内的请求次数。

1. 安装限流依赖

bash

运行

复制代码
npm install express-rate-limit -S

2. 限流配置(middleware/rateLimit.js)

javascript

运行

复制代码
const rateLimit = require('express-rate-limit');
const logger = require('../config/logger');

// 通用限流配置(针对所有接口)
const globalRateLimit = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分钟
  max: 100, // 每个IP最多100次请求
  message: { code: 429, message: '请求过于频繁,请15分钟后再试', data: null },
  standardHeaders: true, // 启用标准限流响应头
  legacyHeaders: false, // 禁用旧版限流响应头
  // 记录限流日志
  skipFailedRequests: true, // 只记录成功的请求
  handler: (req, res, next, options) => {
    logger.warn(`IP ${req.ip} 请求过于频繁,已被限流 | 路径:${req.method} ${req.originalUrl}`);
    res.status(options.statusCode).json(options.message);
  }
});

// 登录接口限流(更严格,防止暴力破解)
const loginRateLimit = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分钟
  max: 5, // 每个IP最多5次登录请求
  message: { code: 429, message: '登录请求过于频繁,请15分钟后再试', data: null },
  standardHeaders: true,
  legacyHeaders: false,
  handler: (req, res, next, options) => {
    logger.warn(`IP ${req.ip} 登录请求过于频繁,已被限流`);
    res.status(options.statusCode).json(options.message);
  }
});

module.exports = {
  globalRateLimit,
  loginRateLimit
};

3. 在入口文件中使用限流中间件(app.js)

javascript

运行

复制代码
const { globalRateLimit, loginRateLimit } = require('./middleware/rateLimit'); // 引入限流配置

// 全局限流(所有接口生效)
app.use(globalRateLimit);

// 测试接口
app.get('/', (req, res) => {
  successResponse(res, { message: 'Vue任务管理系统后端服务运行中' }, '服务正常');
});

// 登录接口单独限流(放在路由挂载前,优先级高于全局限流)
app.use('/api/users/login', loginRateLimit);

// 挂载路由
app.use('/api/users', require('./routes/userRoutes'));
app.use('/api/categories', require('./routes/categoryRoutes'));
app.use('/api/todos', require('./routes/todoRoutes'));

4. 测试限流效果

  • 15 分钟内多次访问登录接口(超过 5 次),返回 "登录请求过于频繁,请 15 分钟后再试";
  • 15 分钟内访问其他接口超过 100 次,返回 "请求过于频繁,请 15 分钟后再试";
  • 日志中记录限流信息,便于监控恶意请求。

四、核心实操四:数据库索引优化 ------ 提升查询效率

当数据库数据量较大时(如 10 万 + 条任务),未优化的查询会变慢。MongoDB 索引可大幅提升查询速度,针对常用查询字段创建索引是企业级项目的必备优化。

1. 为数据模型添加索引(models/Todo.js、models/Category.js)

(1)任务模型索引(models/Todo.js)

javascript

运行

复制代码
const mongoose = require('mongoose');
const mongoosePaginate = require('mongoose-paginate-v2');

const todoSchema = new mongoose.Schema({
  title: {
    type: String,
    required: [true, '任务内容不能为空'],
    trim: true
  },
  completed: {
    type: Boolean,
    default: false
  },
  categoryId: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Category',
    required: [true, '分类ID不能为空'],
    index: true // 为分类查询字段创建索引
  },
  userId: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
    required: [true, '用户ID不能为空'],
    index: true // 为用户查询字段创建索引
  },
  createTime: {
    type: Date,
    default: Date.now,
    index: true // 为排序字段创建索引
  }
}, {
  timestamps: true
});

// 复合索引(针对多条件查询,如按用户+分类+完成状态查询)
todoSchema.index({ userId: 1, categoryId: 1, completed: 1 });

todoSchema.plugin(mongoosePaginate);
const Todo = mongoose.model('Todo', todoSchema);

module.exports = Todo;
(2)分类模型索引(models/Category.js)

javascript

运行

复制代码
const mongoose = require('mongoose');

const categorySchema = new mongoose.Schema({
  name: {
    type: String,
    required: [true, '分类名称不能为空'],
    unique: true,
    trim: true
  },
  userId: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
    required: [true, '用户ID不能为空'],
    index: true // 为用户查询字段创建索引
  },
  createTime: {
    type: Date,
    default: Date.now
  }
}, {
  timestamps: true
});

// 复合索引(确保同一用户下分类名称唯一,配合unique: true)
categorySchema.index({ userId: 1, name: 1 }, { unique: true });

const Category = mongoose.model('Category', categorySchema);
module.exports = Category;

2. 查看索引与查询优化效果

(1)查看索引

使用 MongoDB Compass 连接数据库,进入对应集合(如todos),点击 "Indexes" 可查看创建的索引。

(2)测试查询效率
  • 未创建索引:查询 10 万条任务中 "用户 A 的未完成任务",耗时约 500ms;
  • 创建索引后:相同查询耗时约 50ms,查询效率提升 10 倍。

3. 索引优化原则

  • 只为常用查询字段创建索引(如userIdcategoryId);
  • 避免为频繁修改的字段创建索引(索引会增加写入开销);
  • 复合索引的字段顺序需符合查询习惯(如先按userId查询,再按categoryId);
  • 定期删除无用索引(通过 MongoDB 的explain()分析查询是否使用索引)。

五、核心实操五:单元测试 ------ 确保接口稳定性

企业级项目需要通过单元测试验证接口功能,避免后续修改代码导致功能异常。使用Jest+supertest实现接口单元测试。

1. 安装测试依赖

bash

运行

复制代码
npm install jest supertest -D

2. 配置测试脚本(package.json)

json

复制代码
{
  "scripts": {
    "start": "node app.js",
    "dev": "nodemon app.js",
    "prod": "NODE_ENV=production node app.js",
    "test": "jest --runInBand --detectOpenHandles" // 新增测试脚本
  },
  "jest": {
    "testEnvironment": "node",
    "testMatch": ["**/__tests__/**/*.test.js"] // 测试文件路径
  }
}

3. 创建测试文件夹与测试文件

新建__tests__/api/user.test.js,测试用户相关接口:

javascript

运行

复制代码
const request = require('supertest');
const app = require('../../app');
const User = require('../../models/User');
const mongoose = require('mongoose');
const redis = require('../../config/redis');

// 测试前连接数据库,测试后清理数据
beforeAll(async () => {
  await mongoose.connect(process.env.MONGODB_URL);
});

afterAll(async () => {
  // 清理测试数据
  await User.deleteMany({ username: 'testuser_test' });
  await redis.del('user:test');
  await mongoose.disconnect();
});

// 测试用户注册接口
describe('POST /api/users/register', () => {
  it('should register a new user', async () => {
    const res = await request(app)
      .post('/api/users/register')
      .send({
        username: 'testuser_test',
        password: '123456',
        email: 'testuser_test@163.com'
      });

    expect(res.statusCode).toBe(200);
    expect(res.body.code).toBe(200);
    expect(res.body.message).toBe('注册成功');
    expect(res.body.data).toHaveProperty('token');
    expect(res.body.data.username).toBe('testuser_test');
  });

  it('should return 400 if username already exists', async () => {
    const res = await request(app)
      .post('/api/users/register')
      .send({
        username: 'testuser_test',
        password: '123456',
        email: 'testuser_test2@163.com'
      });

    expect(res.statusCode).toBe(400);
    expect(res.body.code).toBe(400);
    expect(res.body.message).toBe('用户名已存在');
  });
});

// 测试用户登录接口
describe('POST /api/users/login', () => {
  it('should login successfully with correct credentials', async () => {
    const res = await request(app)
      .post('/api/users/login')
      .send({
        username: 'testuser_test',
        password: '123456'
      });

    expect(res.statusCode).toBe(200);
    expect(res.body.code).toBe(200);
    expect(res.body.message).toBe('登录成功');
    expect(res.body.data).toHaveProperty('token');
  });

  it('should return 400 with incorrect password', async () => {
    const res = await request(app)
      .post('/api/users/login')
      .send({
        username: 'testuser_test',
        password: '1234567'
      });

    expect(res.statusCode).toBe(400);
    expect(res.body.code).toBe(400);
    expect(res.body.message).toBe('密码错误');
  });
});

4. 运行单元测试

bash

运行

复制代码
npm run test

5. 测试结果说明

  • 绿色对勾:测试通过,接口功能正常;
  • 红色叉号:测试失败,需检查接口逻辑;
  • 每次修改核心接口后,建议运行单元测试,确保功能未被破坏。

六、综合实战:后端安全加固与生产环境部署

1. 安全加固补充

(1)接口签名验证(防止接口被篡改)

为敏感接口(如修改密码、转账)添加签名验证,前端通过 "参数 + 密钥 + 时间戳" 生成签名,后端验证签名合法性:

javascript

运行

复制代码
// utils/sign.js
const crypto = require('crypto');
require('dotenv').config();

const SIGN_SECRET = process.env.SIGN_SECRET || 'vue_task_system_sign_secret';

// 生成签名(前端使用)
const generateSign = (params, timestamp) => {
  // 按参数名排序
  const sortedParams = Object.keys(params).sort().reduce((obj, key) => {
    obj[key] = params[key];
    return obj;
  }, {});
  // 拼接参数+时间戳+密钥
  const signStr = `${JSON.stringify(sortedParams)}${timestamp}${SIGN_SECRET}`;
  // SHA256加密
  return crypto.createHash('sha256').update(signStr).digest('hex');
};

// 验证签名(后端使用)
const verifySign = (params, timestamp, sign) => {
  // 验证时间戳(防止签名过期,有效期5分钟)
  if (Date.now() - timestamp > 5 * 60 * 1000) {
    return false;
  }
  // 生成签名并对比
  const generatedSign = generateSign(params, timestamp);
  return generatedSign === sign;
};

module.exports = {
  generateSign,
  verifySign
};
(2)HTTPS 配置(生产环境必须)
  • 购买 SSL 证书(阿里云、腾讯云免费证书);
  • app.js中配置 HTTPS:

javascript

运行

复制代码
const https = require('https');
const fs = require('fs');
const path = require('path');

// 生产环境启用HTTPS
if (process.env.NODE_ENV === 'production') {
  const options = {
    key: fs.readFileSync(path.join(__dirname, 'cert', 'private.key')), // 私钥路径
    cert: fs.readFileSync(path.join(__dirname, 'cert', 'cert.pem')) // 证书路径
  };
  // 用HTTPS启动服务
  https.createServer(options, app).listen(443, () => {
    logger.info('HTTPS服务启动成功,运行在端口:443');
  });
  // HTTP重定向到HTTPS
  app.listen(80, () => {
    logger.info('HTTP服务启动成功,自动重定向到HTTPS');
    app.use((req, res) => {
      res.redirect(`https://${req.headers.host}${req.url}`);
    });
  });
} else {
  // 开发环境HTTP启动
  app.listen(PORT, () => {
    logger.info(`后端服务启动成功,运行在端口:${PORT}`);
  });
}

2. 生产环境部署完整流程(阿里云 ECS 为例)

(1)服务器环境准备
  1. 安装 Node.js、MongoDB、Redis、PM2;
  2. 配置防火墙(开放 80、443、3000 端口);
  3. 上传后端项目文件(使用 FTP 或 Git)。
(2)部署步骤
  1. 进入项目目录,安装依赖:

bash

运行

复制代码
npm install --production
  1. 配置.env.production文件:

env

复制代码
PORT=3000
NODE_ENV=production
MONGODB_URL=mongodb://127.0.0.1:27017/vue_task_system_prod
REDIS_URL=redis://127.0.0.1:6379
JWT_SECRET=vue_task_system_backend_prod_secret
JWT_EXPIRES_IN=86400s
FRONTEND_DOMAIN=https://your-frontend-domain.com
SIGN_SECRET=vue_task_system_sign_secret_prod
  1. 上传 SSL 证书到cert文件夹;
  2. 用 PM2 启动服务:

bash

运行

复制代码
pm2 start app.js --name vue-task-system-backend
pm2 startup
pm2 save
  1. 测试接口:访问https://your-backend-domain.com/api/users/login,验证服务正常。

七、本节课总结与后续学习建议

1. 本节课核心收获

  • 工程化能力:掌握全局错误处理、单元测试、接口文档自动化,让后端项目更规范、易维护;
  • 性能优化:学会 Redis 缓存、数据库索引优化、接口限流,提升服务响应速度和稳定性;
  • 安全加固:实现 HTTPS、接口签名、JWT 令牌验证,抵御常见网络攻击;
  • 生产部署:掌握云服务器部署流程、PM2 进程守护,让服务具备生产环境运行能力。

2. 课后作业(必做)

  1. 为所有核心接口添加单元测试(任务、分类接口);
  2. 实现 "修改密码" 接口,并添加签名验证和缓存更新;
  3. 为生产环境配置 HTTPS,确保所有接口通过 HTTPS 访问;
  4. 对数据库所有常用查询字段创建索引,并用explain()验证查询是否使用索引。

3. 后续后端学习建议

  • 框架进阶:学习 NestJS(企业级 Node.js 框架),掌握模块化、依赖注入、拦截器等高级特性;
  • 数据库深化:学习 MongoDB 事务、聚合查询,或 MySQL/PostgreSQL 关系型数据库及 ORM 工具(Sequelize/TypeORM);
  • 微服务与云原生:学习 Docker 容器化、Kubernetes 编排、微服务拆分(如用户服务、任务服务)、服务注册与发现(Nacos/Consul);
  • 高级架构:学习消息队列(RabbitMQ/Kafka)解耦服务、分布式缓存(Redis 集群)、分布式事务;
  • 性能监控:学习 Prometheus+Grafana 监控服务指标、ELK 栈(Elasticsearch+Logstash+Kibana)分析日志。

至此,Node.js 后端企业级进阶课程已全部结束!你已掌握从基础接口开发到生产环境部署、性能优化、安全加固的完整后端开发能力,能够独立设计和开发中小型企业级后端服务。后端开发的核心是 "稳定性、性能、安全性",后续需通过实际项目持续积累经验,不断深化技术栈,成为一名具备架构设计能力的资深后端开发工程师!

相关推荐
达之云*驭影2 小时前
达之云驭影:深耕本地生活服务,以数字化赋能千行百业
大数据
华仔啊2 小时前
JavaScript 有哪些数据类型?它们在内存里是怎么存的?
前端·javascript
维构lbs智能定位2 小时前
融合定位室内外系统:从核心架构、技术原理到部署实施流程等详解(四)
大数据·架构·融合定位室内外系统
我有一棵树2 小时前
淘宝 npm 镜像与 CDN 加速链路解析:不只是 Registry,更是分层静态加速架构
前端·架构·npm
PS1232322 小时前
深入了解 智慧型PH传感器原理 把握精准监测核心
大数据
zhousenshan2 小时前
vue3基础知识100问
前端·vue.js
异界蜉蝣2 小时前
Proxy vs Object.defineProperty:Vue3响应式原理的深度革命
开发语言·前端·javascript
学术小白人2 小时前
IEEE出版|2026年人工智能与社交网络系统国际学术会议(AISNS 2026)
大数据·人工智能·科技·物联网·rdlink研发家
前端早间课2 小时前
Vue3路由实战:优雅封装+灵活拦截,解锁路由配置新姿势
前端·javascript·vue.js