给博客的所有站外链接添加跳转提示页
2025/4/28...大约 2 分钟
首先,本博客的评论系统和博客主站是两套独立的系统,所以要针对两套系统做分别的处理。
1. 博客主站
我们通过插件的方式来实现博客主站中的外链跳转提示页。
1.1 编写插件
在目录 .vuepress/plugins 中创建文件,external-link-redirect.ts:
import { Markdown } from "vuepress/markdown";
// 定义插件选项接口
interface ExternalLinkRedirectOptions {
  internalDomains?: string[];
  redirectPath?: string;
  urlParamName?: string;
}
const external_link_redirect = (options: ExternalLinkRedirectOptions = {}) => ({
  name: "external-link-redirect",
  extendsMarkdown: (md: Markdown) => {
    md.core.ruler.after("inline", "external_link_redirect", (state) => {
      const tokens = state.tokens;
      // 从选项中获取内部域名和重定向路径,提供默认值
      const internalDomains = options.internalDomains || [];
      const redirectPath = options.redirectPath || "/go.html";
      const urlParamName = options.urlParamName || "url";
      for (let i = 0; i < tokens.length; i++) {
        const token = tokens[i];
        if (token.type === "inline" && token.children) {
          token.children.forEach((child) => {
            if (child.type === "link_open") {
              const hrefIndex = child.attrIndex("href");
              if (hrefIndex >= 0 && child.attrs) {
                const hrefAttr = child.attrs[hrefIndex];
                const href = hrefAttr[1];
                if (/^https?:\/\//i.test(href)) {
                  let isInternalLink = false;
                  try {
                    const hrefUrl = new URL(href);
                    isInternalLink = internalDomains.some(
                      (domain) =>
                        hrefUrl.hostname === domain ||
                        hrefUrl.hostname.endsWith(`.${domain}`)
                    );
                  } catch (e) {
                    console.error("Invalid URL:", href, e);
                  }
                  if (!isInternalLink) {
                    try {
                      hrefAttr[1] = `${redirectPath}?${urlParamName}=${encodeURIComponent(
                        href
                      )}`;
                    } catch (e) {
                      console.error("Error encoding URL:", href, e);
                      hrefAttr[1] = `${redirectPath}?${urlParamName}=${href}`; // 回退到不编码的 URL
                    } finally {
                      child.attrSet("target", "_blank");
                      child.attrSet("rel", "noopener noreferrer");
                    }
                  }
                }
              }
            }
          });
        }
      }
    });
  },
});
export default external_link_redirect;1.2 使用插件
在 .vuepress/config.ts 中注册该插件
plugins: [
  externalLinkRedirect({
    internalDomains: ["meowpass.com"],
    redirectPath: "https://www.meowpass.com/go.html",
    urlParamName: "url",
  }),
],配置项说明:
- internalDomains:不进入外链跳转提示页的域名。
- redirectPath:外链跳转提示页的路径,默认是- /go.html。
- urlParamName:跳转提示页中接收外链的参数名,默认是- url。
2. 评论系统
本博客使用的是 Waline 评论系统,所以该修改不适用于其它评论系统。
由于我的评论系统是采用的 Docker 部署,所以不能使用官方的 Docker 镜像启动,需要自己写一个 dockerfile 构建自己的镜像,我们参考官方文件稍作修改:
# https://github.com/nodejs/LTS
FROM node:lts AS build
WORKDIR /app
ENV NODE_ENV production
RUN set -eux; \
        npm config set registry https://registry.npmmirror.com; \
        npm install --production --silent @waline/vercel waline-link-interceptor
FROM node:lts-slim
WORKDIR /app
ENV TZ Asia/Shanghai
ENV NODE_ENV production
COPY --from=build /app .
EXPOSE 8360
CMD ["node", "node_modules/@waline/vercel/vanilla.js"]在第7行,多安装一个依赖 waline-link-interceptor
然后还要修改 docker compose 文件,添加一行配置:
volumes:
  - ./config.js:/app/node_modules/@waline/vercel/config.js:ro挂载的 config.js 文件内容如下:
const LinkInterceptor = require('waline-link-interceptor');
module.exports = {
  plugins: [
    LinkInterceptor({
      whiteList: [
        'meowpass.com'
      ],
      // blackList: [],
      // interceptorTemplate: `redirect to __URL__`,
      redirectUrl: "https://www.meowpass.com/go.html",
      encodeFunc: (url) =>{
        try {
          return `url=${encodeURIComponent(url)}`;
        } catch (error) {
          console.error('URL 编码失败:', error);
          return `url=${url}`; // 降级处理
        }
      }
    })
  ]
}然后,构建镜像并启动容器:
sudo docker compose up -d --build