0904
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
// Package stats 提供首页"提问箱信息统计"所需的全站公开聚合数据。
|
||||
//
|
||||
// 原本这些数据由 crontab 定时查询数据库、生成 question.count.txt 静态文件、
|
||||
// 再由前端 fetch 外部 URL 展示。改为程序内直接计算后,这里用进程内 30 分钟缓存
|
||||
// 吸收请求(节奏与原 crontab 一致),避免每个首页请求都打聚合查询到数据库。
|
||||
package stats
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/tamakyi/TamaBox/internal/db"
|
||||
)
|
||||
|
||||
// View 全站公开统计展示模型。
|
||||
// 同时提供原始数值字段与已排版的多行文本 Text,便于服务端模板渲染与
|
||||
// 客户端定时刷新(/api/v1/site-stats)复用同一份内容。
|
||||
type View struct {
|
||||
RegisteredUsers int64 `json:"registered_users"`
|
||||
TotalQuestions int64 `json:"total_questions"`
|
||||
AnsweredQuestions int64 `json:"answered_questions"`
|
||||
LastQuestionAt string `json:"last_question_at"`
|
||||
LastAnswerAt string `json:"last_answer_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
// Text 已按原 question.count.txt 格式排版好的多行文本,直接展示即可。
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
// cacheTTL 缓存有效期,与原 crontab 生成文件的节奏保持一致(半小时)。
|
||||
const cacheTTL = 30 * time.Minute
|
||||
|
||||
var (
|
||||
mu sync.Mutex
|
||||
cached *View
|
||||
cachedAt time.Time
|
||||
)
|
||||
|
||||
// Get 返回全站统计。进程内缓存 cacheTTL,过期后才重新查询数据库。
|
||||
func Get(ctx context.Context) (*View, error) {
|
||||
mu.Lock()
|
||||
if cached != nil && time.Since(cachedAt) < cacheTTL {
|
||||
v := cached
|
||||
mu.Unlock()
|
||||
return v, nil
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
userCount, err := db.Users.CountAll(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qs, err := db.Questions.QuestionStats(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := &View{
|
||||
RegisteredUsers: userCount,
|
||||
TotalQuestions: qs.TotalQuestions,
|
||||
AnsweredQuestions: qs.AnsweredQuestions,
|
||||
}
|
||||
if qs.LastQuestionAt != nil {
|
||||
v.LastQuestionAt = qs.LastQuestionAt.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
if qs.LastAnswerAt != nil {
|
||||
v.LastAnswerAt = qs.LastAnswerAt.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
v.UpdatedAt = time.Now().Format("2006-01-02 15:04:05")
|
||||
v.Text = formatText(v)
|
||||
|
||||
mu.Lock()
|
||||
cached = v
|
||||
cachedAt = time.Now()
|
||||
mu.Unlock()
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// formatText 生成与原 question.count.txt 一致的多行展示文本。
|
||||
func formatText(v *View) string {
|
||||
s := fmt.Sprintf("注册用户总数: %d\n全站问题总数: %d\n已获得回答的问题总数: %d",
|
||||
v.RegisteredUsers, v.TotalQuestions, v.AnsweredQuestions)
|
||||
if v.LastQuestionAt != "" {
|
||||
s += "\n最后一个提问发生在: " + v.LastQuestionAt
|
||||
}
|
||||
if v.LastAnswerAt != "" {
|
||||
s += "\n最后一个回答发生在: " + v.LastAnswerAt
|
||||
}
|
||||
s += "\n数据更新于:" + v.UpdatedAt
|
||||
return s
|
||||
}
|
||||
|
||||
// Invalidate 清空缓存,供需要立即刷新统计的场景调用(如管理后台手动触发)。
|
||||
func Invalidate() {
|
||||
mu.Lock()
|
||||
cached = nil
|
||||
cachedAt = time.Time{}
|
||||
mu.Unlock()
|
||||
}
|
||||
Reference in New Issue
Block a user