无数据库表白墙源码解析:从JSON存储到服务器部署
简介这是一份可直接部署的校园表白墙网站源码包适合个人站长、学生或网页开发爱好者快速搭建表白网页或校园互动社区。源码包采用无数据库设计上传至网站根目录并绑定域名即可运行后台管理入口及默认账号信息已提供安装门槛很低。压缩包共包含639个文件整体仅16.65MB以PHP后端、HTML/CSS/JS前端及各类静态资源为主54个PHP实现动态功能49个CSS和96个JS负责页面样式与交互逻辑PNG/JPG图片构成主要视觉素材结构清晰便于二次开发。目前已有1130人学习下载值得作为练手项目参考。你可借此快速搭建一个真实可用的表白墙网站也能学习无数据库场景下的PHP建站思路稍作改造后即可变成校园留言墙、节日祝福页或送给恋人的惊喜礼物。1. 静态表白墙源码的定位拿到一份校园表白墙源码先别急着上传解压。这套源码最特别的地方是「无数据库上传就能用」但访客提交的每一句话到底写在哪里取决于后台 /admin 的实现方式。很多从网盘下载的版本入口页面是 html样式表是 amazeui 和 bootstrap 的组合后台用一个固定账号密码登录。它适合不想折腾数据库的个人站长、毕设演示、或者给对象做个小站解决的核心问题不是高并发而是「一个页面、一套后台、几分钟跑起来」的轻量表白场景。正因为它没有数据库很多人第一步就卡在不知道数据怎么存、后台改了为什么前台不显示这类问题上。2. 入口页面与静态资源加载链html、css、js 如何串起来2.1 入口文件与默认文档解压后目录里一般能看到 web.config、style.css、amazeui.min.css、bootstrap.css、fonts.css、buttons.css 以及后台 admin 目录。以我拆过的同类源码为例整理出来的典型结构如下├─ index.html # 前台入口也可以叫 index.php ├─ web.config # IIS 默认文档 / 重写规则 ├─ css/ │ ├─ style.css # 自定义主样式 │ ├─ amazeui.min.css # 移动端 UI 框架 │ ├─ bootstrap.css # 栅格与基础组件 │ ├─ bootstrap.min.css │ └─ buttons.css # 按钮样式 ├─ fonts/ # 图标字体 ├─ js/ # 前台交互脚本 ├─ data/ # 无数据库版的留言存储目录 └─ admin/ # 后台管理这里要先说清楚一个容易踩的坑浏览器输入域名后先找哪个文件由服务器决定。如果你跑在 Windows 的 IIS 上web.config 里的 defaultDocument 会决定先加载 index.html 还是 index.php如果跑在 Nginx 上默认首页由 index 指令控制。无论哪个入口文件里最终要出现的是!doctype html开头的完整 HTML 文档搜索引擎收录时也以这个文档的 title、description、keywords 为准。所以你想改站点标题第一步不是改后台而是改入口文件的 head 区。2.2 样式表的分工与取舍一个包里同时出现 amazeui、bootstrap、buttons、fonts看起来很多实际各自负责的范围并不重叠。文件作用什么时候需要style.css覆盖框架默认样式的自定义层必留改主题色和间距都在这amazeui.min.css移动端组件、表单、按钮、弹层前台卡片和表单依赖它bootstrap.css栅格布局、基础排版做响应式布局时用bootstrap.min.cssbootstrap 的压缩版线上用 min 版本地调试用未压缩版buttons.css按钮配色和 hover 动效只有按钮区用到fonts.css图标字体与特殊字符有 icon 字体时保留如果你发现页面样式错乱先看引入顺序。通用做法是 bootstrap 在最低层、amazeui 在其上、style.css 必须放在最后否则你写的覆盖规则会被框架的同名类名压住。比较常见的问题是把 bootstrap.css 和 bootstrap.min.css 一起引入浏览器虽然只执行后者但多一次请求和潜在冲突线上只保留一个即可。2.3 从!doctype html开始拆入口结构拿一份精简后的入口页面举例!doctype html html langzh-cn head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 meta namekeywords content校园表白墙, 表白网页, html表白网站 title校园表白墙 - 大胆说出爱/title link relstylesheet hrefcss/bootstrap.min.css link relstylesheet hrefcss/amazeui.min.css link relstylesheet hrefcss/style.css /head body div idwall-app/div script srcjs/wall-data.js/script script srcjs/main.js/script /body /html这段代码里的langzh-cn告诉浏览器和搜索引擎当前页面语言是简体中文对 SEO 有帮助meta charsetutf-8决定中文是否乱码所有表白内容只要经过后台写入都必须保持 utf-8 编码meta nameviewport是手机端适配的关键没有它iPhone 和安卓上页面会被缩放得很小。CSS 放在 head 里是为了让页面加载时有基本样式js/main.js放在 body 底部是为了不阻塞首屏渲染。如果你从下载包里拿到的入口文件没有 viewport直接补上这行移动端显示立刻正常。2.4 无数据库下页面数据从哪来前面已经出现 data 目录这是无数据库方案的核心。常见做法有两类一是后端用 PHP 把留言追加写入一个 JSON 文件二是纯前端把留言存 localStorage。区分方法很简单清空浏览器缓存后刷新如果之前发过的留言还在说明走了服务端文件存储如果只剩默认示例数据说明是 localStorage 方案。后面章节以更常见的 JSON 文件存储为例展开因为这样别人用不同手机访问都能看到同一份留言localStorage 只适合自己本地开着玩。3. 表白数据写入与读取无数据库方案的实现细节3.1 为什么无数据库方案够用校园表白墙的本质是低频写入、高频读取。一个班级或一个学院一天能提交几十条已经很多这种压力下MySQL 反而成了负担要创建库表、要维护账号、要在虚拟主机上折腾权限。JSON 文件方案把整张表变成一个 messages.json前台用 fetch 读取后台用 file_put_contents 追加部署时只要保证 data 目录可写。对比项MySQL 方案JSON 文件方案部署依赖需要数据库服务仅需 PHP 文件权限备份mysqldump直接下载 data 目录并发能力高低并发够用几百人同时提交会丢数据适合场景正式运营、多端读写校园表白、毕业留念、个人小站所以「无数据库上传就能用」不是功能阉割而是在这个场景里性价比最高的选择。要注意的是文件方案扛不住刷接口如果源码直接暴露了写入接口恶意脚本可以一秒写几千条。后面章节会讲加一道简单校验来兜底。3.2 留言数据长什么样无数据库后台写入时数据结构一般长这样[ { nickname: 来自计算机学院的小张, content: 图书馆三楼靠窗的位置我注意你一个月了。, time: 2025-01-01 20:18:00, status: 1 }, { nickname: 匿名, content: 祝考研的学姐一切顺利。, time: 2025-01-02 09:30:00, status: 2 } ]数组里的每条记录包含昵称、内容、时间和状态字段。status 是后台控制显隐的关键1 表示正常展示2 表示待审核或已隐藏。前台读取时根据 status 过滤后台编辑时只改这个值不需要删数据避免手滑把某条表白直接弄丢。字段名不一定是这两个也可能是 name、msg、addtime但作用完全一致。你先打开 data 下的 JSON 文件确认真实字段再决定改前台渲染代码否则会出现内容读到了但页面显示空白。3.3 后台写入的 PHP 实现与参数细节下面这段逻辑是我在同类源码里常用的保存方式可以直接替换或对照理解原包里的保存脚本?php // save_msg.php $nickname trim($_POST[nickname] ?? 匿名); $content trim($_POST[content] ?? ); if ($content ) { exit(json_encode([code 0, msg 内容不能为空])); } $nickname mb_substr($nickname, 0, 12); $content mb_substr($content, 0, 200); $file __DIR__ . /data/messages.json; $list file_exists($file) ? json_decode(file_get_contents($file), true) : []; $list[] [ nickname $nickname, content $content, time date(Y-m-d H:i:s), status 1, ]; file_put_contents($file, json_encode($list, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), LOCK_EX); echo json_encode([code 1, msg ok]);这段代码核心是三步取参数、截断长度、追加写入。mb_substr按字符截断而不是按字节避免把中文昵称切断成乱码date(Y-m-d H:i:s)生成的是服务器本地时间如果服务器时区不对记得在文件头部加date_default_timezone_set(Asia/Shanghai)file_put_contents的LOCK_EX参数会在写入时加独占锁阻止两个请求同时写坏文件这是低并发方案里成本最低的防冲突手段。JSON_UNESCAPED_UNICODE保证中文在 JSON 里可读JSON_PRETTY_PRINT让文件在编辑器里按行展示排错时一眼能看出格式问题。注意保存路径要真实存在如果没有 data 目录file_put_contents不会自动建目录页面会报 500。3.4 前台读取与防 XSS 渲染对应前台用 fetch 拉 JSON 再渲染成卡片fetch(data/messages.json, { cache: no-store }) .then((res) res.json()) .then((list) { const box document.getElementById(wall-app); const valid (list || []).filter((item) item.status 1); box.innerHTML valid.map((item) div classwall-card p classwall-nick${escapeHtml(item.nickname)}/p p classwall-text${escapeHtml(item.content)}/p span classwall-time${escapeHtml(item.time)}/span /div ).join(); function escapeHtml(str) { const div document.createElement(div); div.textContent str null ? : String(str); return div.innerHTML; } }) .catch((err) console.error(留言加载失败, err));这里的cache: no-store很关键它告诉浏览器每次刷新都要重新请求 JSON否则你在后台审核通过一条留言后前台可能还显示旧文件。filter((item) item.status 1)对应前面说的状态字段只有正常展示的留言进 DOM。escapeHtml用 textContent 再取 innerHTML 的方式转义掉script和标签符号防止有人往昵称里塞一段 JS 造成存储型 XSS。这个细节很多静态源码都没做上线前务必保留。4. 后台 /admin 登录与内容管理流程4.1 登录页的实现与判断方法演示站的后台地址是 /admin默认账号密码都是 ygwzjs.cn。网上流传的版本里登录校验通常分两种一种是纯前端 JS 校验账号密码写死在 admin/login.js 里另一种是 PHP 会话校验登录成功后写入 session。区别在于前者直接打开源码就能看到密码后者需要知道服务端逻辑。拿到源码先做一件事用浏览器无痕窗口访问 /admin/login.js看是否存在账号相关的字符串。如果有明文账号第一件事就是改掉防止别人用默认密码登录你的后台改页面。4.2 后台操作步骤以实际使用流程来看管理员的日常操作集中在几个页面里访问 http(s)://你的域名/admin进入登录页。输入账号 ygwzjs.cn 和密码 ygwzjs.cn点击登录。进入表白列表列表通常按时间倒序展示所有留言。对需要隐藏或删除的记录操作常见按钮有「隐藏」「通过」「删除」「置顶」。有些版本支持直接编辑留言内容注意编码要选 utf-8否则中文会变成乱码。这里的「隐藏」不是删掉记录而是把 status 从 1 改成 2前台渲染时自然过滤掉。如果是纯前端版本这些操作直接修改 localStorage换浏览器就看不到如果是文件版本修改的是 data 目录下的 messages.json。建议先发一条测试留言看它落在哪里再决定用哪种方式备份。不同版本后台按钮的叫法不一样但对应动作就四种按钮实际效果对应字段变化隐藏前台不再显示数据保留status: 1 - 2通过恢复显示status: 2 - 1删除从数据文件中移除移除整条记录置顶排到列表最前增加或修改排序值4.3 后台登录校验收紧从明文比对到 session容易被人忽略的是默认源码里的登录可能只是一个 if 判断比如?php // admin/login_check.php $user $_POST[user] ?? ; $pass $_POST[pass] ?? ; $okUser ygwzjs.cn; $okPass ygwzjs.cn; if ($user $okUser $pass $okPass) { session_start(); $_SESSION[admin] true; header(Location: index.php); } else { echo 账号或密码错误; }这段逻辑能跑但存在几个问题账号密码写死在 PHP 里一旦源码泄露就形同虚设没有验证码自动化工具可以暴力尝试session 有效期没有限制登录后长时间不关浏览器就一直有效。更稳的做法是把账号密码放到后台目录外的 config.php 里并且在入口处加if (empty($_SESSION[admin])) exit(请先登录);这样前台用户无法直接访问 admin 目录下的页面文件。4.4 目录与文件权限设置后台能登录成功但保存失败大多数情况是文件写权限没开。Linux 服务器上执行chown -R www-data:www-data /var/www/html chmod -R 755 /var/www/html chmod -R 777 /var/www/html/data第一条把站点目录属主改成 Nginx 或 PHP-FPM 的运行用户第二条让目录和文件可读可执行第三条只放开 data 目录的写权限。不要图省事直接chmod 777整个站点否则任何脚本都能往你的网站目录里写入木马。Windows 的 IIS 下则要右键 data 目录在安全选项卡里给 IUSR 或 IIS_IUSRS 添加修改权限。权限设置完重新提交一条留言并查看 messages.json 的时间戳如果时间变化就说明写入链路通了。5. 部署到服务器web.config、Nginx 与上线检查5.1 上传文件与目录校验不管用宝塔、FTP 还是命令行上传前先在本地把压缩包解压确认入口文件在根目录而不是嵌套一层文件夹。常见错误是把源码上传成了yourdomain.com/表白墙源码/index.html访问网站变成目录列表。命令行上传推荐用 rsync只同步变化文件rsync -avz --exclude*.zip ./biao-bai/ root你的服务器IP:/var/www/html/这条命令的作用是把本地biao-bai/目录内容同步到服务器站点根目录。-a保留文件权限和时间戳-v显示过程-z传输前压缩。排除掉 zip 包是为了避免压缩包被人直接下载。同步完成后用ls -l /var/www/html/index.html确认入口文件存在再继续配置服务器。5.2 web.config 与 IIS 默认文档源码自带 web.config 是 IIS 下的配置文件。它最常见的用途是指定默认文档因为静态源码入口可能是 index.html而 IIS 默认优先找 index.aspx?xml version1.0 encodingUTF-8? configuration system.webServer defaultDocument files clear / add valueindex.html / add valueindex.php / /files /defaultDocument /system.webServer /configurationclear清掉系统自带的默认文档列表避免站点根目录同时存在 index.html 和 index.php 时选错。add value按从上到下的顺序匹配排在前面的先被请求。如果你在 IIS 下打开页面返回 403.14八成就是默认文档没匹配上检查这里即可。另外有些版本的 web.config 里会带 rewrite 规则用于去掉 URL 后缀不要随意删除否则后台链接会 404。5.3 Nginx 站点配置部署到 Nginx 时web.config 不生效需要在 server 块里写对应配置server { listen 80; server_name biaobai.example.com; root /var/www/html; index index.html index.php; location / { try_files $uri $uri/ 404; } location ~ \.php$ { include fastcgi_params; fastcgi_pass unix:/run/php/php8.1-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } }root指向站点根目录index决定访问根路径时加载哪个文件顺序很重要如果源码入口是 index.html就把它放前面。try_files先找真实文件再找目录都找不到返回 404避免访问不存在的路径时泄露目录列表。location ~ \.php$是给 PHP 文件用的如果你的版本是纯 HTML这段可以不要但如果后台是 PHP 实现的却没有这段配置登录时就会直接下载 .php 文件而不是执行。5.4 绑定域名、HTTPS 与缓存验证域名解析生效后先访问 http://域名 确认首页能开再申请证书。用 certbot 的话一步到位certbot --nginx -d biaobai.example.com -d www.biaobai.example.com申请成功后用 curl -I 检查返回头curl -I https://biaobai.example.com重点看两处HTTP/2 200表示协议正常strict-transport-security出现表示 HSTS 已开。如果首页能看到但 CSS 全乱打开浏览器开发者工具看 Network 面板404 的文件通常是 css 或 fonts 路径问题因为子目录页面的相对路径写错导致加载不到根目录样式。这种情况把页面里的hrefcss/style.css改成href/css/style.css即可。6. 换肤、敏感词过滤与返回顶部常用改造技巧6.1 用 CSS 变量快速换肤很多表白墙源码把主色写死成粉色系改色要翻遍整个 style.css。做成 CSS 变量后换主题只需要改一处:root { --primary: #ff6b81; --bg: #fdf6f0; --card-bg: #ffffff; } body { background: var(--bg); } .wall-card { background: var(--card-bg); border: 1px solid var(--primary); }当 style.css 里框架类名已经定义过背景或边框时var()写法要放在 style.css 末尾否则会被 amazeui 的同名类覆盖。想换成科技蓝把--primary改成#4a90d9--bg改成#f0f4fb即可。6.2 前端敏感词过滤与提交拦截静态源码没有后端内容审核但可以在提交动作上做一道前端拦截const sensitiveWords [代做, 加微信]; function checkContent(text) { return sensitiveWords.some((word) text.includes(word)); }在表单提交的 click 事件里先执行checkContent(value)命中就 return false 并提示用户修改。some的作用是遍历敏感词数组只要有一个词在内容里出现就返回 true性能开销可忽略。这道防线只对普通访客有效接口层仍要在 PHP 保存脚本里用同样的数组再判断一次否则 curl 直接 POST 就能绕过。6.3 返回顶部按钮与移动端验证列表页一长返回顶部按钮比手动滑动更省事const backTop document.getElementById(backTop); window.addEventListener(scroll, function () { backTop.style.display window.scrollY 300 ? flex : none; }, { passive: true }); backTop.addEventListener(click, function () { window.scrollTo({ top: 0, behavior: smooth }); });window.scrollY 300是按钮出现的滚动阈值值越大越不容易打扰首屏{ passive: true }告诉浏览器不调用 preventDefault滚动监听不会拖慢页面。改造完按这个顺序验证先在 PC 端改 CSS 变量看整站变色再提交一条包含敏感词的测试内容看是否被拦截最后滚动 500 像素确认按钮出现并平滑回顶。前端改造不涉及数据迁移唯一要注意的是别把 style.css 里原有类名删了。本文还有配套的精品资源点击获取