从零到搭建服务器粘贴板

发布于:2025-09-05 ⋅ 阅读:(19) ⋅ 点赞:(0)

1️⃣ 需求设定

我想做一个纯前端粘贴板,特点:

  • 通过宝塔部署在自己的服务器上

  • 输入 IP/xxx 就创建一个新的粘贴板页面

  • 页面可以展示和编辑文本

  • 可以保存到服务器,让不同设备访问同一链接时看到同一份内容

  • 样式简洁舒适

2️⃣ 前端初始方案

  • 用一个 单文件 index.html,根据 location.pathname 判断当前粘贴板的名字(slug)

  • 最初版本只保存到 浏览器 localStorage

  • 功能:输入/粘贴文本,自动保存到本地

问题:

  • 只是本地保存,不同设备无法共享

  • 样式简单,视觉效果一般

3️⃣ 添加服务器保存功能

  • 写一个 极简 PHP 后端 paste.php

    • GET 请求读取 /pastes/{slug}.txt

    • POST 请求保存到 /pastes/{slug}.txt

  • 前端改成 fetch 调用 PHP 保存/读取内容

  • 任意路径 /xxx 对应 /pastes/xxx.txt

遇到的问题和解决方法:

PHP 语法报错:

  • 原先 $ base/$slug.txt 写法在 PHP 低版本报错

  • 修正为 $file = $base . ‘/’ . $slug . ‘.txt’;

前端 JS 报错:

  • 原先使用复杂正则表达式导致 Invalid regular expression flags

  • 简化 JS,仅用 location.pathname 解析 slug

PHP 无法执行:

  • 宝塔站点 PHP 版本选择只有“静态”和“自定义”

  • 解决:安装 PHP(如 8.1)并绑定到站点

路径/权限问题:

  • /home/dist/pastes/ 必须可写(权限 775 或 777)

  • paste.php 必须在站点根目录

4️⃣ 宝塔配置

  • 新建网站:

  • 域名/IP:117.72.99.159

  • 根目录:/home/dist

  • PHP版本:绑定已安装 PHP 8.1

上传文件:

/home/dist/index.html
/home/dist/paste.php
/home/dist/pastes/   ← 可写

伪静态配置(保证 /xxx 都返回 index.html):

location / {
    try_files $uri $uri/ /index.html;
}
location ~ \.php$ {
    include fastcgi.conf;
    fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
}

5️⃣ 测试流程

  • 访问 http://117.72.99.159/zqk → 新建粘贴板

  • 修改内容 → 自动保存到 /home/dist/pastes/zqk.txt

  • 切换设备/浏览器 → 打开相同路径 /zqk → 数据同步

✅ 功能成功实现

6️⃣ 样式优化

  • 使用现代卡片风格、圆角阴影

  • 背景柔和,字体易读

  • 文本框宽度自适应,防止超出父容器

  • 状态栏显示保存时间

7️⃣ 最终源码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>简易粘贴板</title>
<style>
  * {margin:0;padding:0;box-sizing:border-box;}
  body {
    font-family: "Helvetica Neue", Arial, sans-serif;
    background: #f0f2f5;
    color: #333;
  }
  .wrap {
    max-width: 900px;
    margin: 40px auto;
    padding: 20px;
    background: #fff;
    border-radius: 12px;
    box-shadow: 0 8px 20px rgba(0,0,0,0.1);
  }
  h1 {font-size: 22px;margin-bottom:12px;color:#1f2937;}
  .slug {font-weight:normal;color:#2563eb;}
  textarea {
    width: 100%;
    box-sizing: border-box;
    min-height: 70vh;
    resize: vertical;
    padding: 14px;
    font-size: 16px;
    line-height: 1.5;
    border: 1px solid #d1d5db;
    border-radius: 8px;
    outline-color:#2563eb;
    background:#f9fafb;
  }
  .status {
    margin-top:10px;
    font-size:13px;
    color:#6b7280;
    text-align:right;
  }
</style>
</head>
<body>
<div class="wrap">
  <h1>📝 粘贴板:<span class="slug" id="slug"></span></h1>
  <textarea id="pad" placeholder="在这里输入或粘贴内容..."></textarea>
  <div class="status" id="status">—</div>
</div>
<script>
(async function(){
  const slug = decodeURIComponent(location.pathname.replace(/\/$/,'').slice(1) || 'home');
  const elSlug = document.getElementById('slug');
  const pad = document.getElementById('pad');
  const status = document.getElementById('status');
  elSlug.textContent = '/' + slug;

  function setStatus(text){ status.textContent = text; }

  try {
    const res = await fetch('/paste.php?slug=' + encodeURIComponent(slug));
    if(res.ok){ 
      pad.value = await res.text();
      setStatus('已加载服务器内容');
    } else {
      setStatus('新建空板子');
    }
  } catch(e){
    setStatus('加载失败');
  }

  let t;
  pad.addEventListener('input', ()=>{
    clearTimeout(t);
    t = setTimeout(async ()=>{
      try{
        await fetch('/paste.php',{
          method:'POST',
          headers:{'Content-Type':'application/json'},
          body: JSON.stringify({slug, content: pad.value})
        });
        setStatus('已保存到服务器 ' + new Date().toLocaleTimeString());
      } catch(e){
        setStatus('保存失败');
      }
    },500);
  });
})();
</script>
</body>
</html>
paste.php
<?php
header('Content-Type: text/plain; charset=utf-8');

$base = __DIR__ . '/pastes';
if (!is_dir($base)) { mkdir($base, 0775, true); }

function safe_slug($s) { return preg_match('/^[A-Za-z0-9_-]{1,128}$/', $s); }

if ($_SERVER['REQUEST_METHOD'] === 'GET') {
    $slug = $_GET['slug'] ?? '';
    if (!safe_slug($slug)) { http_response_code(400); exit('Bad slug'); }
    $file = $base . '/' . $slug . '.txt';
    if (!file_exists($file)) { http_response_code(204); exit; }
    readfile($file);
    exit;
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $raw = file_get_contents('php://input');
    $j = json_decode($raw, true);
    if (!$j || !isset($j['slug'])) { http_response_code(400); exit('Invalid body'); }
    $slug = $j['slug'];
    if (!safe_slug($slug)) { http_response_code(400); exit('Bad slug'); }
    $content = $j['content'] ?? '';
    $file = $base . '/' . $slug . '.txt';
    if (file_put_contents($file, $content) === false) { http_response_code(500); exit('Write fail'); }
    echo 'OK';
    exit;
}

http_response_code(405);
echo 'Method Not Allowed';

这样就完成了
在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到