安然无恙,各位;

前言

由于之前说的反馈方式一直没跟上,今天正好需要整理程序顺便美好和重写了博客一些功能,也顺便简单创建了一个小程序,用于适配上博客的中间页反馈方式。

话说、更新是不是有些勤奋了?(调侃)不过接下来应该是长达近一个月停更了,要回海南过大年,期间会起一些草稿稿子的,今年连存货的习惯都没有了~

开始

测试环境如下:测试运行环境的版本,推荐使用

  • Nginx 1.28.0 (理论上低版本也可以)
  • PHP 8.0.26(必要,低于函数有问题)

Github暂时下了不支持,往下翻找云盘获取吧!

环境配置(1)

登录宝塔面板 → 「网站」→ 找到「自己的站点」 → 「设置」→ 「伪静态」;

粘贴下方伪静态规则 → 点击「保存F」(宝塔会自动重载 Nginx,无需手动重启)。

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
# 1. 禁止外部访问data目录(无特征404响应,防止目录存在性探测)
location ^~ /data/ {
deny all;
return 404;
}

# 2. 核心:保护目标接口文件,仅允许本站/本地请求,屏蔽所有特征
location ~* (save_feedback|update_status|edit_feedback)\.php$ {
# 强校验Referer,仅允许指定域名、本地回环,空Referer拒绝
if ($http_referer !~* (feedback.koxiuqiu.cn|127.0.0.1|localhost)) {
return 404;
}
# 屏蔽接口访问日志,避免泄露请求信息
access_log off;
log_not_found off;
}

# 3. 全局安全头(all参数确保错误页、接口响应都携带,无死角防护)
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "same-origin" always;
add_header Cache-Control "no-cache, no-store, must-revalidate" always;

# 4. 禁止访问敏感配置/备份/日志/数据库文件(覆盖更全面的敏感后缀)
location ~* (\.user.ini|\.htaccess|\.htpasswd|\.env.*|\.gitignore|\.bak|\.log|\.sql|\.tar|\.gz|\.zip)$ {
return 404;
access_log off;
log_not_found off;
}

# 5. 禁止访问版本控制/开发目录(防止源码、开发配置泄露)
location ~* /(\.git|\.svn|\.idea|node_modules|vendor)/ {
return 404;
access_log off;
log_not_found off;
}

# 6. 屏蔽隐藏文件(所有以.开头的文件/目录,如.git/.env)
location ~ /\. {
return 404;
access_log off;
log_not_found off;
}

# 7. 屏蔽Nginx版本号(伪静态中全局生效)
server_tokens off;

环境配置(2)

修改站点独立配置文件(推荐,如nginx/conf.d/yourdomain.conf

在你的站点内添加以下规则,重启 Nginx 后生效(nginx -s reload

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
server {
listen 80;
# 替换为实际域名/IP,多个用空格分隔(如feedback.koxiuqiu.cn 192.168.1.100)
server_name 你的域名/IP;
# 替换为项目实际根目录(如/var/www/feedback-system)
root 你的项目根目录;
index index.php index.html index.htm;

# 全局配置:屏蔽Nginx版本号(避免泄露服务器信息)
server_tokens off;
# 全局配置:禁止浏览器解析非标准MIME类型(防XSS/文件注入)
types_hash_max_size 2048;

# 核心防护1:禁止外部访问data目录(彻底防止反馈数据文件泄露)
location ^~ /data/ {
deny all;
# 无特征响应:用404代替403,避免攻击者判断目录存在
return 404;
}

# 核心防护2:强化接口文件保护(目标接口:save_feedback/update_status/edit_feedback.php)
# 优先级高于普通PHP解析,仅允许本站/本地请求,屏蔽所有错误信息
location ~* ^/(save_feedback|update_status|edit_feedback)\.php$ {
# 1. Referer强校验:仅允许指定域名、本地回环请求,空Referer直接拒绝
if ($http_referer !~* ^(https?://(你的域名/IP|127.0.0.1|localhost)(:\d+)?/|$)) {
return 404;
}
# 2. 禁止直接访问PHP源文件(防源码泄露)
try_files $uri =404;
# 3. 屏蔽所有PHP错误/警告输出(核心:避免返回任何PHP相关信息)
fastcgi_param PHP_VALUE "display_errors=Off; error_reporting=0";
# 4. Nginx层面屏蔽错误日志输出(避免接口请求日志泄露敏感信息)
error_log off;
# 5. 禁止浏览器缓存接口响应(防止敏感数据被缓存)
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
# 6. 常规PHP FastCGI解析配置(保留原有端口,如9000/9001)
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# 传递完整FastCGI参数,避免PHP解析异常
include fastcgi_params;
# 屏蔽FastCGI错误响应,统一返回无特征页面
fastcgi_intercept_errors on;
error_page 400 403 500 502 =404;
}

# 常规PHP文件解析(非目标接口的普通PHP文件,保留基础防护)
location ~ \.php$ {
try_files $uri =404; # 防止伪静态绕过,文件不存在直接404
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
# 屏蔽PHP错误输出
fastcgi_param PHP_VALUE "display_errors=Off";
}

# 核心防护3:禁止访问隐藏目录/文件(.git/.svn/.env/.htaccess等,优先级高)
location ~ /\. {
deny all;
return 404;
access_log off;
log_not_found off;
}

# 核心防护4:禁止访问敏感备份/日志/配置文件
location ~* (\.user.ini|\.htaccess|\.htpasswd|\.env.*|\.gitignore|\.bak|\.log|\.sql|\.tar|\.zip)$ {
deny all;
return 404;
access_log off;
log_not_found off;
}

# 全局安全头:防XSS、点击劫持、MIME嗅探、跨域等(all参数确保错误页也携带头)
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "same-origin" always; # 限制Referer传递范围
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; # 禁用无用权限

# 禁止访问空目录
autoindex off;
}

本地临时测试(修改 nginx.conf)

若为本地测试环境(如 Windows/Linux 本地 Nginx),可直接在nginx/conf/nginx.confhttp节点内添加全局防护规则(无需单独配置站点):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
http {
# 原有配置不变,新增以下规则
# 全局禁止访问data目录
location ^~ /data/ {
deny all;
return 403;
}
# 全局禁止直接访问txt/php接口文件(仅放行入口文件)
location ~* ^/(save_feedback|update_status|edit_feedback)\.php$ {
if ($http_referer !~* (localhost|127.0.0.1)) {
return 403;
}
}
location ~ \.txt$ {
deny all;
return 403;
}
}

不懂的可以邮件发送你的配置,小柯会直接修改好再发回去给你!注意,复制的是宝塔面板的站点的站点配置中“配置文件”的内容!伪静态的话…CV过去的道理,不能不会吧?

上传源码

云盘获取:feedbacklinks: 一个由世界上最伟大的语言创建的小型链接反馈功能程序。

使用教程

部署步骤

  1. 将所有文件上传到服务器的网站根目录(或子目录,如/feedback/);
  2. 手动创建data子目录,确保 PHP 可读写;
  3. 无需配置数据库,程序会自动创建data/feedbacks.txt数据文件;
  4. 访问前台:http://你的域名/index.php
  5. 访问后台:http://你的域名/admin.php,默认密码admin123务必自行修改,PHP几个文件都需要修改,开头可找到)。

关键修改点

  1. 后台密码:修改admin.phpupdate_status.php中的define('ADMIN_PWD', 'admin123');为自定义复杂密码;
  2. 样式调整:可修改index.phpadmin.php中的内嵌 CSS,调整科技感样式(如颜色、阴影、圆角等);
  3. 隐私说明:可修改index.php中的隐私弹窗文案,适配你的业务场景;
  4. 表单验证:可在save_feedback.php中调整验证规则(如姓名长度、反馈原因字数限制)。

结语

有什么问题和建议随时可以留言或仓库开启issues进行提交,初次尝试,请保证数据安全,勿要投入大范围的生产环境服务器可另开启服务或主机空间。


2026-02-01:发布

2026-02-26:公示栏新增中间页和自动屏蔽链接,防止被搜索引擎因为反馈的违规链接构建在本站html而受到站点惩罚


最后更新2026.2.26 | 开发:2026.2.1

如果你不想将链接公布出来又或者觉得链接都是不好的为什么还要游客跳转过去的可以给index.php替换为以下代码:

link.html

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>朽秋秋雨链接反馈</title>
<!-- 核心:禁止搜索引擎抓取整个页面 + 禁止跟踪外链 -->
<meta name="robots" content="noindex, nofollow, noarchive, nosnippet">
<meta name="referrer" content="no-referrer">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: sans-serif;
}
body {
background: linear-gradient(135deg, #1a202c, #2d3748);
color: #e2e8f0;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1000px;
margin: 0 auto;
background: rgba(30, 41, 59, 0.8);
border-radius: 12px;
box-shadow: 0 0 20px rgba(79, 209, 197, 0.2);
border: 1px solid rgba(79, 209, 197, 0.3);
overflow: hidden;
}
.header {
padding: 20px;
background: linear-gradient(90deg, #2563eb, #0ea5e9);
text-align: center;
border-bottom: 1px solid rgba(79, 209, 197, 0.5);
}
.header h1 {
font-size: 24px;
color: #fff;
font-weight: 600;
}
.btn-group {
display: flex;
justify-content: center;
gap: 20px;
padding: 20px;
border-bottom: 1px solid rgba(79, 209, 197, 0.2);
}
.btn {
padding: 10px 30px;
border: none;
border-radius: 6px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s;
color: #fff;
}
.btn-submit {
background: #2563eb;
}
.btn-submit:hover {
background: #0ea5e9;
box-shadow: 0 0 10px rgba(14, 165, 233, 0.4);
}
.btn-board {
background: #0ea5e9;
}
.btn-board:hover {
background: #2563eb;
box-shadow: 0 0 10px rgba(37, 99, 235, 0.4);
}
.content {
padding: 30px;
display: none;
}
.content.active {
display: block;
}
/* 提交表单样式 */
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
color: #94a3b8;
font-weight: 600;
}
.form-control {
width: 100%;
padding: 12px 15px;
background: rgba(15, 23, 42, 0.8);
border: 1px solid rgba(79, 209, 197, 0.3);
border-radius: 6px;
color: #e2e8f0;
font-size: 16px;
}
.form-control:focus {
outline: none;
border-color: #0ea5e9;
box-shadow: 0 0 10px rgba(14, 165, 233, 0.4);
}
textarea.form-control {
min-height: 120px;
resize: vertical;
}
.submit-btn {
width: 100%;
padding: 12px;
background: #2563eb;
color: #fff;
border: none;
border-radius: 6px;
font-size: 18px;
cursor: pointer;
transition: all 0.3s;
}
.submit-btn:hover {
background: #0ea5e9;
box-shadow: 0 0 15px rgba(79, 209, 197, 0.5);
}
/* 公示板核心样式 */
.board-content {
padding: 10px;
}
.feedback-item {
background: rgba(15, 23, 42, 0.7);
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
border: 1px solid rgba(79, 209, 197, 0.3);
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
.feedback-header {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 15px;
align-items: center;
padding-bottom: 10px;
border-bottom: 1px dashed rgba(79, 209, 197, 0.4);
}
.feedback-name {
color: #0ea5e9;
font-weight: 600;
font-size: 16px;
}
.feedback-status {
padding: 4px 10px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
}
.status-processing {
background: rgba(234, 179, 8, 0.2);
color: #fbbf24;
border: 1px dashed #fbbf24;
}
.status-handled {
background: rgba(34, 197, 94, 0.2);
color: #22c55e;
border: 1px dashed #22c55e;
}
.status-unneeded {
background: rgba(156, 163, 175, 0.2);
color: #9ca3af;
border: 1px dashed #9ca3af;
}
.feedback-time {
color: #94a3b8;
font-size: 12px;
}
.feedback-content-item {
border: 1px dashed rgba(79, 209, 197, 0.5);
border-radius: 6px;
padding: 10px 12px;
margin-bottom: 10px;
background: rgba(20, 33, 61, 0.5);
}
.feedback-content-item:last-child {
margin-bottom: 0;
}
.item-label {
color: #94a3b8;
font-size: 14px;
margin-right: 8px;
font-weight: 600;
}
.masked-url {
color: #38bdf8;
font-size: 14px;
word-break: break-all;
}
.feedback-reason {
font-size: 14px;
line-height: 1.5;
white-space: pre-line;
color: #e2e8f0;
}
/* 图片预览样式(仅展示,无URL) */
.feedback-img {
position: relative;
}
.img-preview {
max-width: 200px;
max-height: 180px;
border-radius: 6px;
border: 1px solid rgba(79, 209, 197, 0.5);
pointer-events: none; /* 彻底禁止点击 */
}
.img-tip {
font-size: 12px;
color: #94a3b8;
margin-top: 5px;
}
.no-data {
text-align: center;
padding: 40px;
color: #94a3b8;
font-size: 16px;
}
/* 提示框样式 */
.alert {
padding: 15px;
border-radius: 6px;
margin-bottom: 20px;
display: none;
}
.alert-success {
background: rgba(34, 197, 94, 0.2);
color: #22c55e;
border: 1px solid rgba(34, 197, 94, 0.3);
}
.alert-error {
background: rgba(239, 68, 68, 0.2);
color: #ef4444;
border: 1px solid rgba(239, 68, 68, 0.3);
}
@media (max-width: 768px) {
.btn-group {
flex-direction: column;
}
.btn {
width: 100%;
}
.feedback-header {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.img-preview {
max-width: 100%;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>朽秋秋雨链接反馈</h1>
</div>
<div class="btn-group">
<button class="btn btn-submit" onclick="showTab('submit')">提交反馈</button>
<button class="btn btn-board" onclick="showTab('board')">处理公示板</button>
</div>

<!-- 提交反馈表单 -->
<div class="content active" id="submit-content">
<div class="alert" id="submit-alert"></div>
<form id="feedback-form">
<div class="form-group">
<label for="name">姓名 *</label>
<input type="text" class="form-control" id="name" name="name" required placeholder="请输入您的姓名">
</div>
<div class="form-group">
<label for="email">联系邮箱 *</label>
<input type="email" class="form-control" id="email" name="email" required placeholder="请输入您的邮箱,方便回复">
</div>
<div class="form-group">
<label for="link">反馈链接 *</label>
<input type="url" class="form-control" id="link" name="link" required placeholder="请输入反馈相关的链接(如网页、商品链接等)">
</div>
<div class="form-group">
<label for="reason">反馈原因 *</label>
<textarea class="form-control" id="reason" name="reason" required placeholder="请详细描述您的反馈原因,换行请按Enter"></textarea>
</div>
<div class="form-group">
<label for="img_url">截图URL(选填)</label>
<input type="url" class="form-control" id="img_url" name="img_url" placeholder="请输入截图的在线URL(如阿里云OSS、腾讯云COS链接)">
</div>
<button type="submit" class="submit-btn">提交反馈</button>
</form>
</div>

<!-- 公示板内容(彻底屏蔽所有完整URL) -->
<div class="content" id="board-content">
<div class="board-content" id="feedback-list">
加载中...
</div>
</div>
</div>
<br>
<div style="text-align: center; width: 100%;">
获取本页面源码:返回博客搜索【链接反馈】
</div>

<script>
// URL脱敏函数(仅保留协议+屏蔽域名,无任何完整URL展示)
function maskUrl(url) {
if (!url) return '无';
try {
const parsed = new URL(url);
const host = parsed.host;
const hostParts = host.split('.');
let maskedHost = host;

if (hostParts.length >= 2) {
const mainHost = hostParts[hostParts.length - 2];
const suffix = hostParts[hostParts.length - 1];
const prefix = hostParts.slice(0, hostParts.length - 2).join('.');

// 强化屏蔽:仅保留前2位,其余全屏蔽(更彻底)
const maskedMain = mainHost.length > 2
? mainHost.substring(0, 2) + '*'.repeat(mainHost.length - 2)
: '*'.repeat(3);

maskedHost = prefix ? `${prefix.substring(0, 2)}*.${maskedMain}.${suffix}` : `${maskedMain}.${suffix}`;
} else {
maskedHost = '*'.repeat(6); // 非标准域名直接显示6个*
}

// 仅返回屏蔽后的域名,不包含路径/参数
return `${parsed.protocol}//${maskedHost}`;
} catch (e) {
return '已屏蔽链接';
}
}

// 切换标签页
function showTab(tab) {
document.querySelectorAll('.content').forEach(el => el.classList.remove('active'));
if (tab === 'submit') {
document.getElementById('submit-content').classList.add('active');
} else if (tab === 'board') {
document.getElementById('board-content').classList.add('active');
loadBoardData();
}
}

// 加载公示板数据(核心:仅传递脱敏后URL,不处理/存储完整URL)
function loadBoardData() {
const feedbackList = document.getElementById('feedback-list');
feedbackList.innerHTML = '加载中...';

fetch('save_feedback.php?action=get_board')
.then(res => {
if (!res.ok) throw new Error('服务器响应异常');
return res.json();
})
.then(result => {
if (result.code === 200) {
renderBoard(result.data);
} else {
feedbackList.innerHTML = `<div class="no-data">数据加载失败:${result.msg}</div>`;
}
})
.catch(err => {
console.error('加载失败:', err);
feedbackList.innerHTML = '<div class="no-data">数据加载失败,请稍后重试</div>';
});
}

// 渲染公示板数据(彻底移除所有完整URL,仅展示脱敏结果)
function renderBoard(data) {
const feedbackList = document.getElementById('feedback-list');

if (data.length === 0) {
feedbackList.innerHTML = '<div class="no-data">暂无反馈记录</div>';
return;
}

let html = '';
data.forEach((item, index) => {
const statusText = item.status === 'processing' ? '正在处理' : (item.status === 'handled' ? '已处理' : '无需处理');
const statusClass = item.status === 'processing' ? 'status-processing' : (item.status === 'handled' ? 'status-handled' : 'status-unneeded');
// 仅使用脱敏后的URL,不引用任何完整URL
const maskedLink = maskUrl(item.link);
const maskedImgUrl = maskUrl(item.img_url);

// 图片仅展示,不显示任何URL(包括脱敏后的),仅提示“已屏蔽”
const imgHtml = item.img_url
? `
<div class="feedback-content-item feedback-img">
<span class="item-label">截图预览:</span><br>
<img src="${item.img_url}" alt="反馈截图" class="img-preview">
<div class="img-tip">截图URL已屏蔽</div>
</div>
`
: '';

html += `
<div class="feedback-item" data-id="${item.id}">
<div class="feedback-header">
<span class="feedback-name">反馈人:${item.name}</span>
<span class="feedback-status ${statusClass}">${statusText}</span>
<span class="feedback-time">提交时间:${item.create_time}</span>
</div>
<!-- 仅展示脱敏后的屏蔽域名,无任何完整URL -->
<div class="feedback-content-item feedback-link">
<span class="item-label">反馈链接:</span>
<span class="masked-url">${maskedLink}</span>
</div>
<!-- 反馈原因 -->
<div class="feedback-content-item feedback-reason">
<span class="item-label">反馈原因:</span>
${item.reason.replace(/\n/g, '<br>&nbsp;&nbsp;&nbsp;&nbsp;')}
</div>
<!-- 截图预览(无URL展示) -->
${imgHtml}
</div>
`;
});

feedbackList.innerHTML = html;
}

// 提交反馈表单
document.getElementById('feedback-form').addEventListener('submit', function(e) {
e.preventDefault();
const alertBox = document.getElementById('submit-alert');
alertBox.style.display = 'none';

const formData = new FormData(this);
fetch('save_feedback.php', {
method: 'POST',
body: formData
})
.then(res => res.json())
.then(data => {
alertBox.className = data.code === 200 ? 'alert alert-success' : 'alert alert-error';
alertBox.textContent = data.msg;
alertBox.style.display = 'block';
if (data.code === 200) this.reset();
})
.catch(err => {
alertBox.className = 'alert alert-error';
alertBox.textContent = '提交失败,请稍后重试';
alertBox.style.display = 'block';
});
});

// 页面加载默认显示提交反馈
window.onload = () => showTab('submit');
</script>
</body>
</html>