219 lines
8.6 KiB
JavaScript
219 lines
8.6 KiB
JavaScript
import fs from 'fs'
|
|
import { Tail } from 'tail'
|
|
import { update, update_explorer } from './update.js'
|
|
|
|
|
|
class 计数 {
|
|
日 = new Map()
|
|
周 = new Map()
|
|
月 = new Map()
|
|
年 = new Map()
|
|
总 = new Map()
|
|
}
|
|
|
|
class 类型 {
|
|
浏览数 = new 计数()
|
|
评论数 = new 计数()
|
|
点赞数 = new 计数()
|
|
收藏数 = new 计数()
|
|
搜索词 = new 计数()
|
|
}
|
|
|
|
const 文章 = new 类型()
|
|
const 作品 = new 类型()
|
|
const 游戏 = new 类型()
|
|
const 截图 = new 类型()
|
|
const 收藏 = new 类型()
|
|
const 综合 = new 类型()
|
|
|
|
// 获取浏览数
|
|
export function get_views(name, ids = []) {
|
|
const all = { 文章, 作品, 游戏, 截图, 收藏, 综合 }
|
|
const counts = all[name].浏览数.总
|
|
return ids.map(id => ({ id: parseInt(id, 10), count: counts.get(id) || 0 }))
|
|
}
|
|
|
|
// 获取当日热门搜索词
|
|
export function get_search(n = 10) {
|
|
const 全部 = new Map()
|
|
const all = { 文章, 作品, 游戏, 截图, 收藏, 综合 }
|
|
for (const key in all) {
|
|
all[key].搜索词.日.forEach((value, key) => {
|
|
console.log(key, value)
|
|
全部.set(key, (全部.get(key) || 0) + value)
|
|
})
|
|
}
|
|
return [...全部.entries()].sort((a, b) => b[1] - a[1]).slice(0, n).map(([text, count]) => {
|
|
return { text, count }
|
|
})
|
|
}
|
|
|
|
const 日期格式 = new Intl.DateTimeFormat('zh-CN', {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit'
|
|
})
|
|
|
|
|
|
// 匹配文章和截图的正则表达式
|
|
const imagesRegex = /\/web\/v1\/images\/detail\/v2\?imagesId=(\d+)¤tUserId=(\d+)/
|
|
const articleRegex = /\/web\/v1\/article\/get\?id=(\d+)&userId=(\d+)/
|
|
const collectionRegex = /\/web\/v1\/member\/explorer\/article\/get\?id=(\d+)/
|
|
|
|
|
|
var 当前日期 = 日期格式.format(new Date()).replace(/\//g, '-')
|
|
var 正在存档 = false
|
|
|
|
|
|
const 存档 = async (message = '正在存档...') => {
|
|
if (正在存档) return
|
|
正在存档 = true
|
|
console.log(message)
|
|
await fs.promises.writeFile(`./data/${当前日期}.json`, JSON.stringify({
|
|
文章: Object.fromEntries(文章.浏览数.日),
|
|
截图: Object.fromEntries(截图.浏览数.日),
|
|
收藏: Object.fromEntries(收藏.浏览数.日),
|
|
|
|
文章搜索: Object.fromEntries(文章.搜索词.日),
|
|
截图搜索: Object.fromEntries(截图.搜索词.日),
|
|
收藏搜索: Object.fromEntries(收藏.搜索词.日),
|
|
}, null, 2))
|
|
正在存档 = false
|
|
}
|
|
|
|
|
|
export function main() {
|
|
console.log(当前日期, '启动时加载全部数据...')
|
|
fs.readdirSync('./data').filter(file => file.endsWith('.json')).map(name => {
|
|
console.log('filename:', name)
|
|
const file = fs.readFileSync(`./data/${name}`, 'utf8')
|
|
if (file) {
|
|
const data = JSON.parse(file)
|
|
Object.entries(data.文章).forEach(([key, value]) => 文章.浏览数.总.set(key, value))
|
|
Object.entries(data.截图).forEach(([key, value]) => 截图.浏览数.总.set(key, value))
|
|
Object.entries(data.收藏).forEach(([key, value]) => 收藏.浏览数.总.set(key, value))
|
|
}
|
|
})
|
|
|
|
console.log(当前日期, '启动时加载当日数据...')
|
|
if (fs.existsSync(`./data/${当前日期}.json`)) {
|
|
const file = fs.readFileSync(`./data/${当前日期}.json`, 'utf8')
|
|
if (file) {
|
|
const data = JSON.parse(file)
|
|
Object.entries(data.文章).forEach(([key, value]) => 文章.浏览数.日.set(key, value))
|
|
Object.entries(data.截图).forEach(([key, value]) => 截图.浏览数.日.set(key, value))
|
|
Object.entries(data.收藏).forEach(([key, value]) => 收藏.浏览数.日.set(key, value))
|
|
|
|
Object.entries(data.文章搜索).forEach(([key, value]) => 文章.搜索词.日.set(key, value))
|
|
Object.entries(data.截图搜索).forEach(([key, value]) => 截图.搜索词.日.set(key, value))
|
|
Object.entries(data.收藏搜索).forEach(([key, value]) => 收藏.搜索词.日.set(key, value))
|
|
}
|
|
}
|
|
|
|
// 定时执行任务,确保上一次执行完毕后才开始下一轮
|
|
async function startScheduledTask() {
|
|
while (true) {
|
|
try {
|
|
存档('每10分钟自动存档...')
|
|
await update('web_images', 'day_rank', Object.entries(Object.fromEntries(截图.浏览数.日)))
|
|
await update('web_article', 'day_rank', Object.entries(Object.fromEntries(文章.浏览数.日)))
|
|
await update_explorer('web_member_explorer', 'day_rank', Object.entries(Object.fromEntries(收藏.浏览数.日)))
|
|
} catch (err) {
|
|
console.error(err)
|
|
}
|
|
await new Promise(resolve => setTimeout(resolve, 10 * 60 * 1000))
|
|
}
|
|
}
|
|
|
|
// 启动定时任务
|
|
startScheduledTask()
|
|
|
|
console.log(当前日期, '开始收集日志...')
|
|
const tail = new Tail('/opt/log/caddy/access.log')
|
|
tail.on('line', async (line) => {
|
|
const item = JSON.parse(line)
|
|
if (item.level !== 'debug') return
|
|
if (item.msg !== 'upstream roundtrip') return
|
|
|
|
// 检查日志的日期是否跨天
|
|
const logDate = 日期格式.format(new Date(item.ts * 1000)).replace(/\//g, '-')
|
|
if (当前日期 !== logDate) {
|
|
await 存档('跨日期存档...')
|
|
文章.浏览数.日.clear()
|
|
截图.浏览数.日.clear()
|
|
收藏.浏览数.日.clear()
|
|
|
|
文章.搜索词.日.clear()
|
|
截图.搜索词.日.clear()
|
|
收藏.搜索词.日.clear()
|
|
|
|
当前日期 = logDate
|
|
}
|
|
|
|
// 处理文章日志
|
|
if (item.request.uri.startsWith('/web/v1/article/get')) {
|
|
const [uri, id] = item.request.uri.match(articleRegex) ?? []
|
|
if (uri && id) {
|
|
文章.浏览数.日.set(id, (文章.浏览数.日.get(id) || 0) + 1)
|
|
文章.浏览数.周.set(id, (文章.浏览数.周.get(id) || 0) + 1)
|
|
文章.浏览数.月.set(id, (文章.浏览数.月.get(id) || 0) + 1)
|
|
文章.浏览数.年.set(id, (文章.浏览数.年.get(id) || 0) + 1)
|
|
文章.浏览数.总.set(id, (文章.浏览数.总.get(id) || 0) + 1)
|
|
}
|
|
}
|
|
|
|
// 处理截图日志
|
|
if (item.request.uri.startsWith('/web/v1/images/detail/v2')) {
|
|
const [uri, id] = item.request.uri.match(imagesRegex) ?? []
|
|
if (uri && id) {
|
|
截图.浏览数.日.set(id, (截图.浏览数.日.get(id) || 0) + 1)
|
|
截图.浏览数.周.set(id, (截图.浏览数.周.get(id) || 0) + 1)
|
|
截图.浏览数.月.set(id, (截图.浏览数.月.get(id) || 0) + 1)
|
|
截图.浏览数.年.set(id, (截图.浏览数.年.get(id) || 0) + 1)
|
|
截图.浏览数.总.set(id, (截图.浏览数.总.get(id) || 0) + 1)
|
|
}
|
|
return
|
|
}
|
|
|
|
// 处理收藏日志
|
|
if (item.request.uri.startsWith('/web/v1/member/explorer/article/get')) {
|
|
const [uri, id] = item.request.uri.match(collectionRegex) ?? []
|
|
if (uri && id) {
|
|
收藏.浏览数.日.set(id, (收藏.浏览数.日.get(id) || 0) + 1)
|
|
收藏.浏览数.周.set(id, (收藏.浏览数.周.get(id) || 0) + 1)
|
|
收藏.浏览数.月.set(id, (收藏.浏览数.月.get(id) || 0) + 1)
|
|
收藏.浏览数.年.set(id, (收藏.浏览数.年.get(id) || 0) + 1)
|
|
收藏.浏览数.总.set(id, (收藏.浏览数.总.get(id) || 0) + 1)
|
|
}
|
|
return
|
|
}
|
|
|
|
// 处理搜索日志
|
|
if (item.request.uri.startsWith('/api?query=')) {
|
|
const url = decodeURI(item.request.uri)
|
|
const regex = /text:"([^"]*)"/g
|
|
const match = regex.exec(url)
|
|
if (match) {
|
|
let key = match[1]
|
|
截图.搜索词.日.set(key, (截图.搜索词.日.get(key) || 0) + 1)
|
|
截图.搜索词.周.set(key, (截图.搜索词.周.get(key) || 0) + 1)
|
|
截图.搜索词.月.set(key, (截图.搜索词.月.get(key) || 0) + 1)
|
|
截图.搜索词.年.set(key, (截图.搜索词.年.get(key) || 0) + 1)
|
|
截图.搜索词.总.set(key, (截图.搜索词.总.get(key) || 0) + 1)
|
|
}
|
|
return
|
|
}
|
|
})
|
|
|
|
// 日志尾部事件监听
|
|
tail.on('end', () => console.log('没有更多内容,停止读取'))
|
|
tail.on('error', (error) => console.error('错误:', error))
|
|
|
|
// 捕获退出信号,确保数据存档
|
|
process.on('SIGINT', async () => {
|
|
tail.unwatch() // 停止监听日志
|
|
await 存档('退出前存档数据...')
|
|
process.exit()
|
|
})
|
|
}
|