首页
文章导航
导航
壁纸
留言板
更多
直播
友链
统计
关于
Search
1
cloudflare上免费部署随机地址生成器
26 阅读
2
bpb面板搭建【有时效性,可能1001用不了】
14 阅读
3
Sub-Hub 基于 Cloudflare Worker+D1数据库的简洁订阅管理器,摒弃第三方订阅转换!
13 阅读
4
Claw Cloud 免费容器平台部署哪吒面板教程
12 阅读
5
【白嫖攻略】在cloudflare搭建域名邮箱并转发
12 阅读
默认
日常
学习
技术
登录
Search
标签搜索
cloudflare
白嫖
CF
docker
安装
脚本
图片
壁纸
Linux
Caddy
代码
哪吒
节点
域名
搭建
github
网页
HTML
LXC
手机壁纸
ws01
累计撰写
124
篇文章
累计收到
45
条评论
首页
栏目
默认
日常
学习
技术
页面
文章导航
导航
壁纸
留言板
直播
友链
统计
关于
搜索到
5
篇与
的结果
2025-10-19
CF _worker搭建 WS01 Note-20251019
CF _worker搭建 WS01 Note,可设置私人日记或公开分享变量设置:1、帐号:USERNAME,默认:9527a2、密码:PASSWORD,默认:9527abc3、KV空间邦定:WS01_NOTE_KV// WS01 Note - Cloudflare Workers + KV 日记本应用 // 作者: WS01 // 功能: 基于Cloudflare Workers和KV数据库的简单日记本 export default { async fetch(request, env, ctx) { const url = new URL(request.url); const path = url.pathname; // 处理CORS const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', }; if (request.method === 'OPTIONS') { return new Response(null, { status: 200, headers: corsHeaders }); } try { // 路由处理 if (path === '/' || path === '/login') { return new Response(getLoginPage(), { headers: { 'Content-Type': 'text/html; charset=utf-8', ...corsHeaders } }); } if (path === '/diary') { return new Response(getDiaryPage(), { headers: { 'Content-Type': 'text/html; charset=utf-8', ...corsHeaders } }); } // 查看具体日记页面 if (path.startsWith('/diary/') && path.split('/').length === 3) { const diaryId = path.split('/')[2]; return new Response(getDiaryDetailPage(diaryId), { headers: { 'Content-Type': 'text/html; charset=utf-8', ...corsHeaders } }); } // 分享日记目录页面路由 if (path === '/share') { return new Response(getShareIndexPage(), { headers: { 'Content-Type': 'text/html; charset=utf-8', ...corsHeaders } }); } // 分享页面路由 if (path.startsWith('/share/') && path.split('/').length === 3) { const shareId = path.split('/')[2]; return new Response(getSharePage(shareId), { headers: { 'Content-Type': 'text/html; charset=utf-8', ...corsHeaders } }); } if (path === '/api/auth') { return handleAuth(request, env, corsHeaders); } if (path === '/api/diary') { return handleDiaryAPI(request, env, corsHeaders); } // 添加分享相关API路由 if (path === '/api/diary/share' && (request.method === 'POST' || request.method === 'PUT')) { return handleShareDiary(request, env, corsHeaders); } // 获取分享日记API路由 if (path.startsWith('/api/share/') && path.split('/').length === 4) { const shareId = path.split('/')[3]; return handleGetSharedDiary(shareId, env, corsHeaders); } // 获取所有分享日记API路由 if (path === '/api/shares' && request.method === 'GET') { return handleGetAllShares(request, env, corsHeaders); } // 获取用户分享日记列表API路由 if (path === '/api/diary/shares' && request.method === 'GET') { return handleGetUserShares(request, env, corsHeaders); } // 删除分享日记API路由 if (path === '/api/diary/share' && request.method === 'DELETE') { return handleDeleteShare(request, env, corsHeaders); } // 添加备份相关API路由 if (path === '/api/diary/backup' && request.method === 'POST') { return handleCreateBackup(request, env, corsHeaders); } if (path === '/api/diary/backups' && request.method === 'GET') { return handleGetBackups(request, env, corsHeaders); } if (path === '/api/diary/restore' && request.method === 'POST') { return handleRestoreBackup(request, env, corsHeaders); } if (path === '/api/diary/backup' && request.method === 'DELETE') { return handleDeleteBackup(request, env, corsHeaders); } // 404处理 return new Response('页面未找到', { status: 404, headers: { 'Content-Type': 'text/plain; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('Worker错误:', error); return new Response('服务器内部错误', { status: 500, headers: { 'Content-Type': 'text/plain; charset=utf-8', ...corsHeaders } }); } } }; // 登录页面 function getLoginPage() { return ` <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>WS01 Note - 登录</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; display: flex; align-items: center; justify-content: center; } .login-container { background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 10px 25px rgba(0,0,0,0.08); width: 100%; max-width: 360px; } .logo { text-align: center; margin-bottom: 1.5rem; } .logo h1 { color: #333; font-size: 1.6rem; font-weight: 400; } .logo p { color: #666; margin-top: 0.3rem; font-size: 0.9rem; } .form-group { margin-bottom: 1.2rem; } .form-group label { display: block; margin-bottom: 0.4rem; color: #333; font-weight: 500; font-size: 0.9rem; } .form-group input { width: 100%; padding: 0.6rem; border: 1px solid #e1e5e9; border-radius: 6px; font-size: 0.9rem; transition: border-color 0.3s; } .form-group input:focus { outline: none; border-color: #667eea; } .login-btn { width: 100%; padding: 0.6rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 6px; font-size: 0.9rem; font-weight: 500; cursor: pointer; transition: transform 0.2s; } .login-btn:hover { transform: translateY(-2px); } .error-message { color: #e74c3c; text-align: center; margin-top: 1rem; display: none; } </style> </head> <body> <div class="login-container"> <div class="logo"> <h1>WS01 Note</h1> <p>您的私人日记本</p> </div> <form id="loginForm"> <div class="form-group"> <label for="username">用户名</label> <input type="text" id="username" name="username" required> </div> <div class="form-group"> <label for="password">密码</label> <input type="password" id="password" name="password" required> </div> <button type="submit" class="login-btn">登录</button> </form> <div id="errorMessage" class="error-message"></div> </div> <script> document.getElementById('loginForm').addEventListener('submit', async (e) => { e.preventDefault(); const username = document.getElementById('username').value; const password = document.getElementById('password').value; const errorDiv = document.getElementById('errorMessage'); try { const response = await fetch('/api/auth', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ username, password }) }); const result = await response.json(); if (result.success) { localStorage.setItem('ws01_token', result.token); window.location.href = '/diary'; } else { errorDiv.textContent = result.message || '登录失败'; errorDiv.style.display = 'block'; } } catch (error) { errorDiv.textContent = '网络错误,请重试'; errorDiv.style.display = 'block'; } }); </script> </body> </html>`; } // 日记页面 function getDiaryPage() { return ` <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>WS01 Note - 我的日记</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f8f9fa; min-height: 100vh; } .sites { width:1000px; background:#F2F2F2; border:1px solid rgba(0,0,0,.06); margin:10px auto; padding:0px; color: white; border-radius: 6px; } .sites01 { width:1280px; background:; border:2px solid auto; margin:15px auto; padding:0px; } .sites dl { height:36px; line-height:36px; display:block; margin:0; } .sites dl.alt { background:#c5dff6; border-top:1px solid #ffffff; border-bottom:1px solid #ffffff; } .sites dl.alt2 { background:#dcecfa; border-top:1px solid #ffffff; border-bottom:1px solid #ffffff; } .sites dt,.sites dd { text-align:center; display:block; float:left; } .sites dt { width:60px; } .sites dd { width:90px; margin:0; } .header { background: #D4D4D4; padding: 0.8rem 1.5rem; box-shadow: 0 1px 6px rgba(0,0,0,0.08); display: flex; justify-content: center; align-items: center; position: relative; } .logo { font-size: 1.5rem; font-weight: 400; color: #333; } .logout-btn { background: #e74c3c; color: white; border: none; width: 40px; height: 40px; border-radius: 50%; cursor: pointer; font-size: 1.2rem; position: absolute; right: 2.6rem; display: flex; align-items: center; justify-content: center; transition: transform 0.2s; } .logout-btn:hover { background: #c0392b; transform: scale(1.1); } .container { max-width: 960px; margin: 1.5rem auto; padding: 0 1.5rem; display: flex; gap: 1.5rem; } .main-content { flex: 1; min-width: 0; } .sidebar { width: 300px; flex-shrink: 0; } /* 搜索框样式 */ .search-section { margin-bottom: 1rem; } .search-container { position: relative; background: white; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); overflow: hidden; } .search-input { width: 100%; padding: 0.8rem 2.5rem 0.8rem 0.8rem; border: none; border-radius: 8px; font-size: 0.9rem; background: white; transition: all 0.3s ease; } .search-input:focus { outline: none; box-shadow: 0 0 0 2px #667eea; } .search-input::placeholder { color: #999; font-size: 0.85rem; } .clear-search-btn { position: absolute; right: 0.5rem; top: 50%; transform: translateY(-50%); background: none; border: none; color: #999; font-size: 1.2rem; cursor: pointer; padding: 0.2rem; border-radius: 50%; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; } .clear-search-btn:hover { background: #f0f0f0; color: #666; } .clear-search-btn.hidden { display: none; } .diary-form { background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); } .diary-form-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.2rem; } .diary-form-title { color: #333; margin: 0; font-size: 1.3rem; } .current-time { color: #666; font-size: 0.8rem; } /* 添加保存按钮图标样式 */ .save-btn-icon { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; width: 32px; height: 32px; border-radius: 50%; cursor: pointer; font-size: 1rem; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 8px rgba(0,0,0,0.15); transition: transform 0.2s; } .save-btn-icon:hover { transform: scale(1.1); } /* 添加分享按钮样式 */ .share-btn-icon { background: linear-gradient(135deg, #28a745 0%, #20c997 100%); color: white; border: none; width: 32px; height: 32px; border-radius: 50%; cursor: pointer; font-size: 1rem; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 8px rgba(0,0,0,0.15); transition: transform 0.2s; } .share-btn-icon:hover { transform: scale(1.1); } /* 添加分享按钮样式 */ .share01-btn-icon { background: linear-gradient(135deg, #28a745 0%, #20c997 100%); color: white; border: none; width: 40px; height: 40px; border-radius: 50%; cursor: pointer; font-size: 1.2rem; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 8px rgba(0,0,0,0.15); transition: transform 0.2s; text-decoration: none; } .share01-btn-icon:hover { transform: scale(1.1); } .header-with-button { display: flex; justify-content: space-between; align-items: center; } .header-controls { display: flex; align-items: center; gap: 0.5rem; } .font-size-select { padding: 0.3rem 0.5rem; border: 1px solid #e1e5e9; border-radius: 4px; font-size: 0.8rem; background: white; cursor: pointer; transition: border-color 0.3s; } .font-size-select:focus { outline: none; border-color: #667eea; } .font-size-select:hover { border-color: #667eea; } .form-group { margin-bottom: 1.2rem; } .form-group label { display: block; margin-bottom: 0.4rem; color: #333; font-weight: 500; font-size: 0.9rem; } .form-group input, .form-group textarea { width: 100%; padding: 0.6rem; border: 1px solid #e1e5e9; border-radius: 6px; font-size: 0.9rem; font-family: inherit; transition: border-color 0.3s; } .form-group input:focus, .form-group textarea:focus { outline: none; border-color: #667eea; } .form-group textarea { width: 100%; padding: 0.6rem; border: 1px solid #e1e5e9; border-radius: 6px; font-size: 0.9rem; font-family: inherit; transition: border-color 0.3s; min-height: 720px; //写日记模块的高度 } /* 添加字符计数器样式 */ .char-count { font-size: 0.8rem; color: #666; text-align: right; margin-top: 0.25rem; } .char-count.warning { color: #ffc107; } .char-count.error { color: #dc3545; } .save-btn { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; padding: 0.75rem 2rem; border-radius: 8px; font-size: 1rem; font-weight: 500; cursor: pointer; transition: transform 0.2s; } .save-btn:hover { transform: translateY(-2px); } .diary-list { background: white; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); overflow: hidden; height: calc(100vh - 410px); display: flex; flex-direction: column; } .diary-list-header { padding: 1.2rem; margin: 0; color: #333; border-bottom: 1px solid #f0f0f0; background: #f8f9fa; font-size: 1rem; font-weight: 600; } .diary-list-content { flex: 1; overflow-y: auto; padding: 0; } .diary-date-group { margin-bottom: 0.8rem; } .date-header { background: #e9ecef; padding: 0.4rem 0.8rem; font-weight: 600; color: #495057; border-bottom: 1px solid #dee2e6; font-size: 0.8rem; position: sticky; top: 0; z-index: 1; } .diary-items { background: white; } .diary-item { padding: 0.6rem 0.8rem; border-bottom: 1px solid #f0f0f0; cursor: pointer; transition: background-color 0.2s; display: flex; flex-direction: column; gap: 0.2rem; } .diary-item:hover { background-color: #f8f9fa; } .diary-item:last-child { border-bottom: none; } .diary-title { color: #333; font-size: 0.85rem; font-weight: 500; line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .diary-time { color: #666; font-size: 0.7rem; align-self: flex-end; } /* 修改: 按钮容器样式 */ .diary-actions { display: flex; gap: 0.4rem; margin-top: 0.3rem; justify-content: flex-end; } .edit-btn, .delete-btn { padding: 0.2rem 0.4rem; font-size: 0.7rem; border: none; border-radius: 3px; cursor: pointer; } .edit-btn { background-color: #007bff; color: white; } .delete-btn { background-color: #dc3545; color: white; } .edit-btn:hover { background-color: #0056b3; } .delete-btn:hover { background-color: #c82333; } .empty-state { text-align: center; padding: 3rem; color: #666; } .message { padding: 1rem; margin-bottom: 1rem; border-radius: 8px; display: none; } .message.success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; } .message.error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } /* 添加分享日记模块样式 */ .share-section { margin-top: 0.8rem; padding-top: 0.8rem; border-top: 1px solid #eee; } .share-controls { display: flex; gap: 0.4rem; margin-bottom: 0.8rem; } .share-list { flex: 1; overflow-y: auto; padding: 0; height: calc(100vh - 520px); } .share-date-group { margin-bottom: 0.8rem; } .share-items { background: white; } .share-item { padding: 0.6rem 0.8rem; border-bottom: 1px solid #f0f0f0; cursor: pointer; transition: background-color 0.2s; display: flex; flex-direction: column; gap: 0.2rem; } .share-item:hover { background-color: #f8f9fa; } .share-item:last-child { border-bottom: none; } .share-title { color: #333; font-size: 0.85rem; font-weight: 500; line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .share-time { color: #666; font-size: 0.7rem; align-self: flex-end; } .share-actions { display: flex; gap: 0.4rem; margin-top: 0.3rem; justify-content: flex-end; } .edit-share-btn, .delete-share-btn { padding: 0.2rem 0.4rem; font-size: 0.7rem; border: none; border-radius: 3px; cursor: pointer; } .edit-share-btn { background-color: #007bff; color: white; } .delete-share-btn { background-color: #dc3545; color: white; } .edit-share-btn:hover { background-color: #0056b3; } .delete-share-btn:hover { background-color: #c82333; } /* 添加备份恢复按钮样式 */ .backup-section { margin-top: 0.8rem; padding-top: 0.8rem; border-top: 1px solid #eee; } .backup-controls { display: flex; gap: 0.4rem; margin-bottom: 0.8rem; } .backup-btn { padding: 0.4rem 0.8rem; border: none; border-radius: 4px; cursor: pointer; font-size: 0.8rem; } .backup-btn.primary { background: #28a745; color: white; } .backup-btn.secondary { background: #17a2b8; color: white; } .backup-list { max-height: 150px; overflow-y: auto; border: 1px solid #ddd; border-radius: 4px; padding: 0.4rem; } .backup-item { background: #ffffff; color: #969696; padding: 0.4rem; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; } .backup-item:last-child { border-bottom: none; } .restore-btn { background: #ffc107; color: black; border: none; padding: 0.2rem 0.4rem; border-radius: 3px; cursor: pointer; font-size: 0.7rem; } .delete-backup-btn { background: #dc3545; color: white; border: none; padding: 0.2rem 0.4rem; border-radius: 3px; cursor: pointer; font-size: 0.7rem; } /* 响应式设计 */ @media (max-width: 768px) { .sites { width: 95%; margin: 5px auto; } .container { flex-direction: column; gap: 0.8rem; margin: 1rem auto; padding: 0 1rem; } .sidebar { width: 100%; order: -1; } .diary-list { height: 300px; } .diary-form { padding: 1rem; } .form-group textarea { min-height: 200px; } /* 隐藏手机端的时间显示 */ .current-time { display: none; } } </style> </head> <body> <div class="sites"> <div class="header"> <div class="logo">WS01 Note</div> <a href="/share" class="share01-btn-icon" target="_blank" title="分享目录">📂</a> <button class="logout-btn" onclick="logout()" title="退出登录">🚪</button> </div> <div class="container"> <div id="message" class="message"></div> <div class="main-content"> <div class="diary-form"> <div class="diary-form-header"> <div class="header-with-button"> <h2 class="diary-form-title">写日记</h2> <div class="header-controls"> <select id="fontSizeSelect" class="font-size-select" title="选择字体大小"> <option value="12">12px</option> <option value="14">14px</option> <option value="16" selected>16px</option> <option value="18">18px</option> <option value="20">20px</option> <option value="22">22px</option> </select> <button type="submit" class="save-btn-icon" title="私人保存">💾</button> <button type="button" class="share-btn-icon" title="分享日记">🔗</button> </div> </div> <div class="current-time" id="currentTime"></div> </div> <form id="diaryForm"> <div class="form-group"> <!-- <label for="diaryTitle">标题</label> --> <input type="text" id="diaryTitle" name="title" placeholder="标题..." required> </div> <div class="form-group"> <!-- <label for="diaryContent">内容</label> --> <textarea id="diaryContent" name="content" placeholder="内容..." required></textarea> <div class="char-count" id="charCount">0 / 100000</div> </div> <!-- 删除原来的保存按钮 --> </form> </div> </div> <div class="sidebar"> <!-- 搜索框 --> <div class="search-section"> <div class="search-container"> <input type="text" id="searchInput" class="search-input" placeholder="搜索日记标题或内容..."> <button id="clearSearch" class="clear-search-btn" title="清除搜索">×</button> </div> </div> <div class="diary-list"> <div class="diary-list-header">我的日记</div> <div class="diary-list-content" id="diaryList"> <div class="empty-state">还没有日记,开始记录吧!</div> </div> </div> <!-- 分享日记模块 --> <div class="share-section"> <div class="diary-list-header">分享日记</div> <div class="share-controls"> <button class="backup-btn secondary" onclick="loadSharedDiaries()">刷新分享列表</button> </div> <div class="share-list" id="shareList"> <div class="empty-state">暂无分享日记</div> </div> </div> <!-- 移动备份和恢复功能到这里 --> <div class="backup-section"> <div class="diary-list-header">数据备份与恢复</div> <div class="backup-controls"> <button class="backup-btn primary" onclick="createBackup()">创建备份</button> <button class="backup-btn secondary" onclick="loadBackups()">刷新备份列表</button> </div> <div class="backup-list" id="backupList"> <div class="empty-state">暂无备份</div> </div> </div> </div> </div> <script> // 更新当前时间 function updateTime() { const now = new Date(); const timeString = now.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }); document.getElementById('currentTime').textContent = timeString; } // HTML转义函数 function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // HTML反转义函数 function unescapeHtml(html) { const div = document.createElement('div'); div.innerHTML = html; return div.textContent || div.innerText || ''; } // 添加: 更新字符计数函数 function updateCharCount() { const content = document.getElementById('diaryContent'); const charCount = document.getElementById('charCount'); const currentLength = content.value.length; charCount.textContent = \`\${currentLength} / 100000\`; // 根据字符数量改变颜色 if (currentLength > 1800) { charCount.className = 'char-count error'; } else if (currentLength > 1500) { charCount.className = 'char-count warning'; } else { charCount.className = 'char-count'; } // 超过限制时截断内容 if (currentLength > 100000) { content.value = content.value.substring(0, 100000); charCount.textContent = '100000 / 100000'; } } // 添加: 字号选择功能 function changeFontSize() { const fontSizeSelect = document.getElementById('fontSizeSelect'); const contentTextarea = document.getElementById('diaryContent'); const selectedSize = fontSizeSelect.value; // 应用字体大小到内容区域 contentTextarea.style.fontSize = selectedSize + 'px'; // 保存用户选择到本地存储 localStorage.setItem('ws01_font_size', selectedSize); } // 添加: 加载保存的字体大小 function loadFontSize() { const savedSize = localStorage.getItem('ws01_font_size'); const fontSizeSelect = document.getElementById('fontSizeSelect'); const contentTextarea = document.getElementById('diaryContent'); if (savedSize) { fontSizeSelect.value = savedSize; contentTextarea.style.fontSize = savedSize + 'px'; } else { // 默认字体大小 contentTextarea.style.fontSize = '14px'; } } // 每秒更新时间 setInterval(updateTime, 1000); updateTime(); // 检查登录状态 function checkAuth() { const token = localStorage.getItem('ws01_token'); if (!token) { window.location.href = '/login'; return false; } return true; } // 显示消息 function showMessage(message, type = 'success') { const messageDiv = document.getElementById('message'); messageDiv.textContent = message; messageDiv.className = \`message \${type}\`; messageDiv.style.display = 'block'; setTimeout(() => { messageDiv.style.display = 'none'; }, 3000); } // 加载日记列表 async function loadDiaries() { try { const response = await fetch('/api/diary', { headers: { 'Authorization': \`Bearer \${localStorage.getItem('ws01_token')}\` } }); const result = await response.json(); if (result.success) { allDiaries = result.diaries; // 存储所有日记数据 // 检查是否有搜索条件,如果有则重新执行搜索 const searchTerm = document.getElementById('searchInput').value.trim(); if (searchTerm) { performSearch(); // 重新执行搜索 } else { filteredDiaries = [...allDiaries]; // 初始化过滤后的数据 displayDiaries(filteredDiaries); } } else { if (result.message === '未授权') { window.location.href = '/login'; } else { showMessage('加载日记失败: ' + result.message, 'error'); } } } catch (error) { showMessage('网络错误,请重试', 'error'); } } // 显示日记列表 function displayDiaries(diaries) { const diaryList = document.getElementById('diaryList'); const searchTerm = document.getElementById('searchInput').value.trim(); if (diaries.length === 0) { if (searchTerm) { diaryList.innerHTML = '<div class="empty-state">没有找到匹配的日记</div>'; } else { diaryList.innerHTML = '<div class="empty-state">还没有日记,开始记录吧!</div>'; } return; } // 按日期分组日记 const groupedDiaries = {}; diaries.forEach(diary => { const date = new Date(diary.date).toLocaleDateString('zh-CN'); if (!groupedDiaries[date]) { groupedDiaries[date] = []; } groupedDiaries[date].push(diary); }); // 生成HTML let html = ''; const sortedDates = Object.keys(groupedDiaries).sort((a, b) => new Date(b) - new Date(a)); sortedDates.forEach(date => { html += \`<div class="diary-date-group"> <div class="date-header">\${date}</div> <div class="diary-items">\`; groupedDiaries[date].forEach(diary => { // 限制标题显示长度 const maxTitleLength = 15; const displayTitle = diary.title.length > maxTitleLength ? diary.title.substring(0, maxTitleLength) + '...' : diary.title; html += \`<div class="diary-item" onclick="viewDiary('\${diary.id}')"> <div style="display: flex; justify-content: space-between; align-items: center;"> <div class="diary-title">\${displayTitle}</div> <div class="diary-actions"> <button class="edit-btn" onclick="editDiary(event, '\${diary.id}')" title="编辑">✎</button> <button class="delete-btn" onclick="deleteDiary(event, '\${diary.id}')" title="删除">🗑</button> </div> </div> <!-- 删除时间显示 --> </div>\`; }); html += \`</div></div>\`; }); diaryList.innerHTML = html; } // 添加: 编辑日记功能 function editDiary(event, diaryId) { event.stopPropagation(); // 防止触发查看日记 // 查找要编辑的日记 fetch('/api/diary', { headers: { 'Authorization': \`Bearer \${localStorage.getItem('ws01_token')}\` } }) .then(response => response.json()) .then(result => { if (result.success) { const diary = result.diaries.find(d => d.id === diaryId); if (diary) { // 填充表单(反转义HTML字符) document.getElementById('diaryTitle').value = unescapeHtml(diary.title); document.getElementById('diaryContent').value = unescapeHtml(diary.content); // 保存当前编辑的日记ID到表单属性中 document.getElementById('diaryForm').setAttribute('data-edit-id', diaryId); // 更改按钮文字为"更新日记" document.querySelector('.save-btn').textContent = '更新日记'; // 滚动到表单顶部 document.querySelector('.diary-form').scrollIntoView({ behavior: 'smooth' }); } else { showMessage('找不到要编辑的日记', 'error'); } } else { if (result.message === '未授权') { window.location.href = '/login'; } else { showMessage('加载失败: ' + result.message, 'error'); } } }) .catch(error => { showMessage('网络错误,请重试', 'error'); }); } // 添加: 删除日记功能 function deleteDiary(event, diaryId) { event.stopPropagation(); // 防止触发查看日记 if (!confirm('确定要删除这篇日记吗?')) { return; } fetch('/api/diary', { method: 'DELETE', headers: { 'Content-Type': 'application/json', 'Authorization': \`Bearer \${localStorage.getItem('ws01_token')}\` }, body: JSON.stringify({ id: diaryId }) }) .then(response => response.json()) .then(result => { if (result.success) { showMessage('日记删除成功!'); loadDiaries(); // 重新加载日记列表,这会更新allDiaries和filteredDiaries // 如果正在编辑被删除的日记,重置表单 const form = document.getElementById('diaryForm'); if (form.getAttribute('data-edit-id') === diaryId) { form.reset(); form.removeAttribute('data-edit-id'); document.querySelector('.save-btn').textContent = '保存日记'; } } else { if (result.message === '未授权') { window.location.href = '/login'; } else { showMessage('删除失败: ' + result.message, 'error'); } } }) .catch(error => { showMessage('网络错误,请重试', 'error'); }); } // 保存日记 document.getElementById('diaryForm').addEventListener('submit', async (e) => { e.preventDefault(); if (!checkAuth()) return; const title = document.getElementById('diaryTitle').value; const content = document.getElementById('diaryContent').value; const editId = document.getElementById('diaryForm').getAttribute('data-edit-id'); const editShareId = document.getElementById('diaryForm').getAttribute('data-edit-share-id'); // 对HTML特殊字符进行转义 const escapedTitle = escapeHtml(title); const escapedContent = escapeHtml(content); try { const method = editId ? 'PUT' : 'POST'; const body = editId ? JSON.stringify({ id: editId, title: escapedTitle, content: escapedContent }) : JSON.stringify({ title: escapedTitle, content: escapedContent, editShareId: editShareId || null }); const response = await fetch('/api/diary', { method: method, headers: { 'Content-Type': 'application/json', 'Authorization': \`Bearer \${localStorage.getItem('ws01_token')}\` }, body: body }); const result = await response.json(); if (result.success) { if (editId) { showMessage('日记更新成功!'); // 重置表单状态 document.getElementById('diaryForm').removeAttribute('data-edit-id'); document.querySelector('.save-btn').textContent = '保存日记'; } else { showMessage('日记保存成功!'); document.getElementById('diaryForm').reset(); // 如果是从分享日记编辑而来,重置表单状态 if (editShareId) { document.getElementById('diaryForm').removeAttribute('data-edit-share-id'); } } // 刷新两个列表 loadDiaries(); loadSharedDiaries(); } else { if (result.message === '未授权') { window.location.href = '/login'; } else { showMessage('保存失败: ' + result.message, 'error'); } } } catch (error) { showMessage('网络错误,请重试', 'error'); } }); // 添加: 为保存按钮图标添加点击事件监听器 document.querySelector('.save-btn-icon').addEventListener('click', function() { document.getElementById('diaryForm').dispatchEvent(new Event('submit')); }); // 添加: 为分享按钮图标添加点击事件监听器 document.querySelector('.share-btn-icon').addEventListener('click', function() { shareDiary(); }); // 分享日记功能 async function shareDiary() { if (!checkAuth()) return; const title = document.getElementById('diaryTitle').value; const content = document.getElementById('diaryContent').value; const editShareId = document.getElementById('diaryForm').getAttribute('data-edit-share-id'); const editDiaryId = document.getElementById('diaryForm').getAttribute('data-edit-id'); if (!title.trim() || !content.trim()) { showMessage('请先填写标题和内容', 'error'); return; } // 对HTML特殊字符进行转义 const escapedTitle = escapeHtml(title); const escapedContent = escapeHtml(content); try { const method = editShareId ? 'PUT' : 'POST'; const body = editShareId ? JSON.stringify({ shareId: editShareId, title: escapedTitle, content: escapedContent }) : JSON.stringify({ title: escapedTitle, content: escapedContent, editDiaryId: editDiaryId || null }); const response = await fetch('/api/diary/share', { method: method, headers: { 'Content-Type': 'application/json', 'Authorization': \`Bearer \${localStorage.getItem('ws01_token')}\` }, body: body }); const result = await response.json(); if (result.success) { if (editShareId) { // 更新分享日记 const shareUrl = \`\${window.location.origin}/share/\${editShareId}\`; showMessage(\`分享日记更新成功!链接:\${shareUrl}\`, 'success'); // 重置表单状态 document.getElementById('diaryForm').removeAttribute('data-edit-share-id'); } else { // 新建分享日记 const shareUrl = \`\${window.location.origin}/share/\${result.shareId}\`; showMessage(\`分享成功!链接:\${shareUrl}\`, 'success'); // 如果是从我的日记编辑而来,重置表单状态 if (editDiaryId) { document.getElementById('diaryForm').removeAttribute('data-edit-id'); } } // 可选:复制链接到剪贴板 if (navigator.clipboard) { const shareUrl = editShareId ? \`\${window.location.origin}/share/\${editShareId}\` : \`\${window.location.origin}/share/\${result.shareId}\`; navigator.clipboard.writeText(shareUrl).then(() => { console.log('分享链接已复制到剪贴板'); }); } // 刷新两个列表 loadDiaries(); loadSharedDiaries(); } else { if (result.message === '未授权') { window.location.href = '/login'; } else { showMessage('分享失败: ' + result.message, 'error'); } } } catch (error) { showMessage('网络错误,请重试', 'error'); } } // 查看具体日记页面 function viewDiary(diaryId) { window.location.href = \`/diary/\${diaryId}\`; } // 退出登录 function logout() { localStorage.removeItem('ws01_token'); window.location.href = '/login'; } // 添加备份相关函数 async function createBackup() { try { const response = await fetch('/api/diary/backup', { method: 'POST', headers: { 'Authorization': \`Bearer \${localStorage.getItem('ws01_token')}\` } }); const result = await response.json(); if (result.success) { showMessage('备份创建成功!'); loadBackups(); } else { if (result.message === '未授权') { window.location.href = '/login'; } else { showMessage('备份创建失败: ' + result.message, 'error'); } } } catch (error) { showMessage('网络错误,请重试', 'error'); } } async function loadBackups() { try { const response = await fetch('/api/diary/backups', { headers: { 'Authorization': \`Bearer \${localStorage.getItem('ws01_token')}\` } }); const result = await response.json(); if (result.success) { displayBackups(result.backups); } else { if (result.message === '未授权') { window.location.href = '/login'; } else { showMessage('加载备份列表失败: ' + result.message, 'error'); } } } catch (error) { showMessage('网络错误,请重试', 'error'); } } function displayBackups(backups) { const backupList = document.getElementById('backupList'); if (backups.length === 0) { backupList.innerHTML = '<div class="empty-state">暂无备份</div>'; return; } let html = ''; backups.forEach(backup => { const date = new Date(backup.timestamp).toLocaleString('zh-CN'); html += \` <div class="backup-item"> <div> <div>备份 #\${backup.id}</div> <div style="font-size: 0.8rem; color: #666;">\${date}</div> <div style="font-size: 0.8rem; color: #666;">包含 \${backup.count} 条日记\${backup.shareCount > 0 ? ',' + backup.shareCount + ' 条分享日记' : ''}</div> </div> <div> <button class="restore-btn" onclick="restoreBackup('\${backup.id}')">恢复</button> <button class="delete-backup-btn" onclick="deleteBackup('\${backup.id}')">删除</button> </div> </div>\`; }); backupList.innerHTML = html; } async function restoreBackup(backupId) { if (!confirm('确定要恢复此备份吗?这将覆盖当前所有日记数据!')) { return; } try { const response = await fetch('/api/diary/restore', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': \`Bearer \${localStorage.getItem('ws01_token')}\` }, body: JSON.stringify({ backupId }) }); const result = await response.json(); if (result.success) { showMessage('数据恢复成功!'); loadDiaries(); // 重新加载日记列表 } else { if (result.message === '未授权') { window.location.href = '/login'; } else { showMessage('数据恢复失败: ' + result.message, 'error'); } } } catch (error) { showMessage('网络错误,请重试', 'error'); } } async function deleteBackup(backupId) { if (!confirm('确定要删除此备份吗?')) { return; } try { const response = await fetch('/api/diary/backup', { method: 'DELETE', headers: { 'Content-Type': 'application/json', 'Authorization': \`Bearer \${localStorage.getItem('ws01_token')}\` }, body: JSON.stringify({ backupId }) }); const result = await response.json(); if (result.success) { showMessage('备份删除成功!'); loadBackups(); // 重新加载备份列表 } else { if (result.message === '未授权') { window.location.href = '/login'; } else { showMessage('备份删除失败: ' + result.message, 'error'); } } } catch (error) { showMessage('网络错误,请重试', 'error'); } } // 搜索相关变量 let allDiaries = []; // 存储所有日记数据 let filteredDiaries = []; // 存储过滤后的日记数据 // 搜索功能 async function performSearch() { const searchTerm = document.getElementById('searchInput').value.toLowerCase().trim(); const clearBtn = document.getElementById('clearSearch'); if (searchTerm === '') { // 如果搜索框为空,显示所有日记 filteredDiaries = [...allDiaries]; clearBtn.classList.add('hidden'); displayDiaries(filteredDiaries); } else { // 搜索我的日记 const myDiaryResults = allDiaries.filter(diary => diary.title.toLowerCase().includes(searchTerm) || diary.content.toLowerCase().includes(searchTerm) ); // 搜索分享日记 try { const response = await fetch('/api/diary/shares', { headers: { 'Authorization': \`Bearer \${localStorage.getItem('ws01_token')}\` } }); if (response.ok) { const result = await response.json(); const shareResults = result.shares.filter(share => share.title.toLowerCase().includes(searchTerm) || share.content.toLowerCase().includes(searchTerm) ); // 合并搜索结果 const combinedResults = [ ...myDiaryResults.map(diary => ({ ...diary, type: 'private' })), ...shareResults.map(share => ({ ...share, type: 'shared' })) ]; // 按日期排序 combinedResults.sort((a, b) => new Date(b.date) - new Date(a.date)); filteredDiaries = combinedResults; } else { // 如果获取分享日记失败,只显示我的日记搜索结果 filteredDiaries = myDiaryResults.map(diary => ({ ...diary, type: 'private' })); } } catch (error) { // 如果网络错误,只显示我的日记搜索结果 filteredDiaries = myDiaryResults.map(diary => ({ ...diary, type: 'private' })); } clearBtn.classList.remove('hidden'); displaySearchResults(filteredDiaries); } } // 显示搜索结果(包含我的日记和分享日记) function displaySearchResults(results) { const diaryList = document.getElementById('diaryList'); const searchTerm = document.getElementById('searchInput').value.trim(); if (results.length === 0) { diaryList.innerHTML = '<div class="empty-state">没有找到匹配的日记</div>'; return; } // 按日期分组日记 const groupedDiaries = {}; results.forEach(diary => { const date = new Date(diary.date).toLocaleDateString('zh-CN'); if (!groupedDiaries[date]) { groupedDiaries[date] = []; } groupedDiaries[date].push(diary); }); // 生成HTML let html = ''; const sortedDates = Object.keys(groupedDiaries).sort((a, b) => new Date(b) - new Date(a)); sortedDates.forEach(date => { html += \`<div class="diary-date-group"> <div class="date-header">\${date}</div> <div class="diary-items">\`; groupedDiaries[date].forEach(diary => { // 限制标题显示长度 const maxTitleLength = 15; const displayTitle = diary.title.length > maxTitleLength ? diary.title.substring(0, maxTitleLength) + '...' : diary.title; // 根据类型显示不同的操作按钮 let actionButtons = ''; if (diary.type === 'private') { actionButtons = \` <div class="diary-actions"> <button class="edit-btn" onclick="editDiary(event, '\${diary.id}')" title="编辑">✎</button> <button class="delete-btn" onclick="deleteDiary(event, '\${diary.id}')" title="删除">🗑</button> </div>\`; } else if (diary.type === 'shared') { actionButtons = \` <div class="diary-actions"> <button class="edit-btn" onclick="editSharedDiary(event, '\${diary.id}')" title="编辑">✎</button> <button class="delete-btn" onclick="deleteSharedDiary(event, '\${diary.id}')" title="删除">🗑</button> </div>\`; } // 根据类型设置点击事件 const clickEvent = diary.type === 'private' ? \`onclick="viewDiary('\${diary.id}')"\` : \`onclick="viewSharedDiary('\${diary.shareUrl}')"\`; html += \`<div class="diary-item" \${clickEvent}> <div style="display: flex; justify-content: space-between; align-items: center;"> <div class="diary-title">\${displayTitle} \${diary.type === 'shared' ? '<span style="color: #28a745; font-size: 0.7rem;">[分享]</span>' : ''}</div> \${actionButtons} </div> </div>\`; }); html += \`</div></div>\`; }); diaryList.innerHTML = html; } // 清除搜索 function clearSearch() { document.getElementById('searchInput').value = ''; document.getElementById('clearSearch').classList.add('hidden'); filteredDiaries = [...allDiaries]; displayDiaries(filteredDiaries); } // 加载分享日记列表 async function loadSharedDiaries() { try { const response = await fetch('/api/diary/shares', { headers: { 'Authorization': \`Bearer \${localStorage.getItem('ws01_token')}\` } }); const result = await response.json(); if (result.success) { displaySharedDiaries(result.shares); } else { if (result.message === '未授权') { window.location.href = '/login'; } else { showMessage('加载分享列表失败: ' + result.message, 'error'); } } } catch (error) { showMessage('网络错误,请重试', 'error'); } } // 显示分享日记列表 function displaySharedDiaries(shares) { const shareList = document.getElementById('shareList'); if (shares.length === 0) { shareList.innerHTML = '<div class="empty-state">暂无分享日记</div>'; return; } // 按日期分组分享日记 const groupedShares = {}; shares.forEach(share => { const date = new Date(share.date).toLocaleDateString('zh-CN'); if (!groupedShares[date]) { groupedShares[date] = []; } groupedShares[date].push(share); }); // 生成HTML let html = ''; const sortedDates = Object.keys(groupedShares).sort((a, b) => new Date(b) - new Date(a)); sortedDates.forEach(date => { html += \`<div class="share-date-group"> <div class="date-header">\${date}</div> <div class="share-items">\`; groupedShares[date].forEach(share => { // 限制标题显示长度 const maxTitleLength = 15; const displayTitle = share.title.length > maxTitleLength ? share.title.substring(0, maxTitleLength) + '...' : share.title; html += \`<div class="share-item" onclick="viewSharedDiary('\${share.shareUrl}')"> <div style="display: flex; justify-content: space-between; align-items: center;"> <div class="share-title">\${displayTitle}</div> <div class="share-actions"> <button class="edit-share-btn" onclick="editSharedDiary(event, '\${share.id}')" title="编辑">✎</button> <button class="delete-share-btn" onclick="deleteSharedDiary(event, '\${share.id}')" title="删除">🗑</button> </div> </div> </div>\`; }); html += \`</div></div>\`; }); shareList.innerHTML = html; } // 查看分享日记 function viewSharedDiary(shareUrl) { window.open(shareUrl, '_blank'); } // 编辑分享日记 async function editSharedDiary(event, shareId) { event.stopPropagation(); // 防止触发查看日记 try { // 获取分享日记内容 const response = await fetch(\`/api/share/\${shareId}\`); if (response.ok) { const shareData = await response.json(); // 填充表单(反转义HTML字符) document.getElementById('diaryTitle').value = unescapeHtml(shareData.title); document.getElementById('diaryContent').value = unescapeHtml(shareData.content); // 保存当前编辑的分享ID到表单属性中 document.getElementById('diaryForm').setAttribute('data-edit-share-id', shareId); // 滚动到表单顶部 document.querySelector('.diary-form').scrollIntoView({ behavior: 'smooth' }); showMessage('分享日记已加载到编辑区域,修改后点击分享按钮更新', 'success'); } else { showMessage('加载分享日记失败', 'error'); } } catch (error) { showMessage('网络错误,请重试', 'error'); } } // 删除分享日记 async function deleteSharedDiary(event, shareId) { event.stopPropagation(); // 防止触发查看日记 if (!confirm('确定要删除这个分享日记吗?')) { return; } try { const response = await fetch('/api/diary/share', { method: 'DELETE', headers: { 'Content-Type': 'application/json', 'Authorization': \`Bearer \${localStorage.getItem('ws01_token')}\` }, body: JSON.stringify({ shareId }) }); const result = await response.json(); if (result.success) { showMessage('分享日记删除成功!'); loadSharedDiaries(); // 重新加载分享列表 } else { if (result.message === '未授权') { window.location.href = '/login'; } else { showMessage('删除失败: ' + result.message, 'error'); } } } catch (error) { showMessage('网络错误,请重试', 'error'); } } // 页面加载时检查认证并加载日记和备份 if (checkAuth()) { loadDiaries(); loadBackups(); // 加载备份列表 loadSharedDiaries(); // 加载分享日记列表 } // 添加内容输入事件监听器 document.addEventListener('DOMContentLoaded', function() { const contentTextarea = document.getElementById('diaryContent'); const fontSizeSelect = document.getElementById('fontSizeSelect'); const searchInput = document.getElementById('searchInput'); const clearSearchBtn = document.getElementById('clearSearch'); if (contentTextarea) { contentTextarea.addEventListener('input', updateCharCount); } if (fontSizeSelect) { fontSizeSelect.addEventListener('change', changeFontSize); } // 添加搜索功能事件监听器 if (searchInput) { searchInput.addEventListener('input', performSearch); } if (clearSearchBtn) { clearSearchBtn.addEventListener('click', clearSearch); } // 加载保存的字体大小 loadFontSize(); }); </script> </body> </html>`; } // 日记详情页面 function getDiaryDetailPage(diaryId) { return ` <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>WS01 Note - 日记详情</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f8f9fa; min-height: 100vh; } .sites { width:1000px; background:#F2F2F2; border:1px solid rgba(0,0,0,.06); margin:10px auto; padding:0px; color: white; border-radius: 6px; } .sites01 { width:1280px; background:; border:2px solid auto; margin:15px auto; padding:0px; } .sites dl { height:36px; line-height:36px; display:block; margin:0; } .sites dl.alt { background:#c5dff6; border-top:1px solid #ffffff; border-bottom:1px solid #ffffff; } .sites dl.alt2 { background:#dcecfa; border-top:1px solid #ffffff; border-bottom:1px solid #ffffff; } .sites dt,.sites dd { text-align:center; display:block; float:left; } .sites dt { width:60px; } .sites dd { width:90px; margin:0; } .header { background: #D4D4D4; padding: 0.8rem 1.5rem; box-shadow: 0 1px 6px rgba(0,0,0,0.08); display: flex; justify-content: center; align-items: center; position: relative; } .logo { font-size: 1.5rem; font-weight: 400; color: #333; } .back-btn { background: #1C86EE; color: white; border: none; padding: 0.4rem 0.8rem; border-radius: 4px; cursor: pointer; font-size: 0.8rem; position: absolute; right: 2.6rem; } .back-btn:hover { background: #1874CD; } .header-controls { display: flex; align-items: center; gap: 0.5rem; } .font-size-select { padding: 0.3rem 0.5rem; border: 1px solid #e1e5e9; border-radius: 4px; font-size: 0.8rem; background: white; cursor: pointer; transition: border-color 0.3s; } .font-size-select:focus { outline: none; border-color: #667eea; } .font-size-select:hover { border-color: #667eea; } .container { max-width: 960px; margin: 1.5rem auto; padding: 0 1.5rem; } .diary-detail { background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); max-height: 900px; overflow-y: auto; } .diary-header { border-bottom: 1px solid #e9ecef; padding-bottom: 0.8rem; margin-bottom: 1.2rem; } .diary-title { font-size: 1.5rem; color: #333; margin-bottom: 0.4rem; font-weight: 600; word-wrap: break-word; } .diary-date { color: #666; font-size: 0.8rem; } .diary-content { color: #333; line-height: 1.6; white-space: pre-wrap; font-size: 0.9rem; word-wrap: break-word; overflow-wrap: break-word; word-break: break-all; border: 1px dashed #ccc; padding: 0.8rem; border-radius: 4px; } .loading { text-align: center; padding: 2rem; color: #666; } .error { text-align: center; padding: 2rem; color: #e74c3c; } /* 添加复制按钮样式 */ .copy-btn { background: #007bff; color: white; border: none; padding: 0.4rem 0.8rem; border-radius: 4px; cursor: pointer; font-size: 0.8rem; margin-left: 0.8rem; } .copy-btn:hover { background: #0056b3; } .title-container { display: flex; align-items: center; flex-wrap: wrap; } .notification { position: fixed; top: 15px; right: 15px; background: #28a745; color: white; padding: 0.8rem; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.12); display: none; z-index: 1000; font-size: 0.8rem; } </style> </head> <body> <div class="sites"> <div class="header"> <div class="logo">WS01 Note</div> <div class="header-controls"> <select id="fontSizeSelect" class="font-size-select" title="选择字体大小"> <option value="12">12px</option> <option value="14">14px</option> <option value="16" selected>16px</option> <option value="18">18px</option> <option value="20">20px</option> <option value="22">22px</option> </select> <a href="/diary" class="back-btn">← 返回</a> </div> </div> <div class="container"> <div id="diaryDetail" class="diary-detail"> <div class="loading">加载中...</div> </div> </div> <div id="notification" class="notification">内容已复制到剪贴板</div> <script> const diaryId = '${diaryId}'; // 检查登录状态 function checkAuth() { const token = localStorage.getItem('ws01_token'); if (!token) { window.location.href = '/login'; return false; } return true; } // 加载日记详情 async function loadDiaryDetail() { if (!checkAuth()) return; try { const response = await fetch('/api/diary', { headers: { 'Authorization': \`Bearer \${localStorage.getItem('ws01_token')}\` } }); const result = await response.json(); if (result.success) { const diary = result.diaries.find(d => d.id === diaryId); if (diary) { displayDiaryDetail(diary); } else { showError('日记不存在'); } } else { if (result.message === '未授权') { window.location.href = '/login'; } else { showError('加载失败: ' + result.message); } } } catch (error) { showError('网络错误,请重试'); } } // 显示日记详情 function displayDiaryDetail(diary) { const diaryDetail = document.getElementById('diaryDetail'); const date = new Date(diary.date).toLocaleString('zh-CN'); // 限制标题显示长度 const maxTitleLength = 15; const displayTitle = diary.title.length > maxTitleLength ? diary.title.substring(0, maxTitleLength) + '...' : diary.title; diaryDetail.innerHTML = \` <div class="diary-header"> <div class="title-container"> <h1 class="diary-title">\${displayTitle}</h1> <button class="copy-btn" onclick="copyContent('\${diary.content.replace(/'/g, "\\'").replace(/\\n/g, '\\\\n')}')">复制内容</button> </div> <div class="diary-date">\${date}</div> </div> <div class="diary-content" id="diaryContent"></div> \`; // 使用textContent设置内容,避免HTML标签被解析 document.getElementById('diaryContent').textContent = diary.content; // 应用保存的字体大小 loadFontSize(); } // 添加: 字号选择功能 function changeFontSize() { const fontSizeSelect = document.getElementById('fontSizeSelect'); const contentDiv = document.getElementById('diaryContent'); const selectedSize = fontSizeSelect.value; if (contentDiv) { // 应用字体大小到内容区域 contentDiv.style.fontSize = selectedSize + 'px'; // 保存用户选择到本地存储 localStorage.setItem('ws01_detail_font_size', selectedSize); } } // 添加: 加载保存的字体大小 function loadFontSize() { const savedSize = localStorage.getItem('ws01_detail_font_size'); const fontSizeSelect = document.getElementById('fontSizeSelect'); const contentDiv = document.getElementById('diaryContent'); if (savedSize && fontSizeSelect && contentDiv) { fontSizeSelect.value = savedSize; contentDiv.style.fontSize = savedSize + 'px'; } else if (contentDiv) { // 默认字体大小16px contentDiv.style.fontSize = '16px'; } } // 显示错误 function showError(message) { const diaryDetail = document.getElementById('diaryDetail'); diaryDetail.innerHTML = \`<div class="error">\${message}</div>\`; } // 复制内容功能 function copyContent(content) { navigator.clipboard.writeText(content).then(() => { const notification = document.getElementById('notification'); notification.style.display = 'block'; setTimeout(() => { notification.style.display = 'none'; }, 100000); }).catch(err => { console.error('复制失败:', err); const notification = document.getElementById('notification'); notification.textContent = '复制失败'; notification.style.backgroundColor = '#dc3545'; notification.style.display = 'block'; setTimeout(() => { notification.style.display = 'none'; // 恢复默认文本和颜色 notification.textContent = '内容已复制到剪贴板'; notification.style.backgroundColor = '#28a745'; }, 100000); }); } // 复制分享内容功能 function copyShareContent() { if (window.shareContent) { copyContent(window.shareContent); } else { // 如果全局变量不存在,尝试从DOM元素获取 const contentElement = document.getElementById('shareContent'); if (contentElement) { copyContent(contentElement.textContent); } else { console.error('无法获取分享内容'); const notification = document.getElementById('notification'); notification.textContent = '复制失败:无法获取内容'; notification.style.backgroundColor = '#dc3545'; notification.style.display = 'block'; setTimeout(() => { notification.style.display = 'none'; }, 3000); } } } // 页面加载时加载日记详情 loadDiaryDetail(); // 添加字号选择事件监听器 document.addEventListener('DOMContentLoaded', function() { const fontSizeSelect = document.getElementById('fontSizeSelect'); if (fontSizeSelect) { fontSizeSelect.addEventListener('change', changeFontSize); } }); </script> </body> </html>`; } // 分享日记目录页面 function getShareIndexPage() { return ` <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>WS01 Note - 分享目录</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f8f9fa; min-height: 100vh; } .sites { width: 1000px; background: #F2F2F2; border: 1px solid rgba(0,0,0,.06); margin: 10px auto; padding: 0px; color: white; border-radius: 6px; } .header { background: #D4D4D4; padding: 0.8rem 1.5rem; box-shadow: 0 1px 6px rgba(0,0,0,0.08); display: flex; justify-content: center; align-items: center; position: relative; } .logo { font-size: 1.5rem; font-weight: 400; color: #333; } .back-btn { background: #1C86EE; color: white; border: none; padding: 0.4rem 0.8rem; border-radius: 4px; cursor: pointer; font-size: 0.8rem; position: absolute; right: 2.6rem; text-decoration: none; display: inline-block; } .back-btn:hover { background: #1874CD; } .container { max-width: 960px; margin: 1.5rem auto; padding: 0 1.5rem; } .share-index { background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); } .share-index-header { border-bottom: 1px solid #e9ecef; padding-bottom: 0.8rem; margin-bottom: 1.2rem; } .share-index-title { font-size: 1.5rem; color: #333; margin-bottom: 0.4rem; font-weight: 600; } .share-index-subtitle { color: #666; font-size: 0.9rem; } .share-list { display: grid; gap: 1rem; } .share-date-group { margin-bottom: 1.5rem; } .date-header { background: #e9ecef; padding: 0.4rem 0.8rem; font-weight: 600; color: #495057; border-bottom: 1px solid #dee2e6; font-size: 0.9rem; position: sticky; top: 0; z-index: 1; border-radius: 4px 4px 0 0; } .share-items { background: white; border: 1px solid #e9ecef; border-top: none; border-radius: 0 0 4px 4px; } .share-item { padding: 1rem; border-bottom: 1px solid #f0f0f0; cursor: pointer; transition: background-color 0.2s; display: flex; justify-content: space-between; align-items: center; } .share-item:hover { background-color: #f8f9fa; } .share-item:last-child { border-bottom: none; } .share-item-info { flex: 1; min-width: 0; } .share-item-title { color: #333; font-size: 1rem; font-weight: 500; margin-bottom: 0.3rem; line-height: 1.4; } .share-item-preview { color: #666; font-size: 0.85rem; line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; margin-bottom: 0.3rem; } .share-item-date { color: #999; font-size: 0.75rem; } .share-item-actions { display: flex; gap: 0.5rem; margin-left: 1rem; } .view-btn { background: #007bff; color: white; border: none; padding: 0.4rem 0.8rem; border-radius: 4px; cursor: pointer; font-size: 0.8rem; text-decoration: none; display: inline-block; } .view-btn:hover { background: #0056b3; } .loading { text-align: center; padding: 2rem; color: #666; } .error { text-align: center; padding: 2rem; color: #e74c3c; } .empty-state { text-align: center; padding: 3rem; color: #666; } .empty-state-icon { font-size: 3rem; margin-bottom: 1rem; opacity: 0.5; } /* 分页控件样式 */ .pagination { display: flex; justify-content: center; align-items: center; margin-top: 2rem; padding: 1rem 0; border-top: 1px solid #e9ecef; } .pagination-info { margin-right: 1rem; color: #666; font-size: 0.9rem; } .pagination-controls { display: flex; gap: 0.5rem; align-items: center; } .pagination-btn { background: #007bff; color: white; border: none; padding: 0.5rem 0.8rem; border-radius: 4px; cursor: pointer; font-size: 0.9rem; transition: background-color 0.2s; } .pagination-btn:hover:not(:disabled) { background: #0056b3; } .pagination-btn:disabled { background: #6c757d; cursor: not-allowed; opacity: 0.6; } .pagination-current { background: #28a745; color: white; border: none; padding: 0.5rem 0.8rem; border-radius: 4px; font-size: 0.9rem; font-weight: 600; } .pagination-jump { display: flex; align-items: center; gap: 0.5rem; margin-left: 1rem; color: blue; } .pagination-jump input { width: 60px; padding: 0.4rem; border: 1px solid #ced4da; border-radius: 4px; text-align: center; font-size: 0.9rem; } .pagination-jump button { background: #6c757d; color: white; border: none; padding: 0.4rem 0.6rem; border-radius: 4px; cursor: pointer; font-size: 0.8rem; } .pagination-jump button:hover { background: #5a6268; } .footer { font-size: 14px; color: #292929; margin: 15px auto; text-align: center; } /* 响应式设计 */ @media (max-width: 768px) { .sites { width: 100%; margin: 0; border-radius: 0; } .container { margin: 0.5rem auto; padding: 0 0.5rem; } .share-index { padding: 0.5rem; margin: 0; } .share-item { flex-direction: column; align-items: flex-start; gap: 0.5rem; padding: 0.5rem; } .share-item-info { width: 100%; overflow: hidden; } .share-item-title { font-size: 0.9rem; word-break: break-word; overflow-wrap: break-word; } .share-item-preview { font-size: 0.8rem; -webkit-line-clamp: 3; } .share-item-date { font-size: 0.7rem; } .share-item-actions { margin-left: 0; align-self: flex-end; flex-shrink: 0; } .view-btn { padding: 0.3rem 0.6rem; font-size: 0.7rem; } .pagination { flex-direction: column; gap: 1rem; padding: 0.5rem 0; } .pagination-info { margin-right: 0; margin-bottom: 0.5rem; text-align: center; font-size: 0.8rem; } .pagination-controls { flex-wrap: wrap; justify-content: center; gap: 0.3rem; } .pagination-btn { padding: 0.4rem 0.6rem; font-size: 0.8rem; min-width: 40px; } .pagination-current { padding: 0.4rem 0.6rem; font-size: 0.8rem; min-width: 40px; } .pagination-jump { margin-left: 0; margin-top: 0.5rem; flex-wrap: wrap; justify-content: center; gap: 0.3rem; } .pagination-jump input { width: 50px; padding: 0.3rem; font-size: 0.8rem; } .pagination-jump button { padding: 0.3rem 0.5rem; font-size: 0.7rem; } .pagination-jump span { font-size: 0.8rem; } /* 确保所有元素不会溢出 */ * { max-width: 100%; box-sizing: border-box; } .share-item-title, .share-item-preview { word-break: break-word; overflow-wrap: break-word; hyphens: auto; } .share-date-group { margin-bottom: 1rem; } .date-header { font-size: 0.8rem; padding: 0.3rem 0.5rem; } } </style> </head> <body> <div class="sites"> <div class="header"> <div class="logo">WS01 Note - 分享目录</div> </div> <div class="container"> <div class="share-index"> <div class="share-index-header"> <h1 class="share-index-title">分享文章目录</h1> <p class="share-index-subtitle">这里展示所有公开分享的文章,点击标题可查看完整内容。复制的分享内容可能有改变。</p> </div> <div id="shareList" class="share-list"> <div class="loading">加载中...</div> </div> <div id="pagination" class="pagination" style="display: none;"> <div class="pagination-info" id="paginationInfo"></div> <div class="pagination-controls"> <button class="pagination-btn" id="prevBtn" onclick="changePage(currentPage - 1)">上一页</button> <div id="pageNumbers"></div> <button class="pagination-btn" id="nextBtn" onclick="changePage(currentPage + 1)">下一页</button> </div> <div class="pagination-jump"> <span>跳转到</span> <input type="number" id="jumpInput" min="1" placeholder="">页 <button onclick="jumpToPage()">跳转</button> </div> </div> </div> </div> <div class="footer"> <span id="timeDate">载入天数...</span> <script language="javascript"> var now = new Date(); function createtime(){ var grt= new Date("10/12/2025 00:00:00");/*---这里是网站的启用时间:月日年--*/ now.setTime(now.getTime()+250); days = (now - grt ) / 1000 / 60 / 60 / 24; dnum = Math.floor(days); document.getElementById("timeDate").innerHTML = "稳定运行"+dnum+"天"; } setInterval("createtime()",250); </script> <span <p> | 本页总访问量 <span id="busuanzi_site_pv"></span> 次 | <a href="/" target="_blank">登录</p></span> <script defer src="https://bsz.211119.xyz/js"></script> <script> // 全局变量 let currentPage = 1; let totalPages = 1; let totalCount = 0; const pageSize = 20; // 从URL获取当前页码 function getCurrentPageFromUrl() { const urlParams = new URLSearchParams(window.location.search); return parseInt(urlParams.get('page')) || 1; } // 更新URL function updateUrl(page) { const url = new URL(window.location); if (page > 1) { url.searchParams.set('page', page); } else { url.searchParams.delete('page'); } window.history.replaceState({}, '', url); } // 加载所有分享日记 async function loadAllShares(page = 1) { try { currentPage = page; const response = await fetch(\`/api/shares?page=\${page}&limit=\${pageSize}\`); if (response.ok) { const result = await response.json(); displayAllShares(result.shares); updatePagination(result.pagination); updateUrl(page); } else { showError('加载失败,请稍后重试'); } } catch (error) { showError('网络错误,请重试'); } } // 显示所有分享日记 function displayAllShares(shares) { const shareList = document.getElementById('shareList'); if (shares.length === 0) { shareList.innerHTML = \` <div class="empty-state"> <div class="empty-state-icon">📝</div> <div>暂无分享日记</div> </div>\`; return; } // 按日期分组分享日记 const groupedShares = {}; shares.forEach(share => { const date = new Date(share.date).toLocaleDateString('zh-CN'); if (!groupedShares[date]) { groupedShares[date] = []; } groupedShares[date].push(share); }); // 生成HTML let html = ''; const sortedDates = Object.keys(groupedShares).sort((a, b) => new Date(b) - new Date(a)); sortedDates.forEach(date => { html += \`<div class="share-date-group"> <div class="date-header">\${date}</div> <div class="share-items">\`; groupedShares[date].forEach(share => { // 生成内容预览(前100个字符) const preview = share.content.length > 100 ? share.content.substring(0, 100) + '...' : share.content; html += \`<div class="share-item" onclick="viewShare('\${share.shareUrl}')"> <div class="share-item-info"> <div class="share-item-title">\${share.title}</div> <div class="share-item-preview">\${preview}</div> <div class="share-item-date">\${new Date(share.date).toLocaleString('zh-CN')}</div> </div> <div class="share-item-actions"> <a href="\${share.shareUrl}" class="view-btn" onclick="event.stopPropagation()">查看</a> </div> </div>\`; }); html += \`</div></div>\`; }); shareList.innerHTML = html; } // 查看分享日记 function viewShare(shareUrl) { window.open(shareUrl, '_blank'); } // 显示错误 function showError(message) { const shareList = document.getElementById('shareList'); shareList.innerHTML = \`<div class="error">\${message}</div>\`; } // 更新分页控件 function updatePagination(pagination) { const paginationDiv = document.getElementById('pagination'); const paginationInfo = document.getElementById('paginationInfo'); const pageNumbers = document.getElementById('pageNumbers'); const prevBtn = document.getElementById('prevBtn'); const nextBtn = document.getElementById('nextBtn'); currentPage = pagination.currentPage; totalPages = pagination.totalPages; totalCount = pagination.totalCount; // 更新分页信息 const startItem = (currentPage - 1) * pageSize + 1; const endItem = Math.min(currentPage * pageSize, totalCount); paginationInfo.textContent = \`显示 \${startItem}-\${endItem} 条,共 \${totalCount} 条记录\`; // 更新按钮状态 prevBtn.disabled = !pagination.hasPrev; nextBtn.disabled = !pagination.hasNext; // 生成页码按钮 let pageHtml = ''; const maxVisiblePages = 5; let startPage = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2)); let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1); if (endPage - startPage + 1 < maxVisiblePages) { startPage = Math.max(1, endPage - maxVisiblePages + 1); } if (startPage > 1) { pageHtml += \`<button class="pagination-btn" onclick="changePage(1)">1</button>\`; if (startPage > 2) { pageHtml += \`<span style="padding: 0.5rem;">...</span>\`; } } for (let i = startPage; i <= endPage; i++) { if (i === currentPage) { pageHtml += \`<button class="pagination-current">\${i}</button>\`; } else { pageHtml += \`<button class="pagination-btn" onclick="changePage(\${i})">\${i}</button>\`; } } if (endPage < totalPages) { if (endPage < totalPages - 1) { pageHtml += \`<span style="padding: 0.5rem;">...</span>\`; } pageHtml += \`<button class="pagination-btn" onclick="changePage(\${totalPages})">\${totalPages}</button>\`; } pageNumbers.innerHTML = pageHtml; // 显示分页控件 if (totalPages > 1) { paginationDiv.style.display = 'flex'; } else { paginationDiv.style.display = 'none'; } } // 切换页面 function changePage(page) { if (page >= 1 && page <= totalPages && page !== currentPage) { loadAllShares(page); // 滚动到顶部 window.scrollTo({ top: 0, behavior: 'smooth' }); } } // 跳转到指定页面 function jumpToPage() { const jumpInput = document.getElementById('jumpInput'); const page = parseInt(jumpInput.value); if (page >= 1 && page <= totalPages) { changePage(page); jumpInput.value = ''; } else { alert(\`请输入 1 到 \${totalPages} 之间的页码\`); } } // 页面加载时加载所有分享日记 loadAllShares(getCurrentPageFromUrl()); </script> </body> </html>`; } // 分享页面 function getSharePage(shareId) { return ` <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>WS01 Note - 分享内容</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f8f9fa; min-height: 100vh; } .sites { width: 1000px; background: #F2F2F2; border: 1px solid rgba(0,0,0,.06); margin: 10px auto; padding: 0px; color: white; border-radius: 6px; } .header { background: #D4D4D4; padding: 0.8rem 1.5rem; box-shadow: 0 1px 6px rgba(0,0,0,0.08); display: flex; justify-content: center; align-items: center; position: relative; } .logo { font-size: 1.5rem; font-weight: 400; color: #333; } .back-btn { background: #1C86EE; color: white; border: none; padding: 0.4rem 0.8rem; border-radius: 4px; cursor: pointer; font-size: 0.8rem; position: absolute; right: 2.6rem; text-decoration: none; display: inline-block; } .back-btn:hover { background: #1874CD; } .container { max-width: 960px; margin: 1.5rem auto; padding: 0 1.5rem; } .diary-detail { background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); max-height: 900px; overflow-y: auto; } .diary-header { border-bottom: 1px solid #e9ecef; padding-bottom: 0.8rem; margin-bottom: 1.2rem; } .diary-title { font-size: 1.5rem; color: #333; margin-bottom: 0.4rem; font-weight: 600; word-wrap: break-word; } .diary-date { color: #666; font-size: 0.8rem; } .diary-content { color: #333; line-height: 1.6; white-space: pre-wrap; font-size: 0.9rem; word-wrap: break-word; overflow-wrap: break-word; word-break: break-all; border: 1px dashed #ccc; padding: 0.8rem; border-radius: 4px; } .loading { text-align: center; padding: 2rem; color: #666; } .error { text-align: center; padding: 2rem; color: #e74c3c; } .share-notice { background: #e7f3ff; border: 1px solid #b3d9ff; border-radius: 4px; padding: 0.8rem; margin-bottom: 1rem; color: #0066cc; font-size: 0.9rem; } .notification { position: fixed; top: 15px; right: 15px; background: #28a745; color: white; padding: 0.8rem; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.12); display: none; z-index: 1000; font-size: 0.9rem; max-width: 300px; } .footer { font-size: 14px; color: #292929; margin: 15px auto; text-align: center; } /* 响应式设计 */ @media (max-width: 768px) { .sites { width: 95%; margin: 5px auto; } .container { margin: 1rem auto; padding: 0 1rem; } .diary-detail { padding: 1rem; } } </style> </head> <body> <div class="sites"> <div class="header"> <div class="logo">WS01 Note - 分享内容</div> <a href="/share" class="back-btn">← 返回分享目录</a> </div> <div class="container"> <div class="share-notice"> 📢 这是一篇分享的文章,任何人都可以查看 </div> <div id="diaryDetail" class="diary-detail"> <div class="loading">加载中...</div> </div> <!-- 通知元素 --> <div id="notification" class="notification">内容已复制到剪贴板</div> </div> <div class="footer"> <span id="timeDate">载入天数...</span> <script language="javascript"> var now = new Date(); function createtime(){ var grt= new Date("10/12/2025 00:00:00");/*---这里是网站的启用时间:月日年--*/ now.setTime(now.getTime()+250); days = (now - grt ) / 1000 / 60 / 60 / 24; dnum = Math.floor(days); document.getElementById("timeDate").innerHTML = "稳定运行"+dnum+"天"; } setInterval("createtime()",250); </script> <span <p> | 本页总访问量 <span id="busuanzi_site_pv"></span> 次 | <a href="/" target="_blank">登录</p></span> <script defer src="https://bsz.211119.xyz/js"></script> <script> const shareId = '${shareId}'; // 加载分享日记 async function loadSharedDiary() { try { const response = await fetch(\`/api/share/\${shareId}\`); if (response.ok) { const diary = await response.json(); displaySharedDiary(diary); } else if (response.status === 404) { showError('分享的日记不存在或已被删除'); } else { showError('加载失败,请稍后重试'); } } catch (error) { showError('网络错误,请重试'); } } // 显示分享日记 function displaySharedDiary(diary) { const diaryDetail = document.getElementById('diaryDetail'); const date = new Date(diary.date).toLocaleString('zh-CN'); diaryDetail.innerHTML = \` <div class="diary-header"> <div class="title-container"> <h1 class="diary-title">\${diary.title}</h1> <button class="copy-btn" onclick="copyShareContent()">复制内容</button> </div> <div class="diary-date">\${date}</div> </div> <div class="diary-content" id="shareContent"></div> \`; // 使用textContent设置内容,避免HTML标签被解析 document.getElementById('shareContent').textContent = diary.content; // 将原始内容存储到全局变量中,供复制功能使用 window.shareContent = diary.content; } // 显示错误 function showError(message) { const diaryDetail = document.getElementById('diaryDetail'); diaryDetail.innerHTML = \`<div class="error">\${message}</div>\`; } // 复制内容功能 function copyContent(content) { navigator.clipboard.writeText(content).then(() => { const notification = document.getElementById('notification'); notification.style.display = 'block'; setTimeout(() => { notification.style.display = 'none'; }, 3000); }).catch(err => { console.error('复制失败:', err); const notification = document.getElementById('notification'); notification.textContent = '复制失败'; notification.style.backgroundColor = '#dc3545'; notification.style.display = 'block'; setTimeout(() => { notification.style.display = 'none'; // 恢复默认文本和颜色 notification.textContent = '内容已复制到剪贴板'; notification.style.backgroundColor = '#28a745'; }, 3000); }); } // 复制分享内容功能 function copyShareContent() { if (window.shareContent) { copyContent(window.shareContent); } else { // 如果全局变量不存在,尝试从DOM元素获取 const contentElement = document.getElementById('shareContent'); if (contentElement) { copyContent(contentElement.textContent); } else { console.error('无法获取分享内容'); const notification = document.getElementById('notification'); notification.textContent = '复制失败:无法获取内容'; notification.style.backgroundColor = '#dc3545'; notification.style.display = 'block'; setTimeout(() => { notification.style.display = 'none'; }, 3000); } } } // 页面加载时加载分享日记 loadSharedDiary(); </script> </body> </html>`; } // 处理认证API async function handleAuth(request, env, corsHeaders) { if (request.method !== 'POST') { return new Response('方法不允许', { status: 405, headers: { 'Content-Type': 'text/plain; charset=utf-8', ...corsHeaders } }); } try { const { username, password } = await request.json(); // 验证用户名和密码(从环境变量获取) const validUsername = env.USERNAME || '9527a'; const validPassword = env.PASSWORD || '9527abc'; if (username === validUsername && password === validPassword) { // 生成简单的token(实际应用中应该使用更安全的方法) const token = btoa(username + ':' + Date.now()); return new Response(JSON.stringify({ success: true, token: token, message: '登录成功' }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } else { return new Response(JSON.stringify({ success: false, message: '用户名或密码错误' }), { status: 401, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } catch (error) { return new Response(JSON.stringify({ success: false, message: '请求格式错误' }), { status: 400, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 处理日记API async function handleDiaryAPI(request, env, corsHeaders) { // 验证token const authHeader = request.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return new Response(JSON.stringify({ success: false, message: '未授权' }), { status: 401, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } const token = authHeader.substring(7); try { if (request.method === 'GET') { // 获取日记列表 const diaries = await getDiaries(env); return new Response(JSON.stringify({ success: true, diaries: diaries }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } if (request.method === 'POST') { // 保存新日记 const { title, content, editShareId } = await request.json(); const diary = { id: Date.now().toString(), title: title, content: content, date: new Date().toISOString() }; await saveDiary(env, diary); // 如果是从分享日记编辑而来,需要从分享日记中删除 if (editShareId) { await env.WS01_NOTE_KV.delete(`shared_${editShareId}`); } return new Response(JSON.stringify({ success: true, message: '日记保存成功' }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } // 添加: 处理更新日记 (PUT) if (request.method === 'PUT') { const { id, title, content } = await request.json(); // 获取现有日记 const diaries = await getDiaries(env); const diaryIndex = diaries.findIndex(d => d.id === id); if (diaryIndex === -1) { return new Response(JSON.stringify({ success: false, message: '日记不存在' }), { status: 404, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } // 更新日记内容 diaries[diaryIndex] = { ...diaries[diaryIndex], title, content, date: new Date().toISOString() // 更新时间 }; await env.WS01_NOTE_KV.put('diaries', JSON.stringify(diaries)); return new Response(JSON.stringify({ success: true, message: '日记更新成功' }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } // 添加: 处理删除日记 (DELETE) if (request.method === 'DELETE') { const { id } = await request.json(); const diaries = await getDiaries(env); const filteredDiaries = diaries.filter(d => d.id !== id); if (filteredDiaries.length === diaries.length) { return new Response(JSON.stringify({ success: false, message: '日记不存在' }), { status: 404, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } await env.WS01_NOTE_KV.put('diaries', JSON.stringify(filteredDiaries)); return new Response(JSON.stringify({ success: true, message: '日记删除成功' }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } return new Response('方法不允许', { status: 405, headers: { 'Content-Type': 'text/plain; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('日记API错误:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 获取日记列表 async function getDiaries(env) { try { const diariesJson = await env.WS01_NOTE_KV.get('diaries'); if (diariesJson) { const diaries = JSON.parse(diariesJson); return diaries.sort((a, b) => new Date(b.date) - new Date(a.date)); } return []; } catch (error) { console.error('获取日记失败:', error); return []; } } // 保存日记 async function saveDiary(env, diary) { try { const diaries = await getDiaries(env); diaries.unshift(diary); // 添加到开头 // 限制最多保存100篇日记 if (diaries.length > 100) { diaries.splice(100); } await env.WS01_NOTE_KV.put('diaries', JSON.stringify(diaries)); } catch (error) { console.error('保存日记失败:', error); throw error; } } // 处理创建备份 async function handleCreateBackup(request, env, corsHeaders) { // 验证token const authHeader = request.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return new Response(JSON.stringify({ success: false, message: '未授权' }), { status: 401, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } try { // 获取当前所有日记 const diaries = await getDiaries(env); // 获取所有分享日记 const listResult = await env.WS01_NOTE_KV.list({ prefix: 'shared_' }); const shares = []; for (const key of listResult.keys) { try { const sharedDiaryJson = await env.WS01_NOTE_KV.get(key.name); if (sharedDiaryJson) { const sharedDiary = JSON.parse(sharedDiaryJson); shares.push(sharedDiary); } } catch (e) { console.error('读取分享日记失败:', e); } } // 创建备份数据 const backup = { id: Date.now().toString(), timestamp: new Date().toISOString(), data: diaries, shares: shares, count: diaries.length, shareCount: shares.length }; // 获取现有的备份列表 let backups = []; try { const backupsJson = await env.WS01_NOTE_KV.get('backups'); if (backupsJson) { backups = JSON.parse(backupsJson); } } catch (e) { console.error('读取备份列表失败:', e); } // 添加新备份到列表开头 backups.unshift(backup); // 限制最多5个备份 if (backups.length > 5) { backups = backups.slice(0, 5); } // 保存备份列表 await env.WS01_NOTE_KV.put('backups', JSON.stringify(backups)); return new Response(JSON.stringify({ success: true, message: '备份创建成功', backupId: backup.id }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('创建备份失败:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 处理获取备份列表 async function handleGetBackups(request, env, corsHeaders) { // 验证token const authHeader = request.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return new Response(JSON.stringify({ success: false, message: '未授权' }), { status: 401, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } try { // 获取备份列表 let backups = []; try { const backupsJson = await env.WS01_NOTE_KV.get('backups'); if (backupsJson) { backups = JSON.parse(backupsJson); } } catch (e) { console.error('读取备份列表失败:', e); } // 只返回必要的信息 const backupInfo = backups.map(backup => ({ id: backup.id, timestamp: backup.timestamp, count: backup.count, shareCount: backup.shareCount || 0 })); return new Response(JSON.stringify({ success: true, backups: backupInfo }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('获取备份列表失败:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 处理恢复备份 async function handleRestoreBackup(request, env, corsHeaders) { // 验证token const authHeader = request.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return new Response(JSON.stringify({ success: false, message: '未授权' }), { status: 401, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } try { const { backupId } = await request.json(); // 获取备份列表 let backups = []; try { const backupsJson = await env.WS01_NOTE_KV.get('backups'); if (backupsJson) { backups = JSON.parse(backupsJson); } } catch (e) { console.error('读取备份列表失败:', e); } // 查找指定的备份 const backup = backups.find(b => b.id === backupId); if (!backup) { return new Response(JSON.stringify({ success: false, message: '备份不存在' }), { status: 404, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } // 恢复个人日记数据 await env.WS01_NOTE_KV.put('diaries', JSON.stringify(backup.data)); // 恢复分享日记数据 if (backup.shares && backup.shares.length > 0) { // 先删除所有现有的分享日记 const existingShares = await env.WS01_NOTE_KV.list({ prefix: 'shared_' }); for (const key of existingShares.keys) { await env.WS01_NOTE_KV.delete(key.name); } // 恢复备份中的分享日记 for (const share of backup.shares) { await env.WS01_NOTE_KV.put(`shared_${share.id}`, JSON.stringify(share)); } } return new Response(JSON.stringify({ success: true, message: '数据恢复成功' }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('恢复备份失败:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 处理分享日记 async function handleShareDiary(request, env, corsHeaders) { // 验证token const authHeader = request.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return new Response(JSON.stringify({ success: false, message: '未授权' }), { status: 401, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } try { const { title, content, shareId, editDiaryId } = await request.json(); if (request.method === 'PUT' && shareId) { // 更新分享日记 const existingShareJson = await env.WS01_NOTE_KV.get(`shared_${shareId}`); if (!existingShareJson) { return new Response(JSON.stringify({ success: false, message: '分享日记不存在' }), { status: 404, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } const existingShare = JSON.parse(existingShareJson); const updatedShare = { ...existingShare, title: title, content: content, date: new Date().toISOString() // 更新修改时间 }; await env.WS01_NOTE_KV.put(`shared_${shareId}`, JSON.stringify(updatedShare)); return new Response(JSON.stringify({ success: true, message: '分享日记更新成功', shareId: shareId }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } else { // 创建新分享日记 const newShareId = Date.now().toString() + Math.random().toString(36).substr(2, 9); const sharedDiary = { id: newShareId, title: title, content: content, date: new Date().toISOString(), shared: true }; // 保存到KV存储 await env.WS01_NOTE_KV.put(`shared_${newShareId}`, JSON.stringify(sharedDiary)); // 如果是从我的日记编辑而来,需要从我的日记中删除 if (editDiaryId) { const diaries = await getDiaries(env); const filteredDiaries = diaries.filter(d => d.id !== editDiaryId); await env.WS01_NOTE_KV.put('diaries', JSON.stringify(filteredDiaries)); } return new Response(JSON.stringify({ success: true, message: '分享成功', shareId: newShareId }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } catch (error) { console.error('分享日记失败:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 处理获取分享日记 async function handleGetSharedDiary(shareId, env, corsHeaders) { try { // 从KV存储中获取分享日记 const sharedDiaryJson = await env.WS01_NOTE_KV.get(`shared_${shareId}`); if (!sharedDiaryJson) { return new Response(JSON.stringify({ success: false, message: '分享的日记不存在' }), { status: 404, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } const sharedDiary = JSON.parse(sharedDiaryJson); return new Response(JSON.stringify(sharedDiary), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('获取分享日记失败:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 处理获取所有分享日记(公开访问) async function handleGetAllShares(request, env, corsHeaders) { try { // 解析URL参数 const url = new URL(request.url); const page = parseInt(url.searchParams.get('page')) || 1; const limit = parseInt(url.searchParams.get('limit')) || 20; // 获取所有分享日记的键 const listResult = await env.WS01_NOTE_KV.list({ prefix: 'shared_' }); const shares = []; for (const key of listResult.keys) { try { const sharedDiaryJson = await env.WS01_NOTE_KV.get(key.name); if (sharedDiaryJson) { const sharedDiary = JSON.parse(sharedDiaryJson); shares.push({ id: sharedDiary.id, title: sharedDiary.title, content: sharedDiary.content, date: sharedDiary.date, shareUrl: `/share/${sharedDiary.id}` }); } } catch (e) { console.error('解析分享日记失败:', e); } } // 按日期排序(最新的在前) shares.sort((a, b) => new Date(b.date) - new Date(a.date)); // 计算分页 const totalCount = shares.length; const totalPages = Math.ceil(totalCount / limit); const startIndex = (page - 1) * limit; const endIndex = startIndex + limit; const paginatedShares = shares.slice(startIndex, endIndex); return new Response(JSON.stringify({ success: true, shares: paginatedShares, pagination: { currentPage: page, totalPages: totalPages, totalCount: totalCount, limit: limit, hasNext: page < totalPages, hasPrev: page > 1 } }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('获取所有分享日记失败:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 处理获取用户分享日记列表 async function handleGetUserShares(request, env, corsHeaders) { // 验证token const authHeader = request.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return new Response(JSON.stringify({ success: false, message: '未授权' }), { status: 401, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } try { // 获取所有分享日记的键 const listResult = await env.WS01_NOTE_KV.list({ prefix: 'shared_' }); const shares = []; for (const key of listResult.keys) { try { const sharedDiaryJson = await env.WS01_NOTE_KV.get(key.name); if (sharedDiaryJson) { const sharedDiary = JSON.parse(sharedDiaryJson); shares.push({ id: sharedDiary.id, title: sharedDiary.title, content: sharedDiary.content, date: sharedDiary.date, shareUrl: `${request.url.split('/')[0]}//${request.headers.get('host')}/share/${sharedDiary.id}` }); } } catch (e) { console.error('解析分享日记失败:', e); } } // 按日期排序(最新的在前) shares.sort((a, b) => new Date(b.date) - new Date(a.date)); return new Response(JSON.stringify({ success: true, shares: shares }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('获取分享日记列表失败:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 处理删除分享日记 async function handleDeleteShare(request, env, corsHeaders) { // 验证token const authHeader = request.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return new Response(JSON.stringify({ success: false, message: '未授权' }), { status: 401, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } try { const { shareId } = await request.json(); // 删除分享日记 await env.WS01_NOTE_KV.delete(`shared_${shareId}`); return new Response(JSON.stringify({ success: true, message: '分享日记删除成功' }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('删除分享日记失败:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 处理删除备份 async function handleDeleteBackup(request, env, corsHeaders) { // 验证token const authHeader = request.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return new Response(JSON.stringify({ success: false, message: '未授权' }), { status: 401, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } try { const { backupId } = await request.json(); // 获取备份列表 let backups = []; try { const backupsJson = await env.WS01_NOTE_KV.get('backups'); if (backupsJson) { backups = JSON.parse(backupsJson); } } catch (e) { console.error('读取备份列表失败:', e); } // 过滤掉要删除的备份 const filteredBackups = backups.filter(b => b.id !== backupId); if (filteredBackups.length === backups.length) { return new Response(JSON.stringify({ success: false, message: '备份不存在' }), { status: 404, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } // 保存更新后的备份列表 await env.WS01_NOTE_KV.put('backups', JSON.stringify(filteredBackups)); return new Response(JSON.stringify({ success: true, message: '备份删除成功' }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('删除备份失败:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } }
2025年10月19日
3 阅读
0 评论
0 点赞
2025-08-22
★★★在Debian 11、12系统,用docker搭建Caddy + PHP动、静态网站
在Debian 11、12系统,用docker搭建Caddy + PHP动、静态网站文件夹 home/html/web/web1 一、常规安装,一个网站,不用数据库【推荐用第二或第三个方式 多站点 方式安装,便于以后在vps上搭建更多网站】,方法可行。 目录结构 (建议)/home/html/web/web1/├── docker-compose.yml├── Caddyfile└── public/ # 网站文件 (index.php, index.html等)1、更新系统sudo apt update && sudo apt upgrade -y2、安装 Docker 和 Docker Composesudo apt install -y docker.io docker-compose3、创建网站目录a、灵活安装sudo mkdir -p /home/html/web/web1 cd /home/html/web/web1或 b、简单安装【推荐】创建目录结构# 创建目录结构 sudo mkdir -p /home/html/web/web1/public创建 Caddyfile,解析好域名的,:80可替换域名example.com# 创建 Caddyfile cat << 'EOF' | sudo tee /home/html/web/web1/Caddyfile :80 { root * /srv php_fastcgi php:9000 file_server } EOF创建测试 PHP 文件# 创建测试 PHP 文件 cat << 'EOF' | sudo tee /home/html/web/web1/public/index.php <?php phpinfo(); EOF4、编写 docker-compose.ymlversion: "3.8" services: caddy: image: caddy:latest container_name: caddy_web1 restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile - ./public:/srv - caddy_data:/data - caddy_config:/config depends_on: - php php: image: php:8.2-fpm container_name: php_web1 restart: unless-stopped volumes: - ./public:/srv volumes: caddy_data: caddy_config:5、编写 Caddyfile,如果第三步中已安装好域名的,此步省略example.com { root * /srv php_fastcgi php:9000 file_server }6、放置测试文件,如果第三步中已放置测试文件,此步省略echo "<?php phpinfo(); ?>" > /home/html/web/web1/public/index.php7、启动服务cd /home/html/web/web1 sudo docker-compose up -d查看容器状态:sudo docker ps8、访问测试打开浏览器访问 http://服务器IP/如果绑定了域名(并改了 Caddyfile),访问 https://你的域名/二、多站点 Caddyfile 模板【不用数据库】,以三个web1、web2、web3为例,更多个网站类推,方法可行目录结构建议 /home/html/web/├── web1/│ ├── public/ # 网站1的文件│ ├── Caddyfile.web1 # 网站1配置├── web2/│ ├── public/ # 网站2的文件│ ├── Caddyfile.web2 # 网站2配置├── web3/│ ├── public/ # 网站3的文件│ ├── Caddyfile.web3 # 网站3配置└── docker-compose.yml # 统一的 compose└── Caddyfile # 主配置,引用各站点1、更新系统sudo apt update && sudo apt upgrade -y2、安装 Docker 和 Docker Composesudo apt install -y docker.io docker-compose3、创建三个目录结构并给予权限【 更多网站类似添加 】网站1sudo mkdir -p /home/html/web/web1/public sudo chmod -R 755 /home/html/web/web1/public 网站2sudo mkdir -p /home/html/web/web2/public sudo chmod -R 755 /home/html/web/web2/public网站3sudo mkdir -p /home/html/web/web3/public sudo chmod -R 755 /home/html/web/web3/public4、在文件夹 /home/html/web/ 创建统一的 docker-compose.yml【 更多网站类似添加 】version: "3.8" services: caddy: image: caddy:latest container_name: caddy_main restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile - ./web1/public:/srv/web1 - ./web2/public:/srv/web2 - ./web3/public:/srv/web3 - caddy_data:/data - caddy_config:/config depends_on: - php php: image: php:8.2-fpm container_name: php_main restart: unless-stopped volumes: - ./web1/public:/srv/web1 - ./web2/public:/srv/web2 - ./web3/public:/srv/web3 volumes: caddy_data: caddy_config:5、总控 Caddyfile【 更多网站类似添加 】# Web1 - 用域名 web1.example.com { root * /srv/web1 php_fastcgi php:9000 file_server } # Web2 - 用域名 web2.example.com { root * /srv/web2 php_fastcgi php:9000 file_server } # Web3 - 用 IP + 端口(测试用) :8080 { root * /srv/web3 php_fastcgi php:9000 file_server }6、启动cd /home/html/web sudo docker-compose up -d7、快速测试文件【 更多网站类似添加,个别vps可能添加不上,用上传方式 】echo "<?php echo 'Hello Web1!'; ?>" | sudo tee /home/html/web/web1/public/index.phpecho "<?php echo 'Hello Web2!'; ?>" | sudo tee /home/html/web/web2/public/index.phpecho "<?php echo 'Hello Web3!'; ?>" | sudo tee /home/html/web/web3/public/index.php三、如果 需要数据库 ,在方式二的基础上修改, docker-compose.yml 添加 db 项,web主目录下新建 php 文件夹,php 文件夹添加 Dockerfile 文件, 注意修改数据库相关密码 目录结构/home/html/web/├── web1/│ └── public/ # 放 Typecho├── web2/│ └── public/├── web3/│ └── public/├── docker-compose.yml├── php/│ └── Dockerfile└── Caddyfile1、统一的 docker-compose.yml 【 注意修改数据库相关密码 】version: "3.8" services: caddy: image: caddy:latest container_name: caddy_main restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile - ./web1/public:/srv/web1 - ./web2/public:/srv/web2 - ./web3/public:/srv/web3 - caddy_data:/data - caddy_config:/config depends_on: - php php: build: ./php image: php:8.2-fpm container_name: php_main restart: unless-stopped volumes: - ./web1/public:/srv/web1 - ./web2/public:/srv/web2 - ./web3/public:/srv/web3 depends_on: - db db: image: mariadb:10.7 container_name: mariadb_web1 restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: rootpass123 MYSQL_DATABASE: typecho MYSQL_USER: typecho MYSQL_PASSWORD: typechopass volumes: - db_data:/var/lib/mysql volumes: caddy_data: caddy_config: db_data:2、php/DockerfileFROM php:8.2-fpm # 安装 MySQL 扩展 RUN docker-php-ext-install pdo_mysql mysqli # 可选:安装 gd、zip 等(上传图片/压缩包可能用到) RUN apt-get update && apt-get install -y libjpeg-dev libpng-dev libzip-dev \ && docker-php-ext-install gd zip WORKDIR /srv 访问 http://web1.example.com,进入安装向导。数据库地址: db 数据库名: typecho 用户: typecho 密码: typechopass 2、安装 Typecho 命令,也可以下载修改好后上传cd /home/html/web/web1/public wget https://github.com/typecho/typecho/archive/refs/tags/v1.2.1.zip -O typecho-1.2.1.zip unzip typecho-1.2.1.zip mv typecho-1.2.1/* ./ rm -rf typecho-1.2.1 typecho-1.2.1.zip 3、启动cd /home/html/web sudo docker-compose up -d四、如果搭建网站中需要两个数据库【三个数据库或更多数据库类推】,例如,安装两个Typecho1、一个数据库服务,多个数据库docker-compose.yml(推荐方案)version: "3.8" services: caddy: image: caddy:latest container_name: caddy_main restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile - ./web1/public:/srv/web1 - ./web2/public:/srv/web2 - ./web3/public:/srv/web3 - caddy_data:/data - caddy_config:/config depends_on: - php php: image: php:8.2-fpm container_name: php_main restart: unless-stopped volumes: - ./web1/public:/srv/web1 - ./web2/public:/srv/web2 - ./web3/public:/srv/web3 depends_on: - db db: image: mariadb:10.6 container_name: mariadb_main restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: rootpass123 volumes: - db_data:/var/lib/mysql volumes: caddy_data: caddy_config: db_data:2、初始化两个数据库启动容器后进入 MariaDB:docker exec -it mariadb_main mysql -uroot -p输入 rootpass123 后,创建两个数据库和用户:CREATE DATABASE typecho1; CREATE DATABASE typecho2; CREATE USER 'typecho1'@'%' IDENTIFIED BY 'pass1'; CREATE USER 'typecho2'@'%' IDENTIFIED BY 'pass2'; GRANT ALL PRIVILEGES ON typecho1.* TO 'typecho1'@'%'; GRANT ALL PRIVILEGES ON typecho2.* TO 'typecho2'@'%'; FLUSH PRIVILEGES;然后【实际安装时用 Pdo驱动SQLite适配器安装, SQLite原生函数适配器安装】:web1 Typecho 安装时填:数据库名 typecho1,用户 typecho1,密码 pass1web2 Typecho 安装时填:数据库名 typecho2,用户 typecho2,密码 pass2五、最小化的 Typecho Dockerfile + docker-compose.yml 示例,放到 /home/html/web/web1/ 下就能直接跑【可以,本方法安装步骤 专门链接 】目录结构(推荐)/home/html/web/web1/├── docker-compose.yml├── Caddyfile├── php/│ └── Dockerfile└── data/ (安装后数据库文件会自动生成)1、docker-compose.ymlversion: '3.9' services: caddy: image: caddy:2-alpine container_name: caddy_web1 ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile - ./site:/srv - caddy_data:/data - caddy_config:/config depends_on: - php php: build: ./php container_name: php_web1 volumes: - ./site:/srv depends_on: - db db: image: mariadb:10.7 container_name: mariadb_web1 restart: always environment: MYSQL_ROOT_PASSWORD: rootpass MYSQL_DATABASE: typecho1 MYSQL_USER: typecho MYSQL_PASSWORD: typechopass volumes: - ./data:/var/lib/mysql volumes: caddy_data: caddy_config:2、Caddyfile文件:80 { root * /srv php_fastcgi php:9000 file_server }或# Web1 - 用域名 web1.example.com { root * /srv/web1 php_fastcgi php:9000 file_server }3、php/DockerfileFROM php:8.1-fpm RUN docker-php-ext-install pdo pdo_mysql mysqli WORKDIR /srv4、下载 Typecho,不是在web1文件夹,是在site文件夹cd /home/html/web/web1 mkdir site cd site wget https://github.com/typecho/typecho/releases/download/v1.2.1/typecho.zip unzip typecho.zip rm typecho.zip5、启动cd /home/html/web/web1 docker-compose up -d然后访问服务器 http://你的IP/或域名 就能进入 Typecho 安装页面【安装时uplads文件夹没权限,单独设置一下,config.inc.php文件也可能没创建权限,手动创建一个就行】。数据库信息填写:数据库地址:db数据库名:typecho1用户名:typecho密码:typechopass⚡ 如果你要再装第二个 Typecho(web2),只需要复制一份 /home/html/web/web1/ 为 /home/html/web/web2/,然后在 docker-compose.yml 里修改:容器名(caddy_web2、php_web2、mariadb_web2)数据库名(typecho2)端口映射(比如 8080:80)六、多个网站web1、web2 → 各自安装一个 Typecho(需要数据库)web3、web4 → 普通动态 PHP 网站(不需要数据库)全部共用同一个 Caddy 容器,不同的域名 / 目录对应不同的网站网站目录结构(在 /home/html/web/ 下)web1/ ← Typecho 站点 1web2/ ← Typecho 站点 2web3/ ← 动态 PHP 站点web4/ ← 动态 PHP 站点1、在 /home/html/ 下创建目录:mkdir -p /home/html/{web1,web2,web3,web4,db1_data,db2_data,caddy_data,caddy_config} 2、把 docker-compose.yml 和 Caddyfile 放在 /home/html/3、下载 Typecho 程序包到 web1/ 和 web2/ 目录,解压4、完整的 docker-compose.yml 示例services: caddy: image: caddy:2 container_name: caddy restart: always ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile - ./caddy_data:/data - ./caddy_config:/config - ./web1:/srv/web1 - ./web2:/srv/web2 - ./web3:/srv/web3 - ./web4:/srv/web4 depends_on: - php php: image: php:8.2-fpm container_name: php-fpm restart: always volumes: - ./web1:/srv/web1 - ./web2:/srv/web2 - ./web3:/srv/web3 - ./web4:/srv/web4 working_dir: /srv command: php-fpm # Typecho 数据库 1 db1: image: mariadb:10.6 container_name: db1 restart: always environment: MYSQL_ROOT_PASSWORD: rootpass MYSQL_DATABASE: typecho1 MYSQL_USER: user1 MYSQL_PASSWORD: pass1 volumes: - ./db1_data:/var/lib/mysql # Typecho 数据库 2 db2: image: mariadb:10.6 container_name: db2 restart: always environment: MYSQL_ROOT_PASSWORD: rootpass MYSQL_DATABASE: typecho2 MYSQL_USER: user2 MYSQL_PASSWORD: pass2 volumes: - ./db2_data:/var/lib/mysql 5、Caddyfile放在 /home/html/Caddyfile,不同网站对应不同文件夹:# web1 - Typecho 1 web1.example.com { root * /srv/web1 php_fastcgi php:9000 file_server } # web2 - Typecho 2 web2.example.com { root * /srv/web2 php_fastcgi php:9000 file_server } # web3 - 普通 PHP 动态站 web3.example.com { root * /srv/web3 php_fastcgi php:9000 file_server } # web4 - 普通 PHP 动态站 web4.example.com { root * /srv/web4 php_fastcgi php:9000 file_server } 6、启动服务:cd /home/html docker-compose up -d7、浏览器访问:http://web1.example.com → 使用数据库 db1,连接参数:数据库主机: db1用户名: user1密码: pass1数据库: typecho1http://web2.example.com → 使用数据库 db2,连接参数:数据库主机: db2用户名: user2密码: pass2数据库: typecho2
2025年08月22日
5 阅读
0 评论
0 点赞
2024-12-22
用Cloudflare轻松搭建Uptime-Flare监控服务!实时掌握网站状况!
用Cloudflare轻松搭建Uptime-Flare监控服务!实时掌握网站状况!本文转载自:https://am.809098.xyz/am-uptime-flare/Uptime-Flare是一个监控工具,能够帮助你监控网站的可用性和性能。通过Cloudflare你可以轻松部署这个服务,免费监控你的网站,告别宕机烦恼! 原项目地址: 其它项目地址 一、需要准备的前提资料创建Cloudflare TOKEN 用于github部署用创建链接:https://dash.cloudflare.com/profile/api-tokens点击创建令牌,选择 编辑 Cloudflare Workers 模板,然后将 帐户资源 设置为自己的账户。 区域资源 设置为 所有区域二、部署uptime-flare1、打开 原项目地址: 其它项目地址 点击项目首页的 use this template ,然后点击 create new repo 完成项目创建2、设置 SECRET点击 settings -> secrets and variables -> new repo secret② Name 的值是 CLOUDFLARE_API_TOKEN③ Secret 的值是在CF获得的 Token 值3、修改 uptime.config.ts 文件修改 PageConfig 配置里的Links数组,这是监控首页的站点显示信,不是监控的站点,如links: [ { link: 'https://am.809098.xyz', label: '个人博客' }, { link: 'https://809098.xyz', label: 'Blog', highlight: true }, ]修改 WorkConfig 配置,里面是要监控的站点monitors: [ { id: 'am.809098.xyz', name: '个人博客', method: 'GET', target: 'https://am.809098.xyz', tooltip: 'My production server monitor', statusPageLink: 'https://am.809098.xyz', timeout: 10000, }, ], 3、在github的actions看部署成功,就完成了部署
2024年12月22日
6 阅读
0 评论
0 点赞
2024-07-19
永久免费节点搭建!通过Cloudflare Worker部署免费的VLESS节点,4K高速,解锁Netflix、ChatGPT
永久免费节点搭建!通过Cloudflare Worker部署免费的VLESS节点,4K高速,解锁Netflix、ChatGPT本文转载自: 科技小露 本文是通过 zizifn 大佬的一个开源项目 edgetunnel ,使得我们可以免费的在 Cloudflare 上面通过部署 Worker ,来创建一个免费 VLESS 节点!一、准备工作注册 Cloudflare 账号并登录二、CloudFlare 部署免费节点原作者 GitHub 源项目地址 Worker 部署 VLESS1、来到 Cloudflare 首页,点击 Workers 和 Pages ,创建 Work ,自定义名称,然后部署!而后,编辑代码,清除原先代码,填入如下代码: Workers代码 2、在线生成一个 UUID ,用于替换下面代码中第七行的 UUID。(或是用 V2rayN 生成一个)3、因为原项目里面的代码“ proxy IP ”为空,关于 proxy IP ,是用于转发CF的一些流量,所以,若是存在套了CF的一些网站无法打开,请更换其中的其他网址,也就是第九行中的部分网址!ProxyIP 可以替换成域名或优选IPproxyip.us.fxxk.dedyn.io更多 proxyIP 列表:CM 维护proxyip.us.fxxk.dedyn.io IP落地区域: 美国 维护频率: 12小时/次 proxyip.sg.fxxk.dedyn.io IP落地区域: 新加坡 维护频率: 12小时/次 proxyip.jp.fxxk.dedyn.io IP落地区域: 日本 维护频率: 12小时/次 proxyip.hk.fxxk.dedyn.io IP落地区域: 香港 维护频率: 12小时/次 proxyip.aliyun.fxxk.dedyn.io IP落地区域: 阿里云 维护频率: 4小时/次 proxyip.oracle.fxxk.dedyn.io IP落地区域: 甲骨文 维护频率: 4小时/次 proxyip.digitalocean.fxxk.dedyn.io IP落地区域: 数码海 维护频率: 4小时/次白嫖哥维护workers.cloudflare.cyouMingyu 维护my-telegram-is-herocore.onecf.eu.org sg.ipdb.rr.nu nl.ipdb.rr.nu hk.ipdb.rr.nu jp.ipdb.rr.nu us.ipdb.rr.nu小一维护hk.cf.zhetengsha.eu.org sg.cf.zhetengsha.eu.org us.cf.zhetengsha.eu.org jp.cf.zhetengsha.eu.org代码修改完毕以后,点击右边的部署 – 保存并部署,然后点击左边的箭头,返回! 4、访问创建的vlessjd,在网站后加上“/UUID” ,即可看到节点信息,当然,里面包含了 订阅链接! 至此,节点部署完毕,复制 VLESS 的订阅链接,粘贴到 V2rayN 里面。其它使用和优选等自己找教程
2024年07月19日
6 阅读
0 评论
0 点赞
2024-06-10
部署MJJ自己的信息聚合站 —— rss-reader改版搭建~(内含详细步骤)
部署MJJ自己的信息聚合站 —— rss-reader改版搭建~(内含详细步骤)本文摘自: topang 原项目 rss-reader 在原项目(旧版)基础上改动:自定义标题和描述列表高度一点样式预览 一、搭建准备:小鸡一键安装好dockercurl -fsSL https://get.docker.com | sh && ln -s /usr/libexec/docker/cli-plugins/docker-compose /usr/local/bin二、开始:1、创建文件夹,如“rrx”mkdir rrx2、进入文件夹cd rrx/3、创建文件:config.json(“listHeight”控制列表高度,源多可以试试300){ "values": [ "https://rss.nodeseek.com/", "https://hostloc.com/forum.php?mod=rss&fid=45&auth=389ec3vtQanmEuRoghE%2FpZPWnYCPmvwWgSa7RsfjbQ%2BJpA%2F6y6eHAx%2FKqtmPOg", "https://linux.do/latest.rss", "https://www.v2ex.com/feed/vps.xml", "https://rss.agag.us.kg/telegram/channel/@vps_track_share", "https://rss.agag.us.kg/telegram/channel/wawowiki", "https://rss.agag.us.kg/telegram/channel/XiangxiuNB", "https://plink.anyfeeder.com/jingjiribao", "https://plink.anyfeeder.com/guangmingribao", "https://plink.anyfeeder.com/people-daily", "https://plink.anyfeeder.com/newscn/whxw", "https://plink.anyfeeder.com/36kr" ], "refresh": 6, "autoUpdatePush": 7, "nightStartTime": "06:30:00", "nightEndTime": "19:30:00" }4、创建文件:docker-compose.yml【现已修改的源有:原版srcrs和其它修改源topang、huhengbo1992等,各有特点】a、srcrs原版源version: "3" services: server: image: srcrs/rss-reader:latest container_name: rss-reader restart: always ports: - "8880:8080" volumes: - "$PWD/config.json:/app/config.json"b、topang修改源version: "3" services: server: image: topang/rss-reader-mix:latest container_name: rss-reader-mix restart: always ports: - "8880:8080" volumes: - "$PWD/config.json:/app/config.json"c、huhengbo1992修改源version: "3" services: server: image: huhengbo1992/rss-reader:latest container_name: rss-reader restart: always ports: - "8880:8080" volumes: - "$PWD/config.json:/app/config.json"5、启动!docker-compose up -d访问8880端口(ip:8880)6、添加反代(Nginx配置文件添加在server{...}里面) location ^~ /{ proxy_pass http://127.0.0.1:8880; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_http_version 1.1; # proxy_hide_header Upgrade; add_header X-Cache $upstream_cache_status; #Set Nginx Cache set $static_fileHXsgUAWW 0; if ( $uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$" ) { set $static_fileHXsgUAWW 1; expires 1m; } if ( $static_fileHXsgUAWW = 0 ) { add_header Cache-Control no-cache; } }其它,一些 RSS资源
2024年06月10日
6 阅读
0 评论
0 点赞