Files
statistics/main.js
2024-11-26 02:29:11 +08:00

120 lines
4.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import fs from 'fs'
import { Tail } from 'tail'
import { update } from './update.js'
// 用于存储文章、游戏、截图相关的统计数据
const articles = new Map() // 文章统计
const games = new Map() // 游戏统计
const screenshots = new Map() // 截图统计
// 日期格式化工具
const dateFormat = new Intl.DateTimeFormat('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' })
// 匹配文章和截图的正则表达式
const articleRegex = /\/web\/v1\/article\/get\?id=(\d+)&userId=(\d+)/
const imagesRegex = /\/api\/images\?similar=(\d+)/
let currentDate = dateFormat.format(new Date()).replace(/\//g, '-') // 当前日期
let isArchiving = false // 是否正在存档标记
// 存档函数:将数据存入文件
const archive = async (message = '正在存档...') => {
if (isArchiving) return // 防止重复存档
isArchiving = true
console.log(message)
await fs.promises.writeFile(`./data/${currentDate}.json`, JSON.stringify({
articles: Object.fromEntries(articles), // 将 Map 转换为普通对象
games: Object.fromEntries(games),
screenshots: Object.fromEntries(screenshots),
}, null, 2))
isArchiving = false
}
// 主函数
export default function () {
// 启动时加载当天的数据文件
if (fs.existsSync(`./data/${currentDate}.json`)) {
console.log(currentDate, '启动时加载数据...')
const file = fs.readFileSync(`./data/${currentDate}.json`, 'utf8')
if (file) {
const data = JSON.parse(file)
// 将文件中的数据加载到对应的 Map 中
Object.entries(data.articles).forEach(([key, value]) => articles.set(key, value))
Object.entries(data.games).forEach(([key, value]) => games.set(key, value))
Object.entries(data.screenshots).forEach(([key, value]) => screenshots.set(key, value))
}
}
console.log(currentDate, '开始收集日志...')
// 定时存档每10分钟
setInterval(() => {
archive('每10分钟自动存档...')
update('day_rank', Object.entries(Object.fromEntries(screenshots))) // 更新数据库中 day_rank 字段
}, 600000)
// 实时读取日志文件
const tail = new Tail('/opt/log/caddy/access.log')
tail.on('line', async (line) => {
const item = JSON.parse(line) // 解析日志行
if (item.level !== 'debug') return // 仅处理 debug 级别的日志
if (item.msg !== 'upstream roundtrip') return // 仅处理指定类型的日志
// 检查日志的日期是否跨天
const logDate = dateFormat.format(new Date(item.ts * 1000)).replace(/\//g, '-')
if (currentDate !== logDate) {
await archive('跨日期存档...') // 保存当前数据
articles.clear() // 清空统计数据
games.clear()
screenshots.clear()
currentDate = logDate // 更新当前日期
}
// 处理文章相关日志
if (item.request.uri.startsWith('/web/v1/article/get')) {
const [uri, id, userId] = item.request.uri.match(articleRegex) ?? []
if (uri && id && userId && item.request.headers.Referer) {
const referer = item.request.headers.Referer[0]
// 根据 Referer 的不同路径,分类计数
if (referer.includes('/articleDetails/')) {
articles.set(id, articles.has(id) ? articles.get(id) + 1 : 1)
return
}
if (referer.includes('/inspirationInfo/')) {
games.set(id, games.has(id) ? games.get(id) + 1 : 1)
return
}
if (referer.includes('/game/')) {
games.set(id, games.has(id) ? games.get(id) + 1 : 1)
return
}
console.log('未处理的 Referer:', referer) // 打印未处理的 Referer
}
return
}
// 处理截图相关日志
if (item.request.uri.startsWith('/api/images')) {
if (item.request.uri.includes('page=')) {
return // 忽略分页请求
}
const [uri, id] = item.request.uri.match(imagesRegex) ?? []
if (uri && id) {
screenshots.set(id, screenshots.has(id) ? screenshots.get(id) + 1 : 1)
}
return
}
})
// 日志尾部事件监听
tail.on('end', () => console.log('没有更多内容,停止读取'))
tail.on('error', (error) => console.error('错误:', error))
// 捕获退出信号,确保数据存档
process.on('SIGINT', async () => {
tail.unwatch() // 停止监听日志
await archive('退出前存档数据...')
process.exit()
})
}