使用Netlify Functions代理将Waline评论系统全面融合到博客之中

最近总算是搞完了整个零神站点的迁移以及博客外观、功能上的修缮,现在可以折腾一些其它的东西了。

目前网站是由一个Hexo生成的静态博客和Waline评论系统组成的,在Netlify中是两个项目也就需要两个域名。在Vercel时期,我是刚刚从Valine升级至Waline,就比较懒,直接两个域名贴上去。

  • zerokami.cn 主站&博客
  • waline.zerokami.cn Waline评论系统

在文章页面通过waline.js直接调用waline.zerokami.cn。但在迁移之后总看总不顺眼,要是能把它合并到zerokami.cn主站的域名就好了。

分析

现在我想到了两种方法:

  1. 将Waline项目合并到主站的仓库中。
  2. 通过某种方式将Waline挂靠在主域名的目录下。

将Waline项目合并到主站的仓库中

先说第一种方法,这种方法非常的不推荐,有很多麻烦:

  1. 每次发布和部署主站时,Waline也要跟着一起部署,项目部署的时间会增加,同时也会增加CPU时间的消耗。
  2. 不好管理。有可能在对主站项目动手脚的时候,也一起误伤了Waline,分开分库比较好。
  3. 合并比较麻烦,我不想动脑子。

基于以上的几种原因,我还是想要通过更为简单的方式来解决这个问题。

通过某种方式将Waline挂靠在主域名的目录下

能通过什么方式来弄呢,直接通过网址200重定向怎么样?

1
2
3
4
5
[[redirects]]
from = "/pinglun/*"
to = "https://xxxxx.netlify.app/.netlify/functions/comment/:splat"
status = 200
force = true

将其合并到主页的Netlify配置文件中,也确实生效了。评论系统是正常生效的,但是如果通过此网址来登入后台就出现很多的问题了,各种报错……

我是没想到客户端的Waline还能正常工作,例如查看评论,除了没法评论。要跳转到外部 URL 且保持网址不变,单纯的 200 重定向(代理)会导致某些功能的地址出错,既然如此,我们还是得要想想其它的方法来解决问题。

后面各种搜索问题,最后还是找AI给了个解决方案-创建一个 Netlify Function 来转发Waline评论系统。

首先就是得要在静态网页的根目录下创建或修改以下几个文件:

1
2
3
4
5
6
/
├── package.json # 包含 node-fetch 依赖
├── netlify.toml # 重定向配置
└── netlify/
└── functions/
└── comment-proxy.js # 代理函数

package.json文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"name": "source",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"node-fetch": "^2.7.0"
}
}

你可以用我的package.json文件,也可以自己在目录下通过命令创建:npm init -ynpm install node-fetch@2

netlify.toml文件

1
2
3
4
5
6
7
8
[[redirects]]
from = "/pinglun/*"
to = "/.netlify/functions/comment"
status = 200

[build.environment]
# 信任代理传递的 IP
WALINE_TRUST_PROXY = "true"

其中第一个重定向就不必多说了吧,把它转换到自己想要设置的地址。

第二个是信任代理传递的IP,这个主要是用来接收评论发送时的地址。如果配合代理进行设置的话,你在Waline后台看到的IP地址就全都是Netlify节点的IP地址了,那么这个功能就彻底没有啥作用了。

comment-proxy.js文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
exports.handler = async (event) => {
console.log('Full path received:', event.path);

const relativePath = event.path.replace('/pinglun', '');
const finalPath = relativePath || '/';

const targetUrl = `你项目的域名/.netlify/functions/comment${finalPath}`;

const queryString = event.queryStringParameters
? '?' + new URLSearchParams(event.queryStringParameters).toString()
: '';

const fullUrl = targetUrl + queryString;

// 获取真实 IP(Netlify 提供的客户端 IP)
const clientIP = event.headers['x-nf-client-connection-ip'] ||
event.headers['x-forwarded-for'] ||
event.headers['client-ip'] ||
'unknown';

console.log('Client IP:', clientIP);
console.log('Proxying to:', fullUrl);

// 处理 OPTIONS 预检
if (event.httpMethod === 'OPTIONS') {
return {
statusCode: 204,
headers: {
'access-control-allow-origin': '*',
'access-control-allow-headers': 'Content-Type, X-Waline-Token, Authorization',
'access-control-allow-methods': 'GET, POST, PUT, DELETE, OPTIONS',
},
body: '',
};
}

try {
const fetchOptions = {
method: event.httpMethod,
headers: {
'content-type': event.headers['content-type'] || 'application/json',
'x-waline-token': event.headers['x-waline-token'] || '',
'authorization': event.headers['authorization'] || '',
'user-agent': event.headers['user-agent'] || 'Netlify-Proxy',
// 关键:传递真实 IP 和客户端信息
'x-forwarded-for': clientIP,
'x-real-ip': clientIP,
'x-client-ip': clientIP,
// 传递其他可能需要的头部信息
'x-forwarded-proto': event.headers['x-forwarded-proto'] || 'https',
'x-forwarded-host': event.headers['host'] || '',
'referer': event.headers['referer'] || '',
'accept-language': event.headers['accept-language'] || '',
},
};

if (!['GET', 'HEAD'].includes(event.httpMethod) && event.body) {
fetchOptions.body = event.body;
}

const response = await fetch(fullUrl, fetchOptions);
const data = await response.text();

console.log('Response status:', response.status);

return {
statusCode: response.status,
headers: {
'content-type': response.headers.get('content-type') || 'application/json',
'access-control-allow-origin': '*',
'access-control-allow-headers': 'Content-Type, X-Waline-Token, Authorization',
'access-control-allow-methods': 'GET, POST, PUT, DELETE, OPTIONS',
'set-cookie': response.headers.get('set-cookie') || '',
},
body: data,
};
} catch (error) {
console.error('Proxy error:', error);
return {
statusCode: 502,
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({
error: 'Proxy error',
message: error.message
}),
};
}
};

这个没有什么,就是设置代理转发等等。如果要用的话需要改成你项目的域名以及你需要跳转的地址。(要和netlify.toml文件一起修改。)

在一切都配置好以后,重新部署就能够生效了,各位可以尝试一下这样做,目前我是没有遇到什么太大的问题,除了后台控制面板登出的时候url还是会跳错,这个问题到时候看看能不能修改comment-proxy.js文件修复,到时候再说吧。