【产品“笨办法”,难倒架构师】还得我小前端出手!

585 阅读6分钟

🙎 "抖音都能做,为什么你们做不了?抖音能做到按权重推荐视频,小红书能把笔记和短视频混排得像彩虹糖!你们能干点啥?"

需求评审会上,新来的产品总监把MacBook重重得合上,会议室温度瞬间骤降10度,公司后端面面相觑,低着头乖得像个鹌鹑。

🙋‍♂️ 空气凝固的第七秒,后端架构师发话了:"抖音有算法团队,他们的推荐算法需要用户行为数据训练模型......"

🙎 "那我给你想个笨办法,每页给用户推三条视频,两条图片,一条文章,前边的不够了,后边的补上"

🙋‍♂️ 后端开发终于说话了:"他们的内容都存在一张表..."

🙎 "别和我说技术架构!用户不在乎你有几张数据库表!上周我们的竞品分析报告显示......"

作为旁观者,我暗自窃喜,还好不是让我做。再说了抖音多少开发,我们前后端加起来才两个人,还想鸟枪换大炮?

📄【需求来了】

  • 背景:前端展示标签选项,用户选中标签后,筛选该标签下的数据,展示为瀑布流,用户可以上拉加载分页,每页给用户展示视频+图片+文章。

  • 要求

    • 每页必须返回6条数据。
    • 每页3条视频+2条图片+1条文章,排序优先级:视频 > 图片 > 文章。
    • 如果视频不够了,用图片当做视频补上,图片不够了用文章当做图片补上。(比如:视频只有1条了,最终就是:1视频+4图片+1文章)
  • 示意图

分页.png

😲【后端慌了】

后端听了需求立马慌了,直摇头说做不了,说后端要重构,嘴里还在无限循环他们有多难办:

  1. 三种数据存在三种表中,每种表中的数据还有不同标签,要筛选出选中的标签数据,再合并起来给前端。
  2. 第一张表没数据第二张表补,第二张表没数据第三张表补,分页后根本找不到上一页数据最后一条是哪个。
  3. 没办法保证一页是6条。
  4. 要做到这个从高到低依次补充数据,很难,速度会很慢。

😅【后端做不了,让前端试试?】

突然,后端和产品经理把目光转向我。

“那个……前端可以搞定吗?”

???

我一个前端切图仔,我能搞定这个?

“你在想屁吃”

我立马开始输出《前端圣经》

“分页?自己遍历全表啊,这不就是你们说的'后端工程化'吗?”

“这个需求做不了是吧,上次数据库删库你们怎么做得比谁都快?”

“Redis不是数据库?数据存Redis不就行了?”

“您不是整天JVM调优呢?我看您是给破船贴创可贴 内存泄漏漏得比你英语词汇量还丰富💧”

😎【前端“笨办法”】

"好,既然你后端做不了,那我就前端做。后端做不了的需求我前端做,后端做得了的我前端更要做,这就是前端!"

方案思路

后端针对视频、图片、文章三种数据,提供三个分页接口,支持用最后一个数据ID和数量分页请求。(这总能做到吧,后端架构师们?)

  1. 备用数据设计

    为了应对最坏情况(比如视频数据为空),提前预请求多条备用数据:

    • 视频接口: 请求 3 条数据(目标数量)。

    • 图片接口: 请求 5 条数据。原因在于:

      • 当视频数据为空时,视频部分需要 3 条图片数据补位;
      • 同时图片区域本身还需要 2 条数据;
      • 因此图片接口至少需要 3 + 2 = 5 条数据。
    • 文章接口: 请求 6 条数据。原因在于:

      • 当视频和图片都为空时,文章接口需要同时补充视频(3 条)、图片(2 条)和文章区域(1 条),共计 6 条数据。
  2. 数据补位顺序

    按照以下优先级补位,确保各区域的数据数量满足要求:

    • 视频部分(目标 3 条):

      • 优先使用视频接口返回的数据。
      • 如果视频数据不足,则从图片备用数据中补足;
      • 如果图片备用数据仍不足,再从文章备用数据中补足。
    • 图片部分(目标 2 条):

      • 在视频部分消耗了部分图片数据后,剩余的图片备用数据用于图片区域;
      • 如果不足,则从文章备用数据中补足。
    • 文章部分(目标 1 条):

      • 在视频和图片部分都补位后,剩余的文章备用数据中取 1 条数据用于文章区域。
  3. 数据合并

    将三个部分的数据按照视频、图片、文章的顺序合并,最终确保前端展示 6 条数据。

async function loadPage(lastVideoId, lastImageId, lastArticleId) {
  // 视频接口:请求3条
  // 图片接口:最坏情况需要提供视频的3条补位 + 图片区域2条 = 5条
  // 文章接口:最坏情况需要提供视频区域3条 + 图片区域2条 + 文章区域1条 = 6条
  const [videoData, imageData, articleData] = await Promise.all([
    fetchVideos(3, lastVideoId),
    fetchImages(5, lastImageId),
    fetchArticles(6, lastArticleId),
  ]);

  // 1. 视频部分:目标是 3 条数据
  let videoSlot = [...videoData];
  let missingVideoCount = 3 - videoSlot.length;
  if (missingVideoCount > 0) {
    // 优先从图片数据中补充
    const substituteFromImages = imageData.splice(0, missingVideoCount);
    videoSlot = videoSlot.concat(substituteFromImages);
    missingVideoCount = 3 - videoSlot.length;
    if (missingVideoCount > 0) {
      // 如果图片仍不足,再从文章数据中补充
      const substituteFromArticles = articleData.splice(0, missingVideoCount);
      videoSlot = videoSlot.concat(substituteFromArticles);
    }
  }

  // 2. 图片部分:目标是 2 条数据
  let imageSlot = [];
  if (imageData.length >= 2) {
    imageSlot = imageData.splice(0, 2);
  } else {
    imageSlot = imageData.splice(0);
    const missingImageCount = 2 - imageSlot.length;
    const substituteFromArticles = articleData.splice(0, missingImageCount);
    imageSlot = imageSlot.concat(substituteFromArticles);
  }

  // 3. 文章部分:目标是 1 条数据
  let articleSlot = [];
  if (articleData.length > 0) {
    articleSlot = articleData.splice(0, 1);
  }

  // 合并顺序:视频、图片、文章,共 6 条数据
  const finalPageData = videoSlot.concat(imageSlot, articleSlot);
  return finalPageData;
}

// 模拟视频接口请求
const fetchVideos = (count, lastId) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve([]);
    }, 1000);
  });
};

// 模拟图片接口请求
const fetchImages = (count, lastId) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve([]);
    }, 1000);
  });
};

// 模拟文章接口请求
const fetchArticles = (count, lastId) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve([]);
    }, 1000);
  });
};

// 示例调用
loadPage(0, 0, 0).then((data) => {
  console.log("最终数据:", data);
});

📝【后记】

三个月后,当我看到后端年终总结PPT写的:设计实现"革命性分页架构"技术方案,笑得我差点把咖啡喷在屏幕上。

茶水间最新传说:某次服务器宕机后,后端Leader在容灾预案里加了一条——"紧急情况下可用用户浏览器当边缘计算节点"。