Compare commits

...

30 Commits

Author SHA1 Message Date
c88d106a55 防止 NULL 2024-12-28 03:49:17 +08:00
42485cc97e DEBUG praise_id 2024-12-28 03:43:08 +08:00
bb1c7ec16d 修正分组统计 2024-12-28 03:39:32 +08:00
7280705baf 修改统计查询,避免 NULL 2024-12-28 03:16:07 +08:00
39e1f58665 DEBUG: 打印临时表前 10 条数据 2024-12-28 03:04:53 +08:00
b2c8345798 打印调试信息 2024-12-28 02:43:35 +08:00
6d56fe8638 处理收藏图片日志 2024-12-25 17:12:43 +08:00
afd0b1125d 使用 int id 2024-12-24 17:07:20 +08:00
b8e35a9760 参数数组 2024-12-24 16:30:09 +08:00
f4beb32725 pagesize 2024-12-16 13:57:58 +08:00
cdf683bd62 DEBUG 2024-12-16 12:09:13 +08:00
80b3219244 处理搜索日志 2024-12-16 12:08:53 +08:00
09f402377e DEBUG 2024-12-16 12:05:47 +08:00
5f127098cf DEBUG 2024-12-16 12:03:32 +08:00
a9b6a2c7fd 格式化 2024-12-16 11:48:05 +08:00
e62c113c93 获取当日热门搜索词 2024-12-16 11:43:49 +08:00
eb2e0e5d2a 获取当日热门搜索词 2024-12-16 11:42:56 +08:00
5d22686cf8 实时统计 2024-12-16 10:50:14 +08:00
31cf0d0daa 处理搜索日志 2024-12-16 10:39:14 +08:00
76924ac0a5 debug 2024-12-16 10:36:10 +08:00
7042f3d11e 处理搜索日志 2024-12-16 10:32:27 +08:00
368d23399a DEBUG 2024-12-16 10:20:27 +08:00
05dc0aac1b DEBUG 2024-12-16 10:16:49 +08:00
c4feb57867 name 2024-12-16 10:16:10 +08:00
a51818759c 将今日浏览数并入总数 2024-12-16 10:09:08 +08:00
497b3ab9ed 处理搜索日志 2024-12-16 10:02:36 +08:00
409ff2f44a 支持输出统计 2024-12-16 09:59:17 +08:00
ac8d89454b 更新频率为10分钟 2024-12-16 09:28:05 +08:00
586fb257bd 不使用左连接以优化更新速度 2024-12-16 09:13:22 +08:00
8372b221ee 更改存档异步等待 2024-12-16 09:00:59 +08:00
3 changed files with 128 additions and 32 deletions

118
main.js
View File

@@ -8,6 +8,7 @@ class 计数 {
= new Map() = new Map()
= new Map() = new Map()
= new Map() = new Map()
= new Map()
} }
class 类型 { class 类型 {
@@ -25,6 +26,27 @@ 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', { const 日期格式 = new Intl.DateTimeFormat('zh-CN', {
year: 'numeric', year: 'numeric',
@@ -36,7 +58,8 @@ const 日期格式 = new Intl.DateTimeFormat('zh-CN', {
// 匹配文章和截图的正则表达式 // 匹配文章和截图的正则表达式
const imagesRegex = /\/web\/v1\/images\/detail\/v2\?imagesId=(\d+)&currentUserId=(\d+)/ const imagesRegex = /\/web\/v1\/images\/detail\/v2\?imagesId=(\d+)&currentUserId=(\d+)/
const articleRegex = /\/web\/v1\/article\/get\?id=(\d+)&userId=(\d+)/ const articleRegex = /\/web\/v1\/article\/get\?id=(\d+)&userId=(\d+)/
const collectionRegex = /\/web\/v1\/member\/explorer\/article\/get\?id=(\d+)/ const collectionArticleRegex = /\/web\/v1\/member\/explorer\/article\/get\?id=(\d+)/
const collectionImagesRegex = /\/web\/v1\/member\/explorer\/get\?id=(\d+)/
var 当前日期 = 日期格式.format(new Date()).replace(/\//g, '-') var 当前日期 = 日期格式.format(new Date()).replace(/\//g, '-')
@@ -60,7 +83,19 @@ const 存档 = async (message = '正在存档...') => {
} }
export default function () { 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(当前日期, '启动时加载当日数据...') console.log(当前日期, '启动时加载当日数据...')
if (fs.existsSync(`./data/${当前日期}.json`)) { if (fs.existsSync(`./data/${当前日期}.json`)) {
const file = fs.readFileSync(`./data/${当前日期}.json`, 'utf8') const file = fs.readFileSync(`./data/${当前日期}.json`, 'utf8')
@@ -76,21 +111,23 @@ export default function () {
} }
} }
// 定时执行任务,确保上一次执行完毕后才开始下一轮
setInterval(async () => { async function startScheduledTask() {
while (true) {
try { try {
存档('每10分钟自动存档...') 存档('每10分钟自动存档...')
await update('web_images', 'day_rank', Object.entries(Object.fromEntries(截图.浏览数.))) await update('web_images', 'day_rank', Object.entries(Object.fromEntries(截图.浏览数.)))
await update('web_article', '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(收藏.浏览数.))) await update_explorer('web_member_explorer', 'day_rank', Object.entries(Object.fromEntries(收藏.浏览数.)))
} catch(err) { } catch (err) {
console.error(err) console.error(err)
}
await new Promise(resolve => setTimeout(resolve, 10 * 60 * 1000))
} }
}
// 启动定时任务
}, 10000) startScheduledTask()
console.log(当前日期, '开始收集日志...') console.log(当前日期, '开始收集日志...')
const tail = new Tail('/opt/log/caddy/access.log') const tail = new Tail('/opt/log/caddy/access.log')
@@ -118,7 +155,11 @@ export default function () {
if (item.request.uri.startsWith('/web/v1/article/get')) { if (item.request.uri.startsWith('/web/v1/article/get')) {
const [uri, id] = item.request.uri.match(articleRegex) ?? [] const [uri, id] = item.request.uri.match(articleRegex) ?? []
if (uri && id) { if (uri && id) {
文章.浏览数..set(id, 文章.浏览数..has(id) ? 文章.浏览数..get(id) + 1 : 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)
文章.浏览数..set(id, (文章.浏览数..get(id) || 0) + 1)
} }
} }
@@ -126,16 +167,53 @@ export default function () {
if (item.request.uri.startsWith('/web/v1/images/detail/v2')) { if (item.request.uri.startsWith('/web/v1/images/detail/v2')) {
const [uri, id] = item.request.uri.match(imagesRegex) ?? [] const [uri, id] = item.request.uri.match(imagesRegex) ?? []
if (uri && id) { if (uri && id) {
截图.浏览数..set(id, 截图.浏览数..has(id) ? 截图.浏览数..get(id) + 1 : 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)
截图.浏览数..set(id, (截图.浏览数..get(id) || 0) + 1)
} }
return return
} }
// 处理收藏日志 // 处理收藏图集日志
if (item.request.uri.startsWith('/web/v1/member/explorer/article/get')) { if (item.request.uri.startsWith('/web/v1/member/explorer/article/get')) {
const [uri, id] = item.request.uri.match(collectionRegex) ?? [] const [uri, id] = item.request.uri.match(collectionArticleRegex) ?? []
if (uri && id) { if (uri && id) {
收藏.浏览数..set(id, 收藏.浏览数..has(id) ? 收藏.浏览数..get(id) + 1 : 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)
收藏.浏览数..set(id, (收藏.浏览数..get(id) || 0) + 1)
}
return
}
// 处理收藏图片日志
if (item.request.uri.startsWith('/web/v1/member/explorer/get')) {
const [uri, id] = item.request.uri.match(collectionImagesRegex) ?? []
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 return
} }

View File

@@ -1,10 +1,19 @@
import fs from 'fs' import fs from 'fs'
import express from 'express' import express from 'express'
import main from './main.js' import { main, get_search, get_views } from './main.js'
const app = express() const app = express()
app.use(express.json()) app.use(express.json())
app.get('/api/get_views/:name', (req, res) => {
res.json(get_views(req.params.name, req.query.ids.split(",")))
})
app.get('/api/get_search/hot', (req, res) => {
res.json(get_search(req.query.pagesize || 10))
})
app.get('/api', (req, res) => { app.get('/api', (req, res) => {
const { start, end } = req.query const { start, end } = req.query
console.log(req.query) console.log(req.query)

View File

@@ -82,35 +82,44 @@ export async function update(table, rank, views) {
// 0=文章/游戏/作品 1=短视频 2=图片 // 0=文章/游戏/作品 1=短视频 2=图片
const TYPE = { const TYPE = {
'web_images': 'type=2', 'web_images': 'type=2',
'web_article': 'type=0', 'web_article': 'type=0',
} }
// 定义查询时间范围 // 定义查询时间范围
const WHERE = { const WHERE = {
'day_rank': `create_time >= CURDATE() AND ${TYPE[table]}`, 'day_rank': `create_time >= CURDATE() AND ${TYPE[table]}`,
'week_rank': `create_time >= CURDATE() - INTERVAL 7 DAY AND ${TYPE[table]}`, 'week_rank': `create_time >= CURDATE() - INTERVAL 7 DAY AND ${TYPE[table]}`,
'month_rank': `create_time >= CURDATE() - INTERVAL 1 MONTH AND ${TYPE[table]}`, 'month_rank': `create_time >= CURDATE() - INTERVAL 1 MONTH AND ${TYPE[table]}`,
'year_rank': `create_time >= CURDATE() - INTERVAL 1 YEAR AND ${TYPE[table]}`, 'year_rank': `create_time >= CURDATE() - INTERVAL 1 YEAR AND ${TYPE[table]}`,
'aeon_rank': `${TYPE[table]}` 'aeon_rank': `${TYPE[table]}`
} }
const JOIN = { const JOIN = {
'comment': `SELECT comment_id AS id, COUNT(*) AS comment_count FROM web_comment WHERE ${WHERE[rank]} GROUP BY id`, 'comment': `SELECT comment_id AS id, IFNULL(COUNT(*), 0) AS comment_count FROM web_comment WHERE ${WHERE[rank]} GROUP BY comment_id`,
'praise': `SELECT praise_id AS id, COUNT(*) AS praise_count FROM web_praise WHERE ${WHERE[rank]} GROUP BY id`, 'praise': `SELECT praise_id AS id, IFNULL(COUNT(*), 0) AS praise_count FROM web_praise WHERE ${WHERE[rank]} GROUP BY praise_id`,
'collect': `SELECT collect_id AS id, COUNT(*) AS collect_count FROM web_collect WHERE ${WHERE[rank]} GROUP BY id` 'collect': `SELECT collect_id AS id, IFNULL(COUNT(*), 0) AS collect_count FROM web_collect WHERE ${WHERE[rank]} GROUP BY collect_id`
} }
for (let name of ['comment', 'praise', 'collect']) { for (let name of ['comment', 'praise', 'collect']) {
console.log(`统计 ${rank} 时间段内 ${name} 数到临时表`) console.log(`统计 ${rank} 时间段内 ${name} 数到临时表`)
await conn.query(`UPDATE temp_updates t JOIN(${JOIN[name]}) stats ON t.id = stats.id SET t.${name}_count = stats.${name}_count`) const updateResult = await conn.query(`UPDATE temp_updates t JOIN(${JOIN[name]}) stats ON t.id = stats.id SET t.${name}_count = stats.${name}_count`)
console.log(`${name} 更新结果:`, updateResult)
} }
console.log('统计当天时间段内排行榜到临时表') console.log('统计当天时间段内排行榜到临时表')
await conn.query("UPDATE temp_updates SET rank = collect_count * 35 + praise_count * 20 + comment_count * 20 + views_count * 25") const rankUpdateResult = await conn.query("UPDATE temp_updates SET rank = IFNULL(collect_count, 0) * 35 + IFNULL(praise_count, 0) * 20 + IFNULL(comment_count, 0) * 20 + IFNULL(views_count, 0) * 25")
console.log('排行榜更新结果:', rankUpdateResult)
// 打印临时表前 10 条数据
console.log('打印临时表前 10 条数据')
const [tempData] = await conn.query("SELECT * FROM temp_updates LIMIT 10")
console.log('临时表数据:', tempData)
console.log('更新数据从临时表转入生产库', table) console.log('更新数据从临时表转入生产库', table)
await conn.query(`UPDATE ${table} t LEFT JOIN temp_updates tu ON t.id = tu.id SET t.${rank} = IFNULL(tu.rank, 0)`) const finalUpdateResult = await conn.query(`UPDATE ${table} t LEFT JOIN temp_updates tu ON t.id = tu.id SET t.${rank} = IFNULL(tu.rank, 0)`)
console.log('生产库更新结果:', finalUpdateResult)
await conn.end() await conn.end()
console.log('更新完成') console.log('更新完成')
} }