服务端渲染的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.send
为const
定义的oldSend
方法,再重新定义一个新的send
给res.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,这里就可以控制不存在页面记录