洛丽糖 https://luolt.cn/ zh-CN 洛丽糖(luolt.cn),致力于互联网资源的共享, 分享各类技术教程,typecho主题模板,zblog主题模板,网站源码等各种资源。 Mon, 02 Mar 2026 11:54:56 +0800 Mon, 02 Mar 2026 11:54:56 +0800 一款不错的额404错误页面源码 https://luolt.cn/archives/3122.html https://luolt.cn/archives/3122.html Mon, 02 Mar 2026 11:54:56 +0800 寻梦xunm 这一款不错的404页面是博主的一个域名没有正确解析后,访问发现的,看着不错就个保存了下来,以后可能用得到。

火狐截图_2026-03-02T03-51-29.190Z.png

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>404 - 页面未找到</title>
<meta name="robots" content="noindex, nofollow" />
<style>
/* ============================
   404 · 居中单卡片(企业蓝 · 微调精修)
   - 仅保留中间内容
   - 出错时间 & 出错 URL + 一键复制
   - IE11 安全:为 var() 提供回退;诊断区用 Flex
   ============================ */
:root{
  --brand-600:#2563EB; --brand-700:#1E48CC;
  --ink:#0B1220; --muted:#5B677C;
  --line:#E6EAF2; --card:#FFFFFF; --bg:#F7F9FC;
  --radius-lg:18px;
  --shadow-lg:0 10px 26px rgba(12,28,64,.10), 0 3px 10px rgba(12,28,64,.04);
}

*{ box-sizing:border-box; }
html,body{ height:100%; }
body{
  margin:0;
  background:#F7F9FC; background:var(--bg);
  color:#0B1220; color:var(--ink);
  font:14px/1.65 "Microsoft YaHei","Segoe UI",system-ui,-apple-system,Arial,"PingFang SC","Hiragino Sans GB","Noto Sans CJK SC",sans-serif;
  -webkit-font-smoothing:antialiased; -moz-osx-font-smoothing:grayscale;
}

/* 舞台:全屏居中,仅一块内容 */
.stage{
  min-height:100vh;
  display:flex; align-items:center; justify-content:center;
  padding:18px;
}

/* 主卡片:左文右图(精修留白) */
.panel{
  width:100%;
  max-width:1020px;
  display:flex; align-items:stretch; justify-content:space-between;
  gap:22px; flex-wrap:wrap;
  background:#FFFFFF; background:var(--card);
  border:1px solid #E6EAF2; border:1px solid var(--line);
  border-radius:18px; border-radius:var(--radius-lg);
  box-shadow:var(--shadow-lg);
  overflow:hidden;
}

/* 左侧内容 */
.col-text{ flex:1 1 520px; min-width:320px; padding:26px 26px 20px 26px; }
.kicker{
  display:inline-flex; align-items:center; gap:8px;
  height:24px; padding:0 10px; border-radius:999px;
  background:#EEF3FF; border:1px solid #DCE7FF;
  color:#14336C; font-size:12px; font-weight:700;
}
.kicker .pill{
  width:8px; height:8px; border-radius:50%;
  background:#2563EB; background:var(--brand-600);
  box-shadow:0 0 0 3px rgba(37,99,235,.12);
}

h1{
    margin: 10px 0 4px;
    font-size: 55px;
    line-height: 1;
    letter-spacing: .3px;
    font-weight: 900;
    background: linear-gradient(180deg,#0B1220,#2A3D61);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    filter: drop-shadow(0 6px 18px rgba(15,23,42,.06));
    white-space: nowrap;
}
.subtitle{ margin:0 0 8px; font-size:18px; font-weight:700; color:#12203A; }
.desc{ margin:0 0 4px; color:#5B677C; color:var(--muted); font-size:14px; }

/* 列表紧凑 + 对齐 */
.reason-list{ margin:12px 0 0; padding:0; list-style:none; }
.reason-list li{
  display:flex; gap:10px; align-items:flex-start;
  padding:8px 10px; border:1px solid #E9EEF7; border-radius:10px;
  background:#fff; color:#23314E; font-size:14px;
}
.reason-list li + li{ margin-top:8px; }
.reason-list .dot{
  flex:0 0 7px; width:7px; height:7px; margin-top:7px; border-radius:50%;
  background:#1E48CC; background:var(--brand-700);
}

/* 诊断信息卡(Flex 两列,支持 IE11) */
.diag{
  margin-top:12px; border:1px dashed #D8E2F6; border-radius:10px; background:#FAFCFF;
  padding:10px 12px;
}
.diag-header{ display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom:6px; }
.diag-title{ font-size:12px; font-weight:800; color:#103063; display:flex; align-items:center; gap:8px; }
.diag-title .badge{ width:8px; height:8px; border-radius:50%; background:#2563EB; background:var(--brand-600); box-shadow:0 0 0 3px rgba(37,99,235,.12); }
.copy-btn{
  -webkit-appearance:none; appearance:none; cursor:pointer; user-select:none;
  display:inline-flex; align-items:center; gap:6px; height:28px; padding:0 10px;
  border-radius:999px; border:1px solid #CBD6EA; background:#fff; color:#1B2B4A; font-size:12px; font-weight:700;
  transition: box-shadow .12s ease, transform .08s ease, background-color .15s ease, border-color .15s ease;
}
.copy-btn:hover{ transform: translateY(-1px); }
.copy-btn:focus, .copy-btn:focus-visible{ outline:none; box-shadow:0 0 0 3px rgba(37,99,235,.22); }

/* 两列(标签 100px,值 自适应) */
.diag-row{
  display:flex; align-items:center; gap:10px; margin:6px 0;
}
.diag-label{
  width:100px; flex:0 0 100px;
  color:#5A6A86; font-size:12px; text-align:right;
}
.diag-value{
  flex:1 1 auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  font-size:13px; background:#FFFFFF; border:1px solid #E3EBFA; border-radius:8px; padding:6px 8px; color:#1D2B4A; overflow:auto;
  word-break: break-all;
}

/* 行动按钮(仅“刷新”) */
.actions{ display:flex; gap:10px; flex-wrap:wrap; margin-top:16px; }
.btn{
  -webkit-appearance:none; appearance:none; user-select:none;
  display:inline-flex; align-items:center; justify-content:center; gap:8px;
  height:40px; padding:0 16px; border-radius:999px;
  border:1px solid transparent; cursor:pointer; text-decoration:none;
  font-size:14px; font-weight:700;
  transition:transform .08s ease, box-shadow .12s ease, background-color .15s ease, border-color .15s ease;
  will-change: transform;
}
.btn:focus, .btn:focus-visible{ outline:none; box-shadow:0 0 0 3px rgba(37,99,235,.22); }
.btn svg{ width:18px; height:18px; }
.btn-primary{
  background:linear-gradient(180deg,#2563EB,#1E48CC); background:linear-gradient(180deg,var(--brand-600),var(--brand-700));
  color:#fff; box-shadow:0 8px 18px rgba(37,99,235,.20);
}
.btn-primary:hover{ transform:translateY(-1px); }

/* 提示(新增):与诊断信息同色系,克制不抢眼 */
.note{
  margin-top:10px; padding:8px 10px; border:1px dashed #D8E7FF; border-radius:8px;
  background:#F6FAFF; color:#5B677C; font-size:12px;
  display:flex; align-items:flex-start; gap:8px;
}
.note svg{ width:16px; height:16px; flex:0 0 16px; }

/* 右侧插画:断开链接(轻量更干净) */
.col-art{
  flex:1 1 360px; min-width:280px; position:relative;
  background:
    radial-gradient(60% 60% at 18% 18%, rgba(37,99,235,.10), rgba(37,99,235,0) 70%),
    radial-gradient(60% 60% at 88% 10%, rgba(37,99,235,.08), rgba(37,99,235,0) 70%),
    linear-gradient(180deg,#FAFBFF 0%, #F3F7FF 100%);
  border-left:1px solid #E6EAF2; border-left:1px solid var(--line);
  display:flex; align-items:center; justify-content:center; padding:16px;
}
.art{
  width:90%; max-width:460px; height:260px;
  background:#FFFFFF; border:1px solid #E2E8F6; border-radius:14px;
  box-shadow:0 12px 28px rgba(16,40,100,.10);
  position:relative; overflow:hidden;
}
.art .band{ position:absolute; top:0; left:0; right:0; height:64px; background:linear-gradient(90deg, rgba(37,99,235,.14), rgba(37,99,235,0) 70%); }
.art .num{ position:absolute; right:-8px; bottom:-16px; font-weight:900; font-size:128px; line-height:1; letter-spacing:3px; color:#EEF2FA; }
.art svg{ position:absolute; inset:0; width:100%; height:100%; }

/* 自适应 */
@media (max-width: 860px){
  .col-text{ padding:22px 18px; }
  h1{ font-size:50px; }
}
@media (max-width: 520px){
  .art{ height:220px; }
  .diag-label{ width:90px; flex-basis:90px; }
}
@media (prefers-reduced-motion: reduce){ .btn, .copy-btn{ transition:none; } }

/* 屏幕阅读器状态文本 */
.status-sr{ position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden; }
</style>
</head>
<body>

<main class="stage" role="main" aria-labelledby="title" aria-describedby="desc">
  <section class="panel" aria-label="页面未找到">
    <!-- 左侧文案 -->
    <div class="col-text">
      <span class="kicker"><span class="pill" aria-hidden="true"></span> 系统提示</span>
      <h1 id="title">Website Error</h1>
      <p class="subtitle">站点未找到或状态异常</p>
      <p id="desc" class="desc">可能的原因如下,请逐项排查:</p>

      <ul class="reason-list">
        <li><span class="dot" aria-hidden="true"></span>主机控制面板未绑定域名(请确认 <code>www</code> 和 <code>@</code> 已分别绑定)。</li>
        <li><span class="dot" aria-hidden="true"></span>域名解析错误,请检查您的域名 DNS 解析记录是否正确。</li>
        <li><span class="dot" aria-hidden="true"></span>产品到期时间过长,当前域名可能已被解除绑定。</li>
      </ul>

      <!-- 诊断信息(Flex 两列) -->
      <section class="diag" aria-labelledby="diag-title">
        <div class="diag-header">
          <div class="diag-title" id="diag-title"><span class="badge" aria-hidden="true"></span> 诊断信息</div>
          <button type="button" class="copy-btn" id="copyDiag" aria-label="复制诊断信息">
            <!-- copy icon -->
            <svg viewBox="0 0 24 24" fill="none" width="16" height="16" aria-hidden="true">
              <rect x="9" y="9" width="10" height="10" rx="2" stroke="currentColor" stroke-width="2"/>
              <rect x="5" y="5" width="10" height="10" rx="2" stroke="currentColor" stroke-width="2" opacity=".6"/>
            </svg>
            复制
          </button>
        </div>

        <div class="diag-row">
          <div class="diag-label">出错时间</div>
          <div class="diag-value"><time id="err-time" datetime="">—</time></div>
        </div>
        <div class="diag-row">
          <div class="diag-label">出错 URL</div>
          <div class="diag-value" id="err-url">—</div>
        </div>
      </section>

      <!-- 新增提示:请记录诊断信息 -->
      <p class="note" role="note" aria-label="处理建议">
        <svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
          <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"></circle>
          <path d="M12 7.5v.1m0 3.4v5" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path>
        </svg>
        提示:如有必要,请将上述诊断信息记录并提供给技术人员处理。
      </p>

      <!-- 仅保留刷新按钮 -->
      <div class="actions" role="group" aria-label="页面操作">
        <button type="button" class="btn btn-primary" onclick="location.reload()" aria-label="刷新当前页面">
          <svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
            <path d="M20 7v6h-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
            <path d="M20 13a8 8 0 1 1-2.34-5.66L20 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
          </svg>
          刷新页面
        </button>
      </div>

      <div class="status-sr" id="live-status" aria-live="polite"></div>
    </div>

    <!-- 右侧插画:断开链接(更克制) -->
    <div class="col-art" aria-hidden="true">
      <div class="art">
        <div class="band"></div>
        <div class="num">404</div>

        <!-- Broken Link Illustration (精简线条) -->
        <svg viewBox="0 0 800 260" role="img" aria-label="断开链接插画">
          <defs>
            <pattern id="dots" width="16" height="16" patternUnits="userSpaceOnUse">
              <circle cx="1" cy="1" r="1" fill="#E6EEFA"/>
            </pattern>
            <linearGradient id="grad" x1="0" x2="1" y1="0" y2="1">
              <stop offset="0%" stop-color="#2563EB"/>
              <stop offset="100%" stop-color="#1E48CC"/>
            </linearGradient>
          </defs>
          <rect x="0" y="0" width="800" height="260" fill="url(#dots)"/>
          <!-- 左链环 -->
          <g transform="translate(188,120) rotate(-14)">
            <rect x="-60" y="-22" rx="16" ry="16" width="200" height="44" fill="#FFFFFF" stroke="#CFE0FF" stroke-width="3"/>
            <rect x="-46" y="-10" rx="9" ry="9" width="172" height="20" fill="#F3F8FF" stroke="#DCE8FF"/>
          </g>
          <!-- 右链环 -->
          <g transform="translate(502,120) rotate(14)">
            <rect x="-60" y="-22" rx="16" ry="16" width="200" height="44" fill="#FFFFFF" stroke="#CFE0FF" stroke-width="3"/>
            <rect x="-46" y="-10" rx="9" ry="9" width="172" height="20" fill="#F3F8FF" stroke="#DCE8FF"/>
          </g>
          <!-- 断裂闪电 & 点缀 -->
          <path d="M394 94 L410 122 L392 134 L408 160" fill="none" stroke="url(#grad)" stroke-width="6.5" stroke-linecap="round" stroke-linejoin="round"/>
          <circle cx="392" cy="134" r="3" fill="#2563EB"/>
          <circle cx="404" cy="118" r="2" fill="#2563EB" opacity=".7"/>
          <circle cx="412" cy="146" r="2" fill="#2563EB" opacity=".5"/>
        </svg>
      </div>
    </div>
  </section>
</main>

<script>
/* ========== 诊断信息填充(IE11 兼容写法) ========== */
(function(){
  function pad2(n){ return (n<10 ? '0' : '') + n; }
  function tzOffsetString(d){
    var offMin = d.getTimezoneOffset();
    var totalMin = -offMin; // 东区为正
    var sign = totalMin >= 0 ? '+' : '-';
    var absMin = Math.abs(totalMin);
    var hh = pad2(Math.floor(absMin/60));
    var mm = pad2(absMin%60);
    return 'UTC' + sign + hh + ':' + mm;
  }
  function formatLocal(d){
    return d.getFullYear() + '-' + pad2(d.getMonth()+1) + '-' + pad2(d.getDate()) +
           ' ' + pad2(d.getHours()) + ':' + pad2(d.getMinutes()) + ':' + pad2(d.getSeconds()) +
           ' (' + tzOffsetString(d) + ')';
  }

  var now = new Date();
  var elTime = document.getElementById('err-time');
  var elUrl  = document.getElementById('err-url');
  if (elTime){
    elTime.textContent = formatLocal(now);
    if (now.toISOString){ elTime.setAttribute('datetime', now.toISOString()); }
  }
  if (elUrl){
    var u = (window.location && window.location.href) ? window.location.href : '';
    elUrl.textContent = u; elUrl.setAttribute('title', u);
  }

  // 复制诊断信息(含回退)
  var btn = document.getElementById('copyDiag');
  var live = document.getElementById('live-status');
  function setLive(msg){ if(live){ live.textContent = msg; } }
  function copyText(txt, onDone){
    if (navigator && navigator.clipboard && navigator.clipboard.writeText){
      navigator.clipboard.writeText(txt).then(function(){ onDone(true); }, function(){ fallback(); });
    } else { fallback(); }
    function fallback(){
      var ta = document.createElement('textarea');
      ta.value = txt; ta.style.position = 'fixed'; ta.style.opacity = '0';
      document.body.appendChild(ta); ta.focus(); ta.select();
      try{ onDone( !!document.execCommand('copy') ); }catch(e){ onDone(false); }
      document.body.removeChild(ta);
    }
  }
  function buildDiagText(){
    var t = elTime && elTime.textContent ? elTime.textContent : '';
    var u = elUrl && elUrl.textContent ? elUrl.textContent : '';
    return '出错时间: ' + t + '\n出错URL: ' + u;
  }
  if (btn){
    btn.onclick = function(){
      copyText(buildDiagText(), function(ok){
        setLive(ok ? '已复制诊断信息' : '复制失败,请手动复制');
        var old = btn.textContent || '复制';
        btn.textContent = ok ? '已复制' : '复制失败';
        setTimeout(function(){ btn.textContent = '复制'; }, 1500);
      });
    };
  }
})();
</script>

</body>
</html>
]]>
0 https://luolt.cn/archives/3122.html#comments https://luolt.cn/feed/
2025年的最后一天肝出了自己想要的武器 https://luolt.cn/archives/3121.html https://luolt.cn/archives/3121.html Mon, 16 Feb 2026 23:17:46 +0800 寻梦xunm 2025年最后的一天,也就是今天。博主也是成功白嫖到了理想的武器和角色,虽然自己现在也不怎么玩了,但是免费又能白嫖的自己不能没有是吧。
[pZOV9gS.md.jpg]
[pZOVp38.md.jpg]

]]>
4 https://luolt.cn/archives/3121.html#comments https://luolt.cn/feed/
“可修”网址工具导航程序(付费开源) https://luolt.cn/archives/3120.html https://luolt.cn/archives/3120.html Wed, 04 Feb 2026 10:55:00 +0800 寻梦xunm 这款程序可以进行工具展示和网址导航等等功能,已经集成二十款内部工具,二次开发也非常的简单,学习成本不高。
开发环境基于PHP8.2+MYSQL5.6即已上(PDO驱动),已经集成傻瓜式安装方式,只要按照提示步骤安装基本都能够成功。

预计售价:999.99,为了和各位网友交一个朋友,我自己砍一个骨折价,只需要“9.9”。是不是非常的劲爆。

好了下来来一个自己上线做书签用的演示地址:http://m.kxiu.cn

下面是程序的一些截图

火狐截图_2026-02-04T02-46-20.188Z.png
火狐截图_2026-02-04T02-46-03.055Z.png
火狐截图_2026-02-04T02-45-52.044Z.png
火狐截图_2026-02-04T02-45-43.345Z.png
火狐截图_2026-02-04T02-45-37.934Z.png
火狐截图_2026-02-04T02-45-28.035Z.png
火狐截图_2026-02-04T02-45-17.204Z.png
火狐截图_2026-02-04T02-45-11.434Z.png
火狐截图_2026-02-04T02-45-02.662Z.png
火狐截图_2026-02-04T02-44-48.757Z.png
火狐截图_2026-02-04T02-44-30.019Z.png
火狐截图_2026-02-04T02-44-07.909Z.png
火狐截图_2026-02-04T02-43-54.143Z.png
火狐截图_2026-02-04T02-43-24.207Z.png

]]>
2 https://luolt.cn/archives/3120.html#comments https://luolt.cn/feed/
(AI出品,必须能跑)自动填充用户评论信息 https://luolt.cn/archives/3119.html https://luolt.cn/archives/3119.html Wed, 04 Feb 2026 10:42:00 +0800 寻梦xunm 用户首次评论提交后,保存其名称、邮箱等信息,下次再进入评论页面(或刷新后),自动将保存的信息填充到对应的输入框中。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>自动填充用户评论信息</title>
    <style>
        /* 简单样式美化,不影响核心功能 */
        .comment-container {
            width: 600px;
            margin: 50px auto;
            padding: 20px;
            border: 1px solid #eee;
            border-radius: 8px;
        }
        .comment-item {
            margin-bottom: 15px;
        }
        label {
            display: block;
            margin-bottom: 5px;
            font-weight: 500;
        }
        input, textarea {
            width: 100%;
            padding: 8px 10px;
            border: 1px solid #ccc;
            border-radius: 4px;
            box-sizing: border-box;
            font-size: 14px;
        }
        textarea {
            height: 120px;
            resize: vertical;
        }
        button {
            padding: 10px 20px;
            background-color: #42b983;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
        }
        button:hover {
            background-color: #359469;
        }
    </style>
</head>
<body>
    <div class="comment-container">
        <h3>评论区</h3>
        <div class="comment-item">
            <label for="username">姓名:</label>
            <input type="text" id="username" placeholder="请输入你的姓名">
        </div>
        <div class="comment-item">
            <label for="useremail">邮箱:</label>
            <input type="email" id="useremail" placeholder="请输入你的邮箱">
        </div>
        <div class="comment-item">
            <label for="comment-content">评论内容:</label>
            <textarea id="comment-content" placeholder="请输入你的评论内容"></textarea>
        </div>
        <button id="submit-comment">提交评论</button>
    </div>

    <script>
        // 1. 获取页面中的DOM元素
        const usernameInput = document.getElementById('username');
        const useremailInput = document.getElementById('useremail');
        const commentContentInput = document.getElementById('comment-content');
        const submitBtn = document.getElementById('submit-comment');

        // 定义localStorage的存储键名(方便后续维护)
        const COMMENT_USER_INFO_KEY = 'comment_user_info';

        // 2. 页面加载完成后,自动填充已保存的用户信息
        function fillUserInfo() {
            // 从localStorage中获取保存的用户信息(localStorage存储的是字符串,需要用JSON.parse转换为对象)
            const savedUserInfo = localStorage.getItem(COMMENT_USER_INFO_KEY);
            if (savedUserInfo) {
                const userInfo = JSON.parse(savedUserInfo);
                // 将保存的信息填充到对应的输入框中
                usernameInput.value = userInfo.username || '';
                useremailInput.value = userInfo.useremail || '';
            }
        }

        // 3. 提交评论时,保存用户的姓名和邮箱信息
        function saveUserInfo() {
            // 获取输入框中的值(去除前后空格)
            const username = usernameInput.value.trim();
            const useremail = useremailInput.value.trim();
            const commentContent = commentContentInput.value.trim();

            // 简单表单验证
            if (!username) {
                alert('请输入你的姓名!');
                return false;
            }
            if (!useremail) {
                alert('请输入你的邮箱!');
                return false;
            }
            if (!commentContent) {
                alert('请输入评论内容!');
                return false;
            }

            // 构造用户信息对象
            const userInfo = {
                username: username,
                useremail: useremail
            };

            // 将用户信息保存到localStorage中(localStorage只能存储字符串,需要用JSON.stringify转换)
            localStorage.setItem(COMMENT_USER_INFO_KEY, JSON.stringify(userInfo));

            // 提示评论提交成功(实际项目中这里会对接后端接口,提交评论数据)
            alert('评论提交成功!');

            // 清空评论内容输入框(姓名和邮箱保留,方便用户再次评论修改)
            commentContentInput.value = '';

            return true;
        }

        // 4. 绑定事件
        // 页面加载完成后执行填充逻辑
        window.onload = fillUserInfo;

        // 提交按钮点击事件
        submitBtn.addEventListener('click', saveUserInfo);
    </script>
</body>
</html>
代码关键部分解释

localStorage 的使用
localStorage.setItem(key, value):用于存储数据,key 是存储的键名,value 只能是字符串类型,因此我们用 JSON.stringify() 将用户信息对象转换为字符串。
localStorage.getItem(key):用于获取指定键名存储的数据,返回值是字符串,因此需要用 JSON.parse() 转换回 JavaScript 对象,方便提取数据。
存储键名定义为常量 COMMENT_USER_INFO_KEY,后续如果需要修改键名,只需修改这一个常量即可,提升可维护性。

页面加载自动填充

利用 window.onload 事件,确保页面所有 DOM 元素加载完成后再执行填充逻辑,避免获取不到输入框元素。
先判断是否存在已保存的信息,存在才进行填充,避免出现 undefined 等异常。

表单验证与数据保存

提交时先去除输入框值的前后空格(trim()),避免用户输入无效的空白内容。
只保存姓名和邮箱,评论内容不保存,符合用户使用习惯(用户每次评论的内容大概率不同)。
提交成功后清空评论内容输入框,保留姓名和邮箱,方便用户再次提交新评论。

]]>
1 https://luolt.cn/archives/3119.html#comments https://luolt.cn/feed/
更新维护Typecho “简单作品展示“模板 https://luolt.cn/archives/3118.html https://luolt.cn/archives/3118.html Thu, 29 Jan 2026 16:27:38 +0800 寻梦xunm 这不是Typecho正式发布了1.3.0版本后,有些代码会有所出入,今天就把以前的旧版本主题模板升级维护了一下。
关于这款主题不只是简单的代码升级维护,也对以前的UI样式问题做出了一些调整和修复。
至于下载地址,等后期视频制作完成后以前上传分享。

]]>
0 https://luolt.cn/archives/3118.html#comments https://luolt.cn/feed/
Typecho1.3.0关于如何获取分类的MID https://luolt.cn/archives/3117.html https://luolt.cn/archives/3117.html Thu, 29 Jan 2026 14:46:00 +0800 寻梦xunm cover_1769669488763.jpg
在 Typecho 1.1 中可以通过这样的方式获取

<?php 
$this->_pageRow['mid'];
?>

如果是 Typecho 1.2,可以通过这样的方式获取

<?php 
$this->pageRow['mid'];
?>

如果是 Typecho 1.3,可以通过这样的方式获取

<?php 
$this->pageRow->mid;
?>
]]>
0 https://luolt.cn/archives/3117.html#comments https://luolt.cn/feed/
使用php对emoji表情进行编码和解码源码教程 https://luolt.cn/archives/3116.html https://luolt.cn/archives/3116.html Thu, 08 Jan 2026 09:37:00 +0800 寻梦xunm 本篇文章主要用于不修改数据库编码,可以支持emoji 表情显示支持。

请输入图片描述
emoji_encode():将字符串中的 emoji 表情(4 字节 UTF-8 字符)编码为 &#十进制数字; 的实体格式,普通字符保持不变。
emoji_decode():将上述编码格式的 emoji 实体还原为原始的 emoji 表情字符。
整体作用是解决 emoji 表情在传输 / 存储中可能出现的乱码问题,通过实体编码的方式兼容不同系统对 emoji 的处理。

{hide}

function emoji_encode($str) {
    // 先将 &amp; 还原为 &,避免干扰 emoji 编码格式
    $str = str_replace('&amp;', '&', $str);
    
    $result = [];
    $len = mb_strlen($str, 'UTF-8');
    for ($i = 0; $i < $len; $i++) {
        $char = mb_substr($str, $i, 1, 'UTF-8');
        // 4字节UTF-8字符的首字节范围是 0xF0-0xF7
        if (ord($char[0]) >= 0xF0 && ord($char[0]) <= 0xF7) {
            $ucs4 = iconv('UTF-8', 'UCS-4BE', $char);
            $dec = hexdec(bin2hex($ucs4));
            $result[] = "&#{$dec};";
        } else {
            $result[] = $char;
        }
    }
    $encoded = implode('', $result);
    return $encoded;
}

function emoji_decode($str) {
    // 第一步:先将 &amp;# 还原为 &#,保证emoji编码格式正确
    $str = str_replace('&amp;#', '&#', $str);
    
    // 正则匹配 &#数字; 格式的编码
    $pattern = '/&#(\d+);/u';
    $result = preg_replace_callback($pattern, function($matches) {
        // 关键修复:将字符串数字转为整数类型
        $dec = (int)$matches[1]; // 强制类型转换为int
        if ($dec < 0x1F000) {
            return $matches[0];
        }
        
        // 将十进制数字转为UCS-4BE格式的二进制
        $hex = dechex($dec); // 现在参数是int类型,不会报错
        $hex = str_pad($hex, 8, '0', STR_PAD_LEFT); // 补全8位16进制
        $ucs4 = pack('H*', $hex);
        
        // 将UCS-4BE转换为UTF-8字符
        $char = iconv('UCS-4BE', 'UTF-8', $ucs4);
        
        return $char;
    }, $str);
    
    return $result;
}

{/hide}

使用方法
// 测试示例
$original = "你dddfakjdsfudsadsafsd(由于博客不支持emoji 表情这里就不添加了,要测试自行添加emoji查看效果)";
$encoded = emoji_encode($original);
$decoded = emoji_decode($encoded);

echo "原始字符串: " . $original . "\n";
echo "编码后: " . $encoded . "\n";
echo "解码后: " . $decoded . "\n";
]]>
1 https://luolt.cn/archives/3116.html#comments https://luolt.cn/feed/
php自动获取favicon源代码(支持ico,png,svg等格式)教程 https://luolt.cn/archives/3115.html https://luolt.cn/archives/3115.html Wed, 07 Jan 2026 10:01:06 +0800 寻梦xunm 功能介绍

该工具是一款零依赖、极致性能的 PHP 版网站 favicon(网站图标)在线获取工具,专为追求高性能、低资源消耗的场景设计,核心功能与特性如下:
1. 核心功能
多路径智能获取:优先尝试网站根目录下最常用的 favicon.ico 和 favicon.png 通用路径,获取失败后自动解析网站 HTML 源码,提取 标签中的精准图标地址,确保获取成功率。
轻量化输出:直接返回 favicon 的二进制内容,无任何冗余封装,可按需输出为图片响应、写入文件或进行其他处理。
URL 自动兼容:支持传入带 / 不带协议的 URL(如 www.baidu.com 或 https://www.baidu.com),自动补全 HTTPS 协议并解析域名,无需额外预处理。
2. 核心技术特性
零第三方依赖:仅依赖 PHP 内置 curl 扩展(PHP 标配组件),无任何外部库、框架或配置文件依赖,可直接集成使用。

极致性能与效率:

短路逻辑:找到有效 favicon 立即终止流程,无冗余执行;
极致超时控制:3 秒总超时 + 1 秒连接超时,避免无效等待;
精简网络传输:不获取响应头、固定 HTTP 版本,减少网络损耗。

零冗余资源占用:

即时内存释放:每步操作后销毁无用变量,无冗余内存占用;
无缓存 / 无磁盘 IO:不写入缓存文件,仅在运行时加载必要数据;
按需加载:仅通用路径获取失败后才执行 HTML 解析,懒执行无预加载损耗。
低耦合设计:核心功能拆分为两个独立纯函数,无全局变量、无耦合依赖,可单独调用、按需集成,适配任意业务场景。
3. 适用场景
高性能网址导航、书签类系统;
对资源消耗、响应速度有严苛要求的后端服务;
轻量级 PHP 项目(如微服务、接口)的 favicon 获取需求;
需低耦合集成 favicon 获取功能的现有系统。

精简项目代码
<?php
/**
 * 极简版Favicon获取函数(零依赖、极致性能、零冗余)
 * @param string $url 目标网站URL
 * @return string|false 成功返回favicon内容(二进制),失败返回false
 */
function get_favicon(string $url): string|false {
    // 步骤1:标准化URL(仅保留核心逻辑,无冗余判断)
    $url = trim($url);
    if (!preg_match('/^https?:\/\//i', $url)) {
        $url = 'https://' . $url; // 优先HTTPS,无冗余的HTTP回退(极简策略)
    }
    
    $parsed = parse_url($url);
    if (!isset($parsed['scheme'], $parsed['host'])) {
        return false;
    }
    $scheme = $parsed['scheme'];
    $host = $parsed['host'];
    
    // 步骤2:尝试通用路径(按需加载,找到即返回,零冗余循环)
    $paths = ['/favicon.ico', '/favicon.png']; // 仅保留最常用的2种格式,极致效率
    foreach ($paths as $path) {
        $favicon_url = "{$scheme}://{$host}{$path}";
        $content = fetch_url_content($favicon_url);
        if ($content !== false) {
            return $content; // 短路返回,无任何冗余操作
        }
        unset($content); // 即时释放内存,零冗余占用
    }
    
    // 步骤3:解析HTML(仅必要时执行,按需加载)
    $html_url = "{$scheme}://{$host}";
    $html = fetch_url_content($html_url);
    if ($html === false) {
        unset($html, $scheme, $host, $html_url); // 释放所有变量
        return false;
    }
    
    // 极简正则提取icon(无冗余分组,仅保留核心匹配)
    if (preg_match('/<link[^>]*rel=["\']?icon["\']?[^>]*href=["\']([^"\']+)["\']/i', $html, $matches)) {
        $icon_path = $matches[1];
        unset($html, $matches); // 即时释放大变量
        
        // 路径补全(极简逻辑,无冗余判断)
        if (str_starts_with($icon_path, 'http')) {
            $icon_url = $icon_path;
        } elseif (str_starts_with($icon_path, '/')) {
            $icon_url = "{$scheme}://{$host}{$icon_path}";
        } else {
            $icon_url = "{$scheme}://{$host}/{$icon_path}";
        }
        
        $content = fetch_url_content($icon_url);
        if ($content !== false) {
            unset($scheme, $host, $icon_path, $icon_url); // 释放变量
            return $content;
        }
        unset($content);
    }
    
    // 最终清理,零冗余内存占用
    unset($scheme, $host, $html, $html_url);
    return false;
}

/**
 * 极简URL内容获取函数(零冗余、极致性能)
 * @param string $url 目标URL
 * @return string|false 二进制内容或false
 */
function fetch_url_content(string $url): string|false {
    $ch = curl_init($url);
    if (!$ch) {
        return false;
    }
    
    // 仅设置必要选项,无任何冗余配置
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_TIMEOUT => 3, // 极致超时(减少等待损耗)
        CURLOPT_CONNECTTIMEOUT => 1,
        CURLOPT_USERAGENT => 'Mozilla/5.0',
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => false,
        CURLOPT_NOBODY => false,
        CURLOPT_HEADER => false, // 不获取响应头,减少数据传输
        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, // 固定HTTP版本,减少协商损耗
    ]);
    
    $content = curl_exec($ch);
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch); // 即时关闭句柄,释放资源
    unset($ch); // 释放句柄变量
    
    // 仅保留核心判断,无冗余逻辑
    if ($http_code !== 200 || empty($content)) {
        unset($content, $http_code);
        return false;
    }
    
    return $content;
}

// ===================== 极简使用示例(按需调用,零冗余) =====================
// 调用示例(仅在需要时执行,按需加载)
// $favicon = get_favicon('https://www.baidu.com');
// if ($favicon) {
//     header('Content-Type: image/x-icon');
//     echo $favicon;
//     unset($favicon); // 即时释放内存
// }
?>
]]>
1 https://luolt.cn/archives/3115.html#comments https://luolt.cn/feed/
php高性能(反正AI是这么说的)PSR-4规范自动加载器 https://luolt.cn/archives/3114.html https://luolt.cn/archives/3114.html Wed, 31 Dec 2025 22:01:29 +0800 寻梦xunm Core\Loader 类 详细开发文档

一、类核心概述

1. 类定位

Core\Loader 是一款高性能PSR-4规范自动加载器,核心用于PHP项目中类、接口、特质的自动加载,无需手动引入require/include,遵循PHP FIG制定的PSR-4自动加载标准,适配各类PHP项目架构。

2. 核心优势

  1. 标准兼容:严格遵循PSR-4规范,适配主流PHP项目的命名空间与文件目录映射逻辑;
  2. 性能优先:类路径缓存机制+路径查找逻辑优化,大幅降低文件IO开销,低内存占用;
  3. 安全可靠:内置类名合法性校验、防目录穿越、缓存加密校验、目录权限校验,杜绝安全风险;
  4. 兼容性强:兼容无独占锁文件系统,采用临时文件+原子替换方案保证缓存写入完整性,适配各类部署环境;
  5. 灵活易用:支持命名空间前缀批量映射、单个类手动映射,支持缓存手动清空、加密盐值自定义。

二、核心特性详情

  1. 自动路径校准:自动统一系统目录分隔符,适配Windows/Linux/Unix等不同操作系统;
  2. 缓存机制:类名-文件路径映射缓存,加载一次永久缓存,后续直接读取缓存加载,提升加载效率;
  3. 缓存安全:缓存数据序列化后叠加HMAC-MD5加密校验,防止缓存被恶意篡改;
  4. 合法目录池:已验证的合法目录缓存,避免重复校验,提升路径合法性校验效率;
  5. 高优先级注册:自动加载回调以高优先级注册,优先触发本加载器逻辑;
  6. 冗余去重:命名空间前缀映射目录自动去重,避免重复查找,减少IO冗余;
  7. 加载校验:文件加载后自动校验类/接口/特质是否加载成功,保证加载有效性。

三、类结构与核心成员

1. 私有静态成员(内部维护,无需外部操作)

成员变量类型核心作用
$prefixMaparray存储命名空间前缀与对应目录的映射关系,格式为[前缀 => [路径1, 路径2...]]
$classCachearray存储类名与文件路径的缓存映射,格式为[类名 => 文件路径]
$cacheFilestring缓存文件的完整存储路径,默认生成在指定缓存目录下的class_map.cache
$cacheSaltstring缓存加密的盐值,用于HMAC-MD5加密校验,可通过公共方法自定义
$validDirsarray已验证合法的目录池,缓存合法目录避免重复校验,格式为[合法目录 => true]

2. 公共静态方法(外部核心调用接口)

方法名入参类型返回值核心作用
initstring(可选,缓存目录)void初始化自动加载器,校验缓存目录、加载历史缓存、注册自动加载回调
registerPrefixstring(命名空间前缀)、string(对应目录)void注册命名空间前缀与目录的映射关系,支持单个前缀对应多个目录
registerClassstring(完整类名)、string(类文件路径)void手动注册单个类的路径映射,适用于非标准PSR-4规范的类文件
clearCachevoid清空内存中的类缓存,同时删除本地缓存文件及残留临时文件
setCacheSaltstring(自定义盐值)void自定义缓存加密盐值,提升缓存安全性,建议项目启动时设置唯一盐值

3. 私有静态方法(内部逻辑支撑,外部不可访问)

核心支撑方法包括类路径解析、文件安全加载、缓存加解密、路径/类名合法性校验、安全路径获取等,保障自动加载全流程的规范性、安全性与高效性,无需外部干预。

四、安装与初始化

1. 安装部署

  1. 将该类文件按项目目录规范存放,建议路径为项目根目录/Core/Loader.php
  2. 确保项目支持命名空间自动识别,PHP版本要求7.4及以上(兼容PHP8.x全版本),需开启serializehash相关扩展(默认开启)。

2. 基础初始化

项目入口文件(如index.php)中执行初始化,是使用该自动加载器的前置操作,核心代码如下:

<?php
// 项目入口文件
// 引入Loader类文件
require_once __DIR__ . '/Core/Loader.php';

// 初始化自动加载器(默认缓存目录:项目根目录/runtime/cache)
\Core\Loader::init();

// 可选:自定义缓存目录(推荐绝对路径,避免路径歧义)
// \Core\Loader::init(__DIR__ . '/storage/cache');

// 可选:自定义缓存加密盐值(提升安全性,建议项目唯一)
// \Core\Loader::setCacheSalt('Your_Project_Unique_Salt_2025');

五、核心使用方法

1. 核心用法:注册命名空间前缀映射(PSR-4标准用法)

这是最常用的用法,将项目的命名空间前缀与实际目录绑定,自动加载器会按PSR-4规范查找类文件。

核心规则

  • 命名空间前缀末尾会自动补全\,无需手动添加;
  • 目录路径会自动统一分隔符、补全末尾分隔符,支持相对路径/绝对路径,优先识别绝对路径;
  • 单个命名空间前缀可绑定多个目录,自动加载器会按顺序查找,找到即加载。

    代码示例

    <?php
    // 入口文件初始化后执行注册
    // 1. 注册App命名空间(核心业务代码),对应项目根目录下的app目录
    \Core\Loader::registerPrefix('App', __DIR__ . '/app');
    
    // 2. 注册Lib命名空间(第三方类库),对应lib目录
    \Core\Loader::registerPrefix('Lib', __DIR__ . '/lib');
    
    // 3. 单个前缀绑定多个目录(示例:Plugin命名空间对应两个插件目录)
    \Core\Loader::registerPrefix('Plugin', __DIR__ . '/plugins/admin');
    \Core\Loader::registerPrefix('Plugin', __DIR__ . '/plugins/front');

    对应文件目录规范(PSR-4)

    例如注册App\Controller前缀对应/app/Controller目录,类App\Controller\IndexController 对应的文件路径必须为 /app/Controller/IndexController.php,命名空间与目录严格对应,类名与文件名完全一致(大小写敏感,需遵循系统文件大小写规则)。

2. 补充用法:手动注册单个类映射

适用于部分不遵循PSR-4规范的类文件,手动指定类名与文件路径,跳过自动解析逻辑,直接缓存映射关系。

<?php
// 手动注册单个类:类名 + 类文件绝对路径
\Core\Loader::registerClass('NonPsr4Class', __DIR__ . '/custom/NonPsr4ClassFile.php');

// 注册后直接使用类即可自动加载
$obj = new NonPsr4Class();

3. 辅助用法:清空缓存

当项目目录结构调整、类文件路径变更时,需清空缓存,否则会加载旧路径导致报错,执行后内存缓存与本地缓存文件会同步清空。

<?php
// 清空自动加载器缓存
\Core\Loader::clearCache();

六、高级配置与优化建议

1. 缓存优化

  1. 缓存目录建议使用绝对路径,避免因项目执行入口不同导致路径歧义;
  2. 生产环境建议将缓存目录配置为非Web可访问目录,避免缓存文件被外部访问;
  3. 项目发布、目录调整后,建议在部署脚本中自动执行clearCache(),避免缓存失效问题。

2. 安全性优化

  1. 务必自定义缓存加密盐值(setCacheSalt),避免使用默认盐值,防止缓存被恶意破解篡改;
  2. 生产环境保持缓存目录权限为0755、缓存文件权限为0644,该类会自动校验并修复权限,无需手动调整;
  3. 避免注册非项目可控目录,防止目录穿越风险,类内部已做严格路径校验,无需额外处理。

3. 性能优化

  1. 命名空间前缀映射尽量精准,避免冗余前缀,减少路径查找次数;
  2. 高频使用的类可通过registerClass手动注册,跳过解析逻辑,提升加载速度;
  3. 避免频繁清空缓存,缓存机制是提升性能的核心,频繁清空会增加IO开销。

七、异常处理与常见问题

1. 常见问题排查

  1. 类加载失败:① 检查命名空间与目录是否对应、类名与文件名是否一致;② 检查命名空间前缀是否正确注册;③ 执行clearCache()清空旧缓存;④ 检查类文件是否存在且可读(权限0644及以上);
  2. 缓存写入失败:① 检查缓存目录是否有写入权限;② 检查磁盘是否已满;③ 确认缓存目录已创建,类会自动创建目录,若创建失败需手动创建;
  3. 缓存解密失败:① 检查是否修改了缓存盐值,修改盐值后需清空旧缓存;② 确认缓存文件未被手动篡改,若篡改执行clearCache()即可。

2. 兼容注意事项

  1. 兼容无独占锁文件系统(如部分分布式存储、轻量服务器文件系统),无需额外配置;
  2. Windows系统需注意文件大小写,PHP类名大小写敏感,需保证类名与文件名大小写完全一致;
  3. 兼容Composer自动加载器,可共存使用,本类注册优先级更高,会优先触发加载逻辑。

八、适用场景

  1. 自定义PHP框架开发,作为核心自动加载组件;
  2. 中小型PHP项目,无需依赖Composer,轻量化实现类自动加载;
  3. 已有项目优化,替换低效自动加载逻辑,提升项目性能;
  4. 需严格遵循PSR-4规范的项目,保障代码架构规范性。

九、代码分享

{hide}

<?php
/**
 * PSR-4 高性能自动加载器(最终修复版:兼容无独占锁文件系统)
 * 核心特性:严格遵循PSR-4规范、自动路径校准、防目录穿越、加密缓存、低内存占用、锁兼容
 */
namespace Core;

class Loader
{
    /**
     * 命名空间前缀与目录映射表 [前缀 => [路径1, 路径2...]]
     * @var array
     */
    private static array $prefixMap = [];

    /**
     * 类名-文件路径缓存 [类名 => 文件路径]
     * @var array
     */
    private static array $classCache = [];

    /**
     * 缓存文件路径
     * @var string
     */
    private static string $cacheFile = '';

    /**
     * 缓存加密盐值
     * @var string
     */
    private static string $cacheSalt = 'PSR4_LOADER_CACHE_SALT_2025';

    /**
     * 已验证合法目录池
     * @var array
     */
    private static array $validDirs = [];

    /**
     * 初始化自动加载器
     * @param string $cacheDir 缓存目录
     * @return void
     */
    public static function init(string $cacheDir = './runtime/cache')
    {
        // 初始化缓存文件路径
        self::$cacheFile = rtrim($cacheDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'class_map.cache';
        // 校验缓存目录权限
        self::checkDirSecurity($cacheDir);
        // 加载历史缓存
        self::loadCache();
        // 注册自动加载回调(高优先级)
        spl_autoload_register([self::class, 'autoload'], true, true);
    }

    /**
     * 核心自动加载逻辑
     * @param string $className 完整类名
     * @return bool
     */
    public static function autoload(string $className): bool
    {
        // 缓存命中:直接加载
        if (isset(self::$classCache[$className])) {
            return self::requireFile(self::$classCache[$className], $className);
        }

        // 类名合法性校验
        if (!self::checkClassNameValid($className)) {
            return false;
        }

        // 解析类文件路径
        $filePath = self::resolveFilePath($className);
        if ($filePath === false) {
            return false;
        }

        // 缓存映射关系
        self::$classCache[$className] = $filePath;
        self::saveCache();

        return self::requireFile($filePath, $className);
    }

    /**
     * 注册命名空间前缀与目录映射
     * @param string $prefix 命名空间前缀(如 App\Demo)
     * @param string $dir 对应目录路径
     * @return void
     */
    public static function registerPrefix(string $prefix, string $dir): void
    {
        // 强制补全前缀末尾反斜杠(PSR-4规范要求)
        $prefix = rtrim($prefix, '\\') . '\\';
        // 统一目录分隔符并补全末尾分隔符
        $dir = rtrim(str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $dir), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
        
        // 获取安全绝对路径
        $realDir = self::getSafeRealPath($dir);
        if ($realDir === false) {
            return;
        }

        // 去重存储,避免冗余
        if (!isset(self::$prefixMap[$prefix])) {
            self::$prefixMap[$prefix] = [];
        }
        if (!in_array($realDir, self::$prefixMap[$prefix])) {
            self::$prefixMap[$prefix][] = $realDir;
        }

        // 加入合法目录池
        self::$validDirs[$realDir] = true;
    }

    /**
     * 手动注册单个类映射
     * @param string $className 完整类名
     * @param string $filePath 类文件路径
     * @return void
     */
    public static function registerClass(string $className, string $filePath): void
    {
        if (!self::checkClassNameValid($className)) {
            return;
        }

        $safePath = self::getSafeRealPath($filePath);
        if ($safePath === false || !is_file($safePath)) {
            return;
        }

        // 记录类所在目录
        $fileDir = dirname($safePath) . DIRECTORY_SEPARATOR;
        self::$validDirs[$fileDir] = true;

        // 缓存映射
        self::$classCache[$className] = $safePath;
        self::saveCache();
    }

    /**
     * 解析类名对应的文件路径(核心PSR-4逻辑)
     * @param string $className 完整类名
     * @return string|false
     */
    private static function resolveFilePath(string $className): string|false
    {
        $className = ltrim($className, '\\');

        // 遍历前缀映射表
        foreach (self::$prefixMap as $prefix => $dirs) {
            if (!str_starts_with($className, $prefix)) {
                continue;
            }

            // 截取相对类名并转换为文件路径
            $relativeClass = substr($className, strlen($prefix));
            $file = str_replace('\\', DIRECTORY_SEPARATOR, $relativeClass) . '.php';

            // 遍历目录查找文件
            foreach ($dirs as $dir) {
                $fullPath = $dir . $file;
                // 先检查文件是否存在可读,再校验目录合法性(顺序优化)
                if (file_exists($fullPath) && is_readable($fullPath)) {
                    // 目录合法性兜底校验
                    if (isset(self::$validDirs[$dir]) || str_starts_with(realpath($fullPath), $dir)) {
                        return $fullPath;
                    }
                }
            }
        }

        return false;
    }

    /**
     * 安全加载文件
     * @param string $filePath 文件路径
     * @param string $className 类名
     * @return bool
     */
    private static function requireFile(string $filePath, string $className = ''): bool
    {
        if (!is_file($filePath) || !is_readable($filePath)) {
            return false;
        }

        require_once $filePath;

        // 校验类是否加载成功
        return empty($className) ? true : (class_exists($className, false) || interface_exists($className, false) || trait_exists($className, false));
    }

    /**
     * 加载缓存
     * @return void
     */
    private static function loadCache(): void
    {
        if (empty(self::$cacheFile) || !file_exists(self::$cacheFile) || !is_readable(self::$cacheFile)) {
            return;
        }

        $cacheData = file_get_contents(self::$cacheFile);
        $decodedData = self::decryptCache($cacheData);

        if (is_array($decodedData)) {
            self::$classCache = array_merge(self::$classCache, $decodedData);
        }
    }

    /**
     * 保存缓存(兼容无独占锁文件系统:临时文件+原子替换)
     * @return void
     */
    private static function saveCache(): void
    {
        if (empty(self::$cacheFile)) {
            return;
        }

        $cacheDir = dirname(self::$cacheFile);
        if (!is_dir($cacheDir)) {
            mkdir($cacheDir, 0755, true);
            chmod($cacheDir, 0755);
        }

        $encryptData = self::encryptCache(self::$classCache);
        // 步骤1:写入临时文件,避免直接写入损坏原缓存
        $tmpFile = $cacheDir . DIRECTORY_SEPARATOR . 'class_map.cache.tmp';
        file_put_contents($tmpFile, $encryptData);
        // 步骤2:原子重命名替换目标文件,保证写入完整性
        rename($tmpFile, self::$cacheFile);
        chmod(self::$cacheFile, 0644);
    }

    /**
     * 清空缓存
     * @return void
     */
    public static function clearCache(): void
    {
        self::$classCache = [];
        if (!empty(self::$cacheFile) && file_exists(self::$cacheFile)) {
            unlink(self::$cacheFile);
        }
        // 清理可能残留的临时文件
        $tmpFile = dirname(self::$cacheFile) . DIRECTORY_SEPARATOR . 'class_map.cache.tmp';
        if (file_exists($tmpFile)) {
            unlink($tmpFile);
        }
    }

    /**
     * 设置缓存加密盐值
     * @param string $salt 盐值
     * @return void
     */
    public static function setCacheSalt(string $salt): void
    {
        self::$cacheSalt = $salt;
    }

    /**
     * 缓存加密
     * @param array $data 缓存数据
     * @return string
     */
    private static function encryptCache(array $data): string
    {
        $serializeData = serialize($data);
        $hmac = hash_hmac('md5', $serializeData, self::$cacheSalt, true);
        return base64_encode($hmac . $serializeData);
    }

    /**
     * 缓存解密
     * @param string $data 加密缓存数据
     * @return array|false
     */
    private static function decryptCache(string $data): array|false
    {
        $decodeData = base64_decode($data);
        if ($decodeData === false) {
            return false;
        }

        $hmacLen = 16; // md5哈希长度
        if (strlen($decodeData) < $hmacLen) {
            return false;
        }

        $hmac = substr($decodeData, 0, $hmacLen);
        $serializeData = substr($decodeData, $hmacLen);

        if (!hash_equals($hmac, hash_hmac('md5', $serializeData, self::$cacheSalt, true))) {
            return false;
        }

        try {
            $result = unserialize($serializeData, ['allowed_classes' => false]);
            return is_array($result) ? $result : false;
        } catch (\Throwable $e) {
            return false;
        }
    }

    /**
     * 类名合法性校验
     * @param string $className 类名
     * @return bool
     */
    private static function checkClassNameValid(string $className): bool
    {
        return preg_match('/^[a-zA-Z0-9_\\\\]+$/', $className) === 1;
    }

    /**
     * 路径安全校验(防目录穿越)
     * @param string $filePath 文件路径
     * @return bool
     */
    private static function checkPathValid(string $filePath): bool
    {
        $realPath = realpath($filePath);
        if ($realPath === false) {
            return false;
        }

        foreach (self::$prefixMap as $dirs) {
            foreach ($dirs as $dir) {
                if (str_starts_with($realPath, $dir)) {
                    return true;
                }
            }
        }

        $fileDir = dirname($realPath) . DIRECTORY_SEPARATOR;
        return isset(self::$validDirs[$fileDir]);
    }

    /**
     * 获取安全绝对路径
     * @param string $path 原始路径
     * @return string|false
     */
    private static function getSafeRealPath(string $path): string|false
    {
        $path = rtrim(str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path), DIRECTORY_SEPARATOR);
        $realPath = realpath($path);
        return $realPath === false ? false : $realPath . DIRECTORY_SEPARATOR;
    }

    /**
     * 目录权限校验
     * @param string $dir 目录路径
     * @return void
     */
    private static function checkDirSecurity(string $dir): void
    {
        if (!is_dir($dir)) {
            mkdir($dir, 0755, true);
        }

        $perm = substr(sprintf('%o', fileperms($dir)), -4);
        if ($perm !== '0755') {
            chmod($dir, 0755);
        }
    }
}

{/hide}

内容由 AI 生成

]]>
0 https://luolt.cn/archives/3114.html#comments https://luolt.cn/feed/
今天给luolt.cn和kxiu.cn两个域名续费 https://luolt.cn/archives/3113.html https://luolt.cn/archives/3113.html Mon, 29 Dec 2025 23:19:00 +0800 寻梦xunm 今天突然想起来有个域名快要过期了,趁今天想起来了就顺便续费一下。

登录某平台发现续费居然需要40大洋,还是有点小贵小贵,于是就重新找了一个续费38大洋的知名平台进行域名转出。

第一次使用域名转出功能还以为非常的麻烦,一时间都不知道要怎么操作,不过经过相关文档教程查看后,才发现非常的简单,就几步操作而已。

现在域名已经成功转移到了续费比较低的平台上,顺便也把另一个域名也续费了一遍。

]]>
2 https://luolt.cn/archives/3113.html#comments https://luolt.cn/feed/