Vue 3 缓存架构设计深度指南

概述

缓存是提高系统性能的重要手段,它通过将频繁访问的数据存储在高速存储介质中,减少对慢速存储或计算资源的访问次数,从而提高系统的响应速度和吞吐量。本集将深入探讨缓存架构设计,包括缓存的基本概念、常见策略、前端缓存实现、后端缓存集成以及缓存一致性保证,帮助您构建高性能的Vue 3全栈应用。

一、缓存基本概念

1. 什么是缓存

缓存是一种临时存储机制,用于存储频繁访问的数据或计算结果,以减少对原始数据源的访问。它的核心思想是利用空间换时间,通过牺牲一定的存储空间来提高系统性能。

2. 缓存的优势

  • 提高响应速度:缓存数据位于高速存储介质,访问速度快
  • 减少数据库压力:降低对数据库的查询次数
  • 提高系统吞吐量:减少服务器处理时间,支持更多并发请求
  • 降低网络延迟:缓存可以部署在靠近用户的位置,减少网络传输时间
  • 提高系统可用性:当原始数据源不可用时,缓存可以作为临时替代

3. 缓存分类

按存储位置分类

  • 客户端缓存:存储在用户设备上,如浏览器缓存、应用本地缓存
  • 服务器端缓存:存储在服务器上,如内存缓存、Redis缓存
  • CDN缓存:存储在CDN节点上,用于加速静态资源访问

按缓存层次分类

  • L1缓存:CPU内部缓存,速度最快
  • L2缓存:CPU外部缓存,速度次之
  • L3缓存:共享缓存,速度较慢
  • 内存缓存:如Redis、Memcached
  • 磁盘缓存:如文件系统缓存

按缓存数据类型分类

  • 静态资源缓存:HTML、CSS、JavaScript、图片等
  • 动态数据缓存:API响应、数据库查询结果等
  • 计算结果缓存:复杂计算的结果

二、缓存策略

1. 常见缓存替换策略

策略 全称 描述 适用场景
LRU Least Recently Used 最近最少使用,移除最久未访问的数据 大多数Web应用
LFU Least Frequently Used 最少使用,移除访问频率最低的数据 访问频率差异大的场景
FIFO First In First Out 先进先出,移除最早进入缓存的数据 简单场景,如队列
LIFO Last In First Out 后进先出,移除最近进入缓存的数据 栈式访问模式
MRU Most Recently Used 最近最常使用,移除最近访问的数据 热点数据频繁变化的场景

2. 缓存更新策略

(1)Cache-Aside(旁路缓存)

  • 读取流程:先从缓存读取,若未命中则从数据库读取,然后更新缓存
  • 写入流程:先更新数据库,然后删除缓存
  • 优点:简单易用,适用于大多数场景
  • 缺点:可能存在短暂的不一致

(2)Write-Through(写穿透)

  • 写入流程:同时更新缓存和数据库
  • 读取流程:从缓存读取,若未命中则从数据库读取
  • 优点:缓存和数据库始终一致
  • 缺点:写入性能较低

(3)Write-Back(写回)

  • 写入流程:只更新缓存,异步更新数据库
  • 读取流程:从缓存读取,若未命中则从数据库读取
  • 优点:写入性能高
  • 缺点:可能导致数据丢失,实现复杂

(4)Write-Around(写旁通)

  • 写入流程:只更新数据库,不更新缓存
  • 读取流程:从缓存读取,若未命中则从数据库读取并更新缓存
  • 优点:适用于写入频繁但读取较少的场景
  • 缺点:首次读取性能较低

3. 缓存失效策略

  • TTL(Time To Live):设置缓存的生存时间,过期自动失效
  • 手动失效:通过代码或命令手动删除或更新缓存
  • 被动失效:当缓存容量不足时,根据替换策略自动失效

三、前端缓存实现

1. 浏览器缓存

强缓存

// 在后端设置响应头
res.setHeader('Cache-Control', 'max-age=31536000, immutable'); // 缓存1年
res.setHeader('Expires', new Date(Date.now() + 31536000000).toUTCString());

协商缓存

// 在后端设置响应头
const etag = generateEtag(content);
res.setHeader('ETag', etag);
res.setHeader('Last-Modified', lastModified);

// 检查请求头
if (req.headers['if-none-match'] === etag || req.headers['if-modified-since'] === lastModified) {
  return res.status(304).send(); // 未修改,使用缓存
}

2. Vue 3 组件缓存

Keep-Alive 组件

<template>
  <div class="app">
    <keep-alive :include="['Home', 'About']" :max="10">
      <router-view />
    </keep-alive>
  </div>
</template>

<script setup>
// Keep-Alive 会缓存不活动的组件实例,而不是销毁它们
// include: 只有名称匹配的组件会被缓存
// exclude: 任何名称匹配的组件都不会被缓存
// max: 最多可以缓存多少组件实例
</script>

组件内部缓存

<template>
  <div class="user-profile">
    <h2>用户资料</h2>
    <div v-if="isLoading">加载中...</div>
    <div v-else>{{ user }}</div>
  </div>
</template>

<script setup>
import { ref, computed, onMounted, watch } from 'vue';

const props = defineProps(['userId']);
const isLoading = ref(false);
const userCache = ref(new Map()); // 组件内部缓存

const user = computed(() => userCache.value.get(props.userId));

const fetchUser = async (userId) => {
  if (userCache.value.has(userId)) {
    return; // 使用缓存
  }
  
  isLoading.value = true;
  try {
    const response = await fetch(`/api/users/${userId}`);
    const data = await response.json();
    userCache.value.set(userId, data);
  } catch (error) {
    console.error('获取用户失败:', error);
  } finally {
    isLoading.value = false;
  }
};

onMounted(() => {
  fetchUser(props.userId);
});

watch(() => props.userId, (newUserId) => {
  fetchUser(newUserId);
});
</script>

3. Pinia 状态管理缓存

// src/stores/cache-store.js
import { defineStore } from 'pinia';

export const useCacheStore = defineStore('cache', {
  state: () => ({
    // 使用Map作为缓存存储
    cache: new Map(),
    // 缓存配置
    config: {
      ttl: 3600000, // 默认TTL:1小时
      maxSize: 100 // 最大缓存数量
    }
  }),

  getters: {
    // 获取缓存项
    get: (state) => (key) => {
      const item = state.cache.get(key);
      if (!item) return null;
      
      // 检查是否过期
      if (Date.now() > item.expiresAt) {
        state.cache.delete(key);
        return null;
      }
      
      // 更新访问时间(用于LRU)
      item.lastAccessedAt = Date.now();
      return item.value;
    },
    
    // 获取缓存大小
    size: (state) => state.cache.size
  },

  actions: {
    // 设置缓存项
    set(key, value, ttl = this.config.ttl) {
      // 如果缓存已满,移除最久未访问的项(LRU)
      if (this.cache.size >= this.config.maxSize) {
        this.evictLRU();
      }
      
      this.cache.set(key, {
        value,
        expiresAt: Date.now() + ttl,
        lastAccessedAt: Date.now()
      });
    },
    
    // 移除缓存项
    remove(key) {
      this.cache.delete(key);
    },
    
    // 清空缓存
    clear() {
      this.cache.clear();
    },
    
    // LRU 淘汰策略
    evictLRU() {
      let oldestKey = null;
      let oldestTime = Infinity;
      
      for (const [key, item] of this.cache.entries()) {
        if (item.lastAccessedAt < oldestTime) {
          oldestKey = key;
          oldestTime = item.lastAccessedAt;
        }
      }
      
      if (oldestKey) {
        this.cache.delete(oldestKey);
      }
    },
    
    // 预加载缓存
    preload(keys, fetchFn) {
      keys.forEach(key => {
        if (!this.get(key)) {
          fetchFn(key).then(value => {
            this.set(key, value);
          });
        }
      });
    }
  }
});

4. 自定义缓存钩子

// src/composables/useCache.js
import { ref, computed } from 'vue';
import { useCacheStore } from '@/stores/cache-store';

export function useCache(key, fetchFn, ttl = 3600000) {
  const cacheStore = useCacheStore();
  const isLoading = ref(false);
  const error = ref(null);
  
  // 从缓存获取数据
  const data = computed(() => cacheStore.get(key));
  
  // 刷新数据
  const refresh = async () => {
    isLoading.value = true;
    error.value = null;
    
    try {
      const result = await fetchFn();
      cacheStore.set(key, result, ttl);
      return result;
    } catch (err) {
      error.value = err;
      throw err;
    } finally {
      isLoading.value = false;
    }
  };
  
  // 初始化数据
  const initialize = async () => {
    if (!data.value) {
      await refresh();
    }
  };
  
  return {
    data,
    isLoading,
    error,
    refresh,
    initialize
  };
}

使用自定义缓存钩子

<template>
  <div class="product-detail">
    <h2>产品详情</h2>
    
    <div v-if="isLoading">加载中...</div>
    <div v-else-if="error">加载失败: {{ error.message }}</div>
    <div v-else-if="data">
      <h3>{{ data.name }}</h3>
      <p>{{ data.description }}</p>
      <p class="price">¥{{ data.price }}</p>
      <button @click="refresh">刷新</button>
    </div>
  </div>
</template>

<script setup>
import { onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { useCache } from '@/composables/useCache';

const route = useRoute();
const productId = route.params.id;

// 使用自定义缓存钩子
const { data, isLoading, error, refresh, initialize } = useCache(
  `product_${productId}`,
  async () => {
    const response = await fetch(`/api/products/${productId}`);
    if (!response.ok) {
      throw new Error('获取产品失败');
    }
    return response.json();
  },
  60000 // 缓存1分钟
);

// 初始化数据
onMounted(() => {
  initialize();
});
</script>

四、后端缓存实现(Node.js + Redis)

1. Redis 连接配置

安装依赖

# 安装 Redis 客户端
npm install ioredis
# 安装 Redis 速率限制库
npm install ioredis-rate-limit
# 安装配置管理库
npm install dotenv

Redis 连接管理

// src/infrastructure/cache/redis-connection.js
const Redis = require('ioredis');
require('dotenv').config();

class RedisConnection {
  constructor() {
    this.client = null;
    this.config = {
      host: process.env.REDIS_HOST || 'localhost',
      port: parseInt(process.env.REDIS_PORT || '6379'),
      password: process.env.REDIS_PASSWORD || null,
      db: parseInt(process.env.REDIS_DB || '0'),
      retryStrategy: (times) => {
        // 指数退避策略
        const delay = Math.min(times * 50, 2000);
        return delay;
      },
      reconnectOnError: (err) => {
        const targetError = 'READONLY';
        return err.message.includes(targetError);
      },
      maxRetriesPerRequest: null, // 无限重试
      enableReadyCheck: true // 启用就绪检查
    };
    this.connected = false;
  }

  // 建立连接
  async connect() {
    try {
      this.client = new Redis(this.config);
      
      // 连接事件
      this.client.on('connect', () => {
        console.log('Redis 连接成功');
        this.connected = true;
      });
      
      // 错误事件
      this.client.on('error', (err) => {
        console.error('Redis 错误:', err);
        this.connected = false;
      });
      
      // 关闭事件
      this.client.on('close', () => {
        console.error('Redis 连接关闭');
        this.connected = false;
      });
      
      // 就绪事件
      this.client.on('ready', () => {
        console.log('Redis 就绪');
        this.connected = true;
      });
      
      return this.client;
    } catch (error) {
      console.error('创建 Redis 连接失败:', error);
      throw error;
    }
  }

  // 获取客户端实例
  async getClient() {
    if (!this.client || !this.connected) {
      await this.connect();
    }
    return this.client;
  }

  // 关闭连接
  async close() {
    if (this.client) {
      await this.client.quit();
      this.client = null;
      this.connected = false;
      console.log('Redis 连接已关闭');
    }
  }
}

// 导出单例实例
const redisConnection = new RedisConnection();
module.exports = redisConnection;

2. Redis 缓存服务

// src/infrastructure/cache/redis-cache-service.js
const redisConnection = require('./redis-connection');

class RedisCacheService {
  constructor() {
    this.defaultTTL = 3600; // 默认TTL:1小时
  }

  // 设置缓存
  async set(key, value, ttl = this.defaultTTL) {
    const client = await redisConnection.getClient();
    const serializedValue = JSON.stringify(value);
    await client.set(key, serializedValue, 'EX', ttl);
  }

  // 获取缓存
  async get(key) {
    const client = await redisConnection.getClient();
    const value = await client.get(key);
    return value ? JSON.parse(value) : null;
  }

  // 删除缓存
  async delete(key) {
    const client = await redisConnection.getClient();
    await client.del(key);
  }

  // 批量获取缓存
  async mget(keys) {
    const client = await redisConnection.getClient();
    const values = await client.mget(keys);
    return values.map(value => value ? JSON.parse(value) : null);
  }

  // 批量设置缓存
  async mset(keyValuePairs, ttl = this.defaultTTL) {
    const client = await redisConnection.getClient();
    const pipeline = client.pipeline();
    
    for (const [key, value] of Object.entries(keyValuePairs)) {
      pipeline.set(key, JSON.stringify(value), 'EX', ttl);
    }
    
    await pipeline.exec();
  }

  // 检查缓存是否存在
  async exists(key) {
    const client = await redisConnection.getClient();
    return await client.exists(key) === 1;
  }

  // 设置缓存过期时间
  async expire(key, ttl) {
    const client = await redisConnection.getClient();
    await client.expire(key, ttl);
  }

  // 获取缓存剩余过期时间
  async ttl(key) {
    const client = await redisConnection.getClient();
    return await client.ttl(key);
  }

  // 递增操作
  async incr(key) {
    const client = await redisConnection.getClient();
    return await client.incr(key);
  }

  // 递减操作
  async decr(key) {
    const client = await redisConnection.getClient();
    return await client.decr(key);
  }

  // 哈希表设置
  async hset(key, field, value) {
    const client = await redisConnection.getClient();
    await client.hset(key, field, JSON.stringify(value));
  }

  // 哈希表获取
  async hget(key, field) {
    const client = await redisConnection.getClient();
    const value = await client.hget(key, field);
    return value ? JSON.parse(value) : null;
  }

  // 清空缓存
  async flushDb() {
    const client = await redisConnection.getClient();
    await client.flushDb();
  }
}

module.exports = new RedisCacheService();

3. 缓存中间件

// src/middlewares/cache-middleware.js
const redisCacheService = require('../infrastructure/cache/redis-cache-service');

// 缓存中间件
const cacheMiddleware = (ttl = 3600) => {
  return async (req, res, next) => {
    // 生成缓存键
    const cacheKey = `api_${req.method}_${req.originalUrl}`;
    
    try {
      // 尝试从缓存获取
      const cachedResponse = await redisCacheService.get(cacheKey);
      if (cachedResponse) {
        console.log(`缓存命中: ${cacheKey}`);
        return res.json(cachedResponse);
      }
      
      // 保存原始的res.json方法
      const originalJson = res.json;
      
      // 重写res.json方法,保存响应到缓存
      res.json = async (data) => {
        // 调用原始的json方法
        originalJson.call(res, data);
        
        // 保存响应到缓存
        await redisCacheService.set(cacheKey, data, ttl);
        console.log(`缓存设置: ${cacheKey}`);
      };
      
      next();
    } catch (error) {
      console.error('缓存中间件错误:', error);
      next(); // 缓存错误不影响正常请求
    }
  };
};

module.exports = cacheMiddleware;

使用缓存中间件

// src/api/routes/product-routes.js
const express = require('express');
const router = express.Router();
const cacheMiddleware = require('../../middlewares/cache-middleware');
const productController = require('../controllers/product-controller');

// 对GET请求应用缓存中间件,缓存10分钟
router.get('/', cacheMiddleware(600), productController.getProducts);
router.get('/:id', cacheMiddleware(300), productController.getProductById);

// 非GET请求不应用缓存
router.post('/', productController.createProduct);
router.put('/:id', productController.updateProduct);
router.delete('/:id', productController.deleteProduct);

module.exports = router;

4. 缓存一致性实现

// src/api/controllers/product-controller.js
const productRepository = require('../../infrastructure/persistence/repositories/product-repository');
const redisCacheService = require('../../infrastructure/cache/redis-cache-service');

class ProductController {
  // 创建产品
  async createProduct(req, res) {
    try {
      const product = await productRepository.create(req.body);
      
      // 清除相关缓存
      await this.clearProductCache();
      
      res.status(201).json(product);
    } catch (error) {
      res.status(500).json({ error: '创建产品失败' });
    }
  }

  // 更新产品
  async updateProduct(req, res) {
    try {
      const product = await productRepository.update(req.params.id, req.body);
      
      // 清除相关缓存
      await this.clearProductCache();
      await redisCacheService.delete(`api_GET_/api/products/${req.params.id}`);
      
      res.json(product);
    } catch (error) {
      res.status(500).json({ error: '更新产品失败' });
    }
  }

  // 删除产品
  async deleteProduct(req, res) {
    try {
      await productRepository.delete(req.params.id);
      
      // 清除相关缓存
      await this.clearProductCache();
      await redisCacheService.delete(`api_GET_/api/products/${req.params.id}`);
      
      res.status(204).send();
    } catch (error) {
      res.status(500).json({ error: '删除产品失败' });
    }
  }

  // 清除产品列表缓存
  async clearProductCache() {
    // 清除产品列表缓存
    await redisCacheService.delete('api_GET_/api/products');
    
    // 清除所有产品相关的缓存(实际项目中应更精确)
    // 这里可以使用Redis的KEYS命令,但生产环境不推荐
    // 推荐使用Redis的SCAN命令或使用更精确的缓存键命名策略
  }
}

module.exports = new ProductController();

五、缓存常见问题及解决方案

1. 缓存穿透

问题:请求不存在的数据,导致每次都访问数据库

解决方案

  • 布隆过滤器:在缓存前添加布隆过滤器,快速判断数据是否存在
  • 空值缓存:对不存在的数据设置空值缓存,设置较短的TTL

布隆过滤器实现

// src/infrastructure/cache/bloom-filter.js
class BloomFilter {
  constructor(size = 1000000, hashCount = 5) {
    this.size = size;
    this.hashCount = hashCount;
    this.bitArray = new Array(size).fill(0);
  }

  // 哈希函数
  #hash(value, seed) {
    let hash = 0;
    for (let i = 0; i < value.length; i++) {
      hash = (hash * seed + value.charCodeAt(i)) % this.size;
    }
    return hash;
  }

  // 添加元素
  add(value) {
    const strValue = JSON.stringify(value);
    for (let i = 0; i < this.hashCount; i++) {
      const index = this.#hash(strValue, i + 1);
      this.bitArray[index] = 1;
    }
  }

  // 检查元素是否可能存在
  mightContain(value) {
    const strValue = JSON.stringify(value);
    for (let i = 0; i < this.hashCount; i++) {
      const index = this.#hash(strValue, i + 1);
      if (this.bitArray[index] === 0) {
        return false;
      }
    }
    return true;
  }
}

module.exports = BloomFilter;

2. 缓存击穿

问题:热点数据过期,导致大量请求同时访问数据库

解决方案

  • 互斥锁:使用Redis分布式锁,只允许一个请求更新缓存
  • 热点数据永不过期:对热点数据不设置TTL,定期异步更新
  • 缓存预热:提前加载热点数据到缓存

分布式锁实现

// src/infrastructure/cache/distributed-lock.js
const redisCacheService = require('./redis-cache-service');

class DistributedLock {
  constructor() {
    this.lockPrefix = 'lock_';
    this.defaultExpire = 10; // 默认锁过期时间(秒)
  }

  // 获取锁
  async acquire(key, expire = this.defaultExpire) {
    const lockKey = `${this.lockPrefix}${key}`;
    const lockValue = `${Date.now()}_${Math.random()}`;
    
    // 使用SETNX命令获取锁
    const result = await redisCacheService.client.set(lockKey, lockValue, 'NX', 'EX', expire);
    
    return result === 'OK' ? lockValue : null;
  }

  // 释放锁
  async release(key, value) {
    const lockKey = `${this.lockPrefix}${key}`;
    
    // 使用Lua脚本原子性释放锁
    const script = `
      if redis.call('GET', KEYS[1]) == ARGV[1] then
        return redis.call('DEL', KEYS[1])
      else
        return 0
      end
    `;
    
    return await redisCacheService.client.eval(script, 1, lockKey, value);
  }

  // 尝试获取锁,带重试
  async tryAcquire(key, maxRetries = 5, retryDelay = 100, expire = this.defaultExpire) {
    for (let i = 0; i < maxRetries; i++) {
      const lockValue = await this.acquire(key, expire);
      if (lockValue) {
        return lockValue;
      }
      await new Promise(resolve => setTimeout(resolve, retryDelay));
    }
    return null;
  }
}

module.exports = new DistributedLock();

3. 缓存雪崩

问题:大量缓存同时过期,导致数据库压力骤增

解决方案

  • 随机过期时间:为缓存项设置随机TTL,避免同时过期
  • 分层缓存:使用多级缓存,不同层级设置不同TTL
  • 限流降级:对数据库请求进行限流,设置降级策略
  • 缓存预热:提前加载数据到缓存

随机TTL实现

// src/infrastructure/cache/redis-cache-service.js
const redisConnection = require('./redis-connection');

class RedisCacheService {
  constructor() {
    this.defaultTTL = 3600; // 默认TTL:1小时
    this.randomTTLRange = 300; // 随机TTL范围:±5分钟
  }

  // 设置缓存,带随机TTL
  async set(key, value, ttl = this.defaultTTL) {
    // 添加随机TTL,避免雪崩
    const randomTTL = ttl + Math.floor(Math.random() * this.randomTTLRange * 2) - this.randomTTLRange;
    const client = await redisConnection.getClient();
    const serializedValue = JSON.stringify(value);
    await client.set(key, serializedValue, 'EX', randomTTL);
  }
  
  // 其他方法...
}

module.exports = new RedisCacheService();

六、缓存监控与最佳实践

1. 缓存监控指标

  • 缓存命中率:缓存命中次数 / (缓存命中次数 + 缓存未命中次数)
  • 缓存穿透率:缓存穿透次数 / 总请求次数
  • 缓存过期率:过期缓存数量 / 总缓存数量
  • 缓存大小:缓存占用的存储空间
  • 缓存延迟:从缓存读取数据的时间
  • 缓存错误率:缓存操作错误次数 / 总缓存操作次数

2. Redis 监控命令

# 查看Redis信息
redis-cli info

# 查看内存使用情况
redis-cli info memory

# 查看命中率
redis-cli info stats | grep keyspace_hits
redis-cli info stats | grep keyspace_misses

# 查看慢查询
redis-cli config get slowlog-log-slower-than
redis-cli slowlog get

# 查看当前连接数
redis-cli info clients | grep connected_clients

3. 缓存最佳实践

(1)缓存键设计

  • 使用前缀:如 user_123product_456
  • 包含版本号:如 v1_user_123,便于缓存迁移
  • 包含环境信息:如 dev_user_123prod_user_123
  • 使用唯一标识符:如UUID,避免冲突
  • 保持简洁:避免过长的缓存键

(2)缓存数据设计

  • 只缓存必要数据:避免缓存过大的数据
  • 序列化选择:JSON、MessagePack、Protocol Buffers等
  • 压缩数据:对大对象进行压缩
  • 避免缓存敏感数据:如密码、令牌等

(3)性能优化

  • 批量操作:使用mget、mset等批量命令
  • 管道操作:减少网络往返次数
  • 使用连接池:避免频繁创建和关闭连接
  • 选择合适的数据结构:根据业务需求选择Hash、List、Set、Sorted Set等

(4)安全性考虑

  • 限制Redis访问:使用防火墙和密码认证
  • 避免使用KEYS命令:生产环境使用SCAN命令
  • 设置合理的内存限制:避免Redis占用过多内存
  • 定期备份:确保数据安全

七、总结

本集深入探讨了缓存架构设计的核心概念、策略和实现方案,包括:

  1. 缓存基础:从缓存的基本概念、分类到常见的缓存策略
  2. 前端缓存:Vue 3组件缓存、Pinia状态管理缓存和自定义缓存钩子
  3. 后端缓存:Node.js + Redis的完整实现,包括连接管理、缓存服务和中间件
  4. 缓存问题解决方案:缓存穿透、击穿、雪崩的应对策略
  5. 缓存监控和最佳实践:如何监控缓存性能和遵循最佳实践

通过本集的学习,您应该掌握了如何在Vue 3全栈应用中设计和实现高效、可靠的缓存架构。缓存是提高系统性能的重要手段,但也需要谨慎设计,避免引入新的问题。在实际项目中,应根据具体业务需求选择合适的缓存策略和实现方案,并结合监控工具持续优化缓存性能。

代码仓库

本集示例代码已上传至GitHub:

下集预告

下一集将深入探讨搜索架构实现,包括搜索的基本概念、技术选型、索引设计、搜索算法以及与Elasticsearch的集成方案。我们将学习如何构建高效的搜索系统,为用户提供快速、准确的搜索体验。

« 上一篇 Vue 3 领域事件与消息队列深度指南:构建可靠的事件驱动系统 下一篇 » Vue 3 搜索架构实现深度指南:构建高效搜索功能