express生成静态页面

299 阅读2分钟

服务端渲染的web页面,同一时刻发生大量访问的话,每次生成web页面会容易出现性能问题,为了解决这种问题一般是服务端渲染+cdn去处理,但是也会出现瓶颈。可以采用前端渲染或者静态页面。对于前端渲染的话,在低网络情况下,加载笨重的js会导致出现白屏时间比较长,体验感不好,因此对于大量请求访问页面的话,一般主要是以静态页面为主

1、express生成静态页面

  • 主要采取方案是拦截响应的中间件
/**
 * 检测是否是特殊关键字
 * @param {string} key
 */
const getIfSpecialKeyWords = (key) => {
  let rex =/.*\/(config|realization|webmanifest.json)$/ig;
  let ifSpecialKeyWords = false;
  (key.match(rex))&&(ifSpecialKeyWords=true);
  return ifSpecialKeyWords;
}
/**
 * 生成静态页面中间件
 * @param {Object} req
 * @param {Object} res
 * @param {Function} next
 */
const generateStaticPage = function (req, res, next) {
  const oldSend = res.send;
  let key = req.originalUrl || req.url;
  key = key.replace(/((\?)|(\/\?)).*$/ig, "");
  if (!key.startsWith("/main") && !key.startsWith("/debug")) {
    key = "/main" + key;
  }
  const hostname = process.env.NODE_ENV === "development" ? process.env.HOSTNAME : req.hostname;
  const url = `${hostname}${key}`;
  if (!getIfSpecialKeyWords(url)) {
    res.send = (body) => {
      //上传至oss服务
      $html.uploadUrlHtml("miniweb", url, body).then(m => {
        oldSend.call(res, body);//其中body就是拦截生成的页面
      }).catch(m => {
        next(m);
      })
    }
  }
  next();
}
app.use(express.static(path.join(__dirname, "public")));
app.use(generateStaticPage);
  • 通过拦截响应的方法res.sendconst 定义的oldSend方法,再重新定义一个新的sendres.send方法。
  • 注意:
    • 不可以直接把方法旧的方法定义在res对象上res.oldSend,不然存在特殊的路由的话,会出现死循环
    • 该中间件定义,不能直接放在静态资源中间件之前,不然也会存在对静态资源做拦截处理

2、静态页面代理

  • 可以直接使用express的代理插件http-proxy-middleware,就可以快速实现代理静态资源
const indexRexStr = "^\/$"; // /
const homeRexStr = `^\/home$`; // /home
const privacyRexStr = "^\/privacy$";// /privacy
const termsRexStr = "^\/terms$";// /terms
const gameboxRexStr = "^\/gamebox$";// /gamebox
const todayRexStr = "^\/today$"; // /today
const versionRexStr = "^\/version.json$"; // /version.json
const subchannelRexStr = "^\/[^\/.]*$"; //main
const subchannelSencondRexStr = "^\/[^\/.]*\/(home|privacy|terms|gamebox|today)$"; //main/home
const gameThirdRexStr = "^\/game\/[^\/.]*\/(play|play1|match|play)$"; // /game/:gameId/play
const subchannelGameRexStr = "^\/([^\/.]*)\/game\/[^\/.]*\/(play|play1|match)$"; // /:sub_channel_id/game/:gameId/play/
const staticFileRexStr = "^\/static\/.*.(jpg|ico|png|js|css|json)$"; // 静态资源

const specialRex = new RegExp(`(${indexRexStr})|(${versionRexStr})|(${subchannelRexStr})|(${subchannelSencondRexStr})|(${gameThirdRexStr})|(${subchannelGameRexStr}|${staticFileRexStr})`, "ig");
const homeRex = new RegExp(homeRexStr, "ig");
const privacyRex = new RegExp(privacyRexStr, "ig");
const termsRex = new RegExp(termsRexStr, "ig");
const gameboxRex = new RegExp(gameboxRexStr, "ig");
const todayRex = new RegExp(todayRexStr, "ig");
const versionRex = new RegExp(versionRexStr, "ig");
const subchannelRex = new RegExp(subchannelRexStr, "ig");
const subchannelSencondRex = new RegExp(subchannelSencondRexStr, "ig");
const gameThirdRex = new RegExp(gameThirdRexStr, "ig");
const subchannelGameRex = new RegExp(subchannelGameRexStr, "ig");
const staticFileRex = new RegExp(staticFileRexStr, "ig");

/**
 * 需要代理的请求,false返回404错误页面
 * @return {Boolean}
 */
const filter = (path, req) => {
  let url = removeQuery(path) || "/";
  const ifInclude = url.match(specialRex) && req.method === 'GET';
  return ifInclude;
};
/**
 * 移除查询参数
 * @param {path}
 */
const removeQuery = (path) => {
  let url = path;
  url = url.replace(/\?.*/ig, "");
  if (url && url?.lastIndexOf("/") === url.length - 1) {
    url = url.substring(0, url?.lastIndexOf("/"));
  }
  return url
}

const dealurl = (path, req) => {
  let url = removeQuery(path)
  let fileSuffix = ".html";
  let hostname = process.env.NODE_ENV === "development" ? process.env.HOSTNAME : req.hostname;
  let finalUrl = "";

  if (url.match(staticFileRex)) {
    //静态资源代理处理
    url = url.substring(url?.indexOf("/") + 1, url?.length);
    finalUrl = `${url}`;
  } else {
    //子渠道处理,默认main或者debug
    if (url.match(subchannelRex) && !url.match(homeRex) && !url.match(privacyRex) && !url.match(termsRex) && !url.match(gameboxRex) && !url.match(todayRex) && !url.startsWith("/main") && !url.startsWith("/debug")) {
      url = "/main";
    }

    if (url.match(subchannelSencondRex) && !url.startsWith("/main/") && !url.startsWith("/debug/")) {
      const s = url.match(subchannelSencondRex);
      url = `/main/${s[1]}`;
    }

    if (url.match(subchannelGameRex) && !url.startsWith("/main/") && !url.startsWith("/debug/")) {
      const s = url.match(subchannelGameRex);
      url = `${s[0].replace(`/${s[1]}/`, "/main/")}`;
    }

    if (!url.startsWith("/main") && !url.startsWith("/debug")) {
      url = "/main" + url;
    }
    finalUrl = `${hostname}${url}${fileSuffix}`;
  }
  return finalUrl;
}

app.use(
  '/*',
  createProxyMiddleware(
    filter,
    {
      target: `${process.env.WEB_API_BASE_URL}`,
      changeOrigin: true,
      pathRewrite: (path, req) => {
        let url = dealurl(path, req);
        console.log("最终oss上的url:", url)
        return url
      },
    })
);

app.get("*", (req, res, next) => {
  res.status(400).send('<html lang="en" class="" style="font-size: 50px;"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><link rel = "icon" type = "image/x-icon" href = "/images/favicon.ico" ></head><body><p style="color: #747474;font-size: 48px;font-family: Helvetica;font-weight: 400;">404</p></body></html>')
});//其他页面访问不到,默认显示404,这里就可以控制不存在页面记录