在 Hugo 中集成 Memos 多用户微博系统
摘要 本文详细阐述在 Hugo 静态网站生成器中集成 Memos 0.18.2 多用户微博系统的完整技术方案。通过 RESTful API 调用、Twikoo 评论系统集成、图片资源处理等关键技术,实现了在静态网站中展示动态微博内容的功能。本文涵盖系统架构设计、核心代码实现、性能优化策略以及部署配置指南,为开发者提供完整的实施参考。样例请见本站 说说页面 。 1. 主要流程 1.1 系统组成 前端框架: Hugo 静态网站生成器 微博服务: Memos 0.18.2 RESTful API 评论系统: Twikoo 评论服务 图片处理: ViewImage 灯箱 + Lozad 懒加载 内容渲染: Marked.js Markdown 解析器 1.2 数据流架构 Memos API → 数据获取 → 内容处理 → 评论统计 → HTML 渲染 → 前端展示2. 核心实现方案 2.1 多用户配置管理 在 Hugo 模板中定义用户配置数组: var memosMyList = [ { "creatorName": "用户A", "website": "https://example.com", "link": "https://memos.example.com", "creatorId": "1", "avatar": "/avatars/user-a.png", "twikoo": "https://twikoo.example.com" }, { "creatorName": "用户B", "website": "https://example.org", "link": "https://memos.example.com", "creatorId": "2", "avatar": "/avatars/user-b.png", "twikoo": "https://twikoo.example.com" } ]; 剩余 13 行代码 展开剩余代码 2.2 API 集成与数据处理 2.2.1 并发数据获取 async function getAllUsersMemos() { const userPromises = currentUsers.map(user => getUserMemos(user.link, user.creatorId, user.creatorName, user.avatar) ); const allUserResults = await Promise.allSettled(userPromises); const successfulResults = []; allUserResults.forEach((result) => { if (result.status === 'fulfilled' && Array.isArray(result.value)) { successfulResults.push(...result.value); } }); return successfulResults; } 剩余 11 行代码 展开剩余代码 2.2.2 数据验证与标准化 function validateUserConfig() { const validUsers = currentUsers.filter(user => user.creatorId && user.link && user.creatorName ); return validUsers.length > 0; } function normalizeUrl(baseUrl, path) { const normalizedBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl; const normalizedPath = path.startsWith('/') ? path : '/' + path; return normalizedBase + normalizedPath; } 剩余 8 行代码 展开剩余代码 2.3 评论系统集成 2.3.1 批量评论统计 async function getMemoCount(memos) { const twikooGroups = {}; // 按 Twikoo 环境分组处理 memos.forEach(item => { if (!item?.twikoo) return; const envId = item.twikoo; const memoUrl = normalizeUrl(item.link, `/m/${item.id}`); if (!twikooGroups[envId]) { twikooGroups[envId] = []; } twikooGroups[envId].push({ url: memoUrl }); }); const allTwikooCount = []; for (const [envId, items] of Object.entries(twikooGroups)) { try { const urls = items.map(item => item.url); const res = await twikoo.getCommentsCount({ envId: envId, urls: urls, includeReply: false }); if (Array.isArray(res)) { allTwikooCount.push(...res); } } catch (error) { console.error(`Twikoo 环境 ${envId} 评论数获取失败:`, error); } } // 关联评论数到对应 Memo memos.forEach(item => { if (!item?.twikoo) { item.count = 0; return; } const url = normalizeUrl(item.link, `/m/${item.id}`); const countData = allTwikooCount.find(o => o?.url === url); item.count = countData?.count || 0; }); return memos; } 剩余 44 行代码 展开剩余代码 2.4 内容渲染引擎 2.4.1 Markdown 内容处理 function processMemoContent(content) { const TAG_REGEX = /#([^#\s!.,;:?"'()]+)(?= )/g; const IMAGE_REGEX = /\!\[(.*?)\]\((.*?)\)/g; const LINK_REGEX = /(?<!!)\[(.*?)\]\((.*?)\)/g; let processed = content .replace(TAG_REGEX, '') .replace(IMAGE_REGEX, '') .replace(LINK_REGEX, '<a class="primary" href="$2" target="_blank">$1</a>'); return marked.parse(processed); } 剩余 7 行代码 展开剩余代码 2.4.2 图片资源处理 function processImageResources(content, memo) { let imageHtml = ''; // 处理内联图片 const inlineImages = content.match(IMAGE_REGEX); if (inlineImages?.length > 0) { const imageString = inlineImages.join('').replace(/,/g, ''); imageHtml = imageString.replace(IMAGE_REGEX, '<div class="memo-resource width-100">' + '<img class="lozad" data-src="$2" alt="$1">' + '</div>' ); } // 处理附件图片 if (memo.resourceList?.length > 0) { memo.resourceList.forEach(resource => { if (resource.type?.startsWith('image')) { const imageUrl = resource.externalLink || normalizeUrl(memo.link, `/o/r/${resource.uid || resource.id}`); imageHtml += '<div class="memo-resource w-100">' + '<img class="lozad" data-src="${imageUrl}">' + '</div>'; } }); } return imageHtml ? '<div class="resource-wrapper">' + '<div class="images-wrapper my-2" view-image>' + imageHtml + '</div>' + '</div>' : ''; } 剩余 27 行代码 展开剩余代码 3. 性能优化策略 3.1 分页加载机制 function pagination(data, page, limit) { const startIndex = (page - 1) * limit; const endIndex = startIndex + limit; return data.slice(startIndex, endIndex); } function updateData(data) { const validData = data.filter(item => item && typeof item === 'object'); validData.sort((a, b) => b.createdTs - a.createdTs); const pageData = pagination(validData, currentPage, itemsPerPage); renderMemoList(pageData); const totalPages = Math.ceil(validData.length / itemsPerPage); updatePaginationControls(currentPage, totalPages); } 剩余 11 行代码 展开剩余代码 3.2 图片性能优化 function initializeImageOptimizations() { // 图片懒加载 if (typeof lozad !== 'undefined') { const imageObserver = lozad('.lozad', { rootMargin: '50px 0px', threshold: 0.1 }); imageObserver.observe(); } // 图片灯箱初始化 if (typeof ViewImage !== 'undefined') { ViewImage.init('.images-wrapper img'); } } 剩余 10 行代码 展开剩余代码 3.3 缓存策略 class MemosCache { constructor() { this.cacheKey = 'memos-data-cache'; this.cacheTimeout = 5
以上内容来自博客订阅源,版权归原作者。关注作者后可以在 关注列表 第一时间查看作者文章。
登录后,加入这场讨论。
登录参与