我打算用思源的 Docker 发布功能搭建博客,但不知如何统计访问量。能否提供一段 JS 代码,实现记录访问量和网站运行时间,并将这些信息显示在侧边栏?另外,希望代码存储的数据能通过 S3 同步或打包。
相关帖子
-
wilsons • • 1 • 1 赞同付费者 捐赠者
写了一个代码,但不知你说的侧边栏是指放哪?感觉放不下,所以放状态栏了。
另外,手机版也不知道哪里有位置显示,所以暂不支持。
仅供参考。
// 统计网站访问量和运行时间 (async ()=>{ // api地址,最后不要加 / const apiUrl = 'http://127.0.0.1:6806'; // api token 在设置->关于中查看 const apiToken = ''; // 初始化数据 const initData = { // 网站总访问量 total: 0, // 网站运行初始日期 startDay: '2025-04-13' }; // 定义统计信息显示模板 const tongjiTpl = `总访问量:{total} 网站运行:{runningDays}天`; // 已统计的跳过 if(document.querySelector('#status .site__tongji')) return; // 记录初始访问量 const initTotal = initData.total || 0; // 延迟等待数据加载和同步 await sleep(1500); // 获取置顶数据,格式 {"total":0, "startDay":''} let data = await getFile('/data/storage/site_tongji.json'); data = JSON.parse(data||'{}'); if(data.code && data.code !== 0) data = {}; data = {...initData, ...data}; // 仅网站版和发布服务版才统计 if(siyuan.config.readonly && isBrowser()) { // 当不存在total字段时初始化 if(!data.total) data.total = 0; // 当总数小于初始化值时,直接赋值为初始化的值 if(data.total < initTotal) data.total = initTotal; // 网站访问+1 data.total++; // 存储网站访问数据 if(data.total > 0) putFile('/data/storage/site_tongji.json', JSON.stringify(data, null, 4)); } // 获取网站运行时间 const runningDays = calculateRunningDays(data); // 状态栏显示统计信息 showStatusMsg(tongjiTpl.replace('{total}', data.total).replace('{runningDays}', runningDays)); // 计算网站运行时间(单位:天) function calculateRunningDays(data) { // 获取当前日期并重置时间为 00:00:00 const currentDate = new Date(); resetTime(currentDate); // 解析初始日期并重置时间为 00:00:00 const startDate = new Date(data.startDay); if (isNaN(startDate.getTime())) { return 1; } resetTime(startDate); // 计算时间差(毫秒) const timeDifference = currentDate - startDate; // 转换为天数 const daysDifference = Math.floor(timeDifference / (1000 * 60 * 60 * 24)); return daysDifference + 1; } function resetTime(date) { date.setHours(0, 0, 0, 0); // 设置时间为 00:00:00.000 return date; } // 状态栏输出 function showStatusMsg(html) { const statusMsg = document.querySelector('#status .status__msg'); if(!statusMsg) return; const tongji = document.querySelector('#status .site__tongji'); if(tongji) tongji.remove(); const style = ` color: var(--b3-theme-on-surface); white-space: nowrap; text-overflow: ellipsis; overflow: hidden; padding-left: 5px; font-size: 12px; `; html = `<div class="site__tongji" style="${style}">${html}</div>`; statusMsg.insertAdjacentHTML('beforebegin', html); } function isBrowser() { return !navigator.userAgent.startsWith("SiYuan") || navigator.userAgent.indexOf("iPad") > -1 || (/Android/.test(navigator.userAgent) && !/(?:Mobile)/.test(navigator.userAgent)); } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // 获取文件 async function getFile(path) { return fetch("/api/file/getFile", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ path, }), }).then((response) => { if (response.ok) { return response.text(); } else { throw new Error("Failed to get file content"); } }).catch((error) => { console.error(error); }); } // 存储文件,支持创建文件夹,当isDir true时创建文件夹,忽略文件 async function putFile(path, content = '', isDir = false) { const formData = new FormData(); formData.append("path", path); formData.append("isDir", isDir) formData.append("file", new Blob([content])); const result = await fetch(apiUrl+"/api/file/putFile", { // 写入js到本地 method: "POST", headers: { "Authorization": "token " + apiToken // 添加 Authorization 头 }, body: formData, }); const json = await result.json(); return json; } })();
-
-
写了一个代码,但不知你说的侧边栏是指放哪?感觉放不下,所以放状态栏了。
另外,手机版也不知道哪里有位置显示,所以暂不支持。
仅供参考。
// 统计网站访问量和运行时间 (async ()=>{ // api地址,最后不要加 / const apiUrl = 'http://127.0.0.1:6806'; // api token 在设置->关于中查看 const apiToken = ''; // 初始化数据 const initData = { // 网站总访问量 total: 0, // 网站运行初始日期 startDay: '2025-04-13' }; // 定义统计信息显示模板 const tongjiTpl = `总访问量:{total} 网站运行:{runningDays}天`; // 已统计的跳过 if(document.querySelector('#status .site__tongji')) return; // 记录初始访问量 const initTotal = initData.total || 0; // 延迟等待数据加载和同步 await sleep(1500); // 获取置顶数据,格式 {"total":0, "startDay":''} let data = await getFile('/data/storage/site_tongji.json'); data = JSON.parse(data||'{}'); if(data.code && data.code !== 0) data = {}; data = {...initData, ...data}; // 仅网站版和发布服务版才统计 if(siyuan.config.readonly && isBrowser()) { // 当不存在total字段时初始化 if(!data.total) data.total = 0; // 当总数小于初始化值时,直接赋值为初始化的值 if(data.total < initTotal) data.total = initTotal; // 网站访问+1 data.total++; // 存储网站访问数据 if(data.total > 0) putFile('/data/storage/site_tongji.json', JSON.stringify(data, null, 4)); } // 获取网站运行时间 const runningDays = calculateRunningDays(data); // 状态栏显示统计信息 showStatusMsg(tongjiTpl.replace('{total}', data.total).replace('{runningDays}', runningDays)); // 计算网站运行时间(单位:天) function calculateRunningDays(data) { // 获取当前日期并重置时间为 00:00:00 const currentDate = new Date(); resetTime(currentDate); // 解析初始日期并重置时间为 00:00:00 const startDate = new Date(data.startDay); if (isNaN(startDate.getTime())) { return 1; } resetTime(startDate); // 计算时间差(毫秒) const timeDifference = currentDate - startDate; // 转换为天数 const daysDifference = Math.floor(timeDifference / (1000 * 60 * 60 * 24)); return daysDifference + 1; } function resetTime(date) { date.setHours(0, 0, 0, 0); // 设置时间为 00:00:00.000 return date; } // 状态栏输出 function showStatusMsg(html) { const statusMsg = document.querySelector('#status .status__msg'); if(!statusMsg) return; const tongji = document.querySelector('#status .site__tongji'); if(tongji) tongji.remove(); const style = ` color: var(--b3-theme-on-surface); white-space: nowrap; text-overflow: ellipsis; overflow: hidden; padding-left: 5px; font-size: 12px; `; html = `<div class="site__tongji" style="${style}">${html}</div>`; statusMsg.insertAdjacentHTML('beforebegin', html); } function isBrowser() { return !navigator.userAgent.startsWith("SiYuan") || navigator.userAgent.indexOf("iPad") > -1 || (/Android/.test(navigator.userAgent) && !/(?:Mobile)/.test(navigator.userAgent)); } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // 获取文件 async function getFile(path) { return fetch("/api/file/getFile", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ path, }), }).then((response) => { if (response.ok) { return response.text(); } else { throw new Error("Failed to get file content"); } }).catch((error) => { console.error(error); }); } // 存储文件,支持创建文件夹,当isDir true时创建文件夹,忽略文件 async function putFile(path, content = '', isDir = false) { const formData = new FormData(); formData.append("path", path); formData.append("isDir", isDir) formData.append("file", new Blob([content])); const result = await fetch(apiUrl+"/api/file/putFile", { // 写入js到本地 method: "POST", headers: { "Authorization": "token " + apiToken // 添加 Authorization 头 }, body: formData, }); const json = await result.json(); return json; } })();
1 回复 -
可以,等下用新的 dom 显示就行,现在用的 status 公共消息区,可能 🈶 新消息覆盖了
还有别的问题吗?一起改
2 回复暂时没有发现什么问题了CongSec •@CongSec 我没用过 docker 版,你 docker 版有没有加代码片段?有没有验证是否 js 加载成功了?比如,加个 alert(1),看看是否能弹出等,有没有网址可以看看。你是想仅发布模式下才增加,编辑模式不增加吗?wilsons •@wilsons 感谢,我新创建一个服务器来提供你测试,http://8.146.199.199:6806/,密码为 123CongSec •@CongSec 我发现问题了,发布服务下,所以写接口都是禁止的,即无法写入任何数据,自然计数也无法增加了。现在有两种方式:1 是能否调用你同 ip 下的 6806 接口,如果可以可以调用 6806 的接口写 2 如果 1 不行,可以把数据存储到在线,方式同我之前开发的一键访问手机伺服类似,看你选哪种方便?@CongSec 改好了,按方案 1,代码已更新,同时那个测试网站代码也加进去了。发布服务测试:http://8.146.199.199:6808/ 用户 a 密码 1 非发布服务,仅查看,刷新不在更新访问数。 -
你最近有做什么吗?先推测下可能的原因,这样才能针对的解决问题。大致方向是同步的锅,比如存储的统计数据,尚未同步成功时被执行了存储操作,还有如果客户端上开启了只读模式,如果尚未同步成功,可能会被重写数据。所以,现在做了以下几种限制:1 仅发布服务和浏览器访问才统计,2 网站加载时暂停 1.5 秒执行,等待数据同步完成 3 判断已存数据是否小于初始数据 4 数据只有大于 0 的时候才被存储
-
刚才更新了代码,我就尝试了不断地刷新,发现访问量竟然变成了 2,但是当我再次想尝试通过不断刷新来进行复现的时候,发现不行,因该是有概率性会出现这种情况,此时我的本机电脑访问量并不是 2,所以因该不是同步原因造成的,对了,我的博客是上了阿里云的 DCDN(DCDN 支持 WebSocket 协议),不知道是否是这个造成的,使用 DCDN 可以将博客的访问时间由原来的 10 几秒提升到 1-2 秒,所以 DCDN 功能 不能移除
以下是我的成功复现时的日志文件:systemlog.zip
1 回复1 操作CongSec 在 2025-04-20 19:02:43 更新了该回帖