feat(insights): 箱主数据面板——访问量、提问趋势、处理漏斗
新增 /user/insights,让箱主第一次能看到「有多少人来过我的箱、提问处理得怎么样」。 数据来源: - 新表 box_views:一行 = 一访客×一天×一箱,views 累加,唯一索引 (user_id, viewed_on, visitor)。刻意不内嵌 dbutil.Model —— 软删除 + 唯一索引 会让被删行永久占槽,且删过的槽位再也插不进去。 - 访客标识 = HMAC(日盐, IP) 前 32 hex:同日可去重(UV 准)、跨日不可关联(隐私)。 代价是近 7/30 天 UV = 各日 UV 之和,页面上已写明口径。 写入是异步的:buffered channel 512 + 2s 批量 flush,队列满即丢(统计尽力而为, 不该因为记一笔访问就拖慢页面)。过滤箱主本人、空 UA、爬虫 UA、预取请求。 密钥复用 server.xsrf_key;每日清理 400 天前的明细。 页面(纯 CSS 画图,不引图表库): - 今日/近 7 天/近 30 天 KPI + 上周对比 - 30 天 PV/UV 双柱趋势、24 小时时段分布 - 提问处理漏斗 + 回答率进度条 + 自动结论 聚合层 internal/db/box_analytics.go 复用 analyticsDialectFor 的时区处理, 与全站 stats 分开(那个不含单箱维度、按自然日缓存)。 测试:box_views 4 项(含「同批同键先内存合并」——PG 的 ON CONFLICT 不允许 同条 INSERT 二次命中同一行)、insights 的 pct/窗口切分/visitorID/爬虫过滤。
This commit is contained in:
@@ -1054,6 +1054,50 @@ var enDict = map[string]string{
|
||||
"stats.weekday.6": "Saturday",
|
||||
"stats.weekday.7": "Sunday",
|
||||
|
||||
// Owner insights panel (/user/insights)
|
||||
"insights.title": "Insights",
|
||||
"insights.heading": "Your insights",
|
||||
"insights.lead": "These numbers are yours alone: how many people opened your box, how many questions arrived, and how many are still waiting. Everything is aggregated — no visitor or asker is ever identified.",
|
||||
"insights.back": "Back to settings",
|
||||
"insights.entryTitle": "Insights",
|
||||
"insights.entryDesc": "See how many people visit your box and how your questions are doing",
|
||||
"insights.summary": "At a glance",
|
||||
"insights.noViews": "No visits recorded yet. Numbers will appear once someone opens your box.",
|
||||
"insights.today": "Today",
|
||||
"insights.week": "Last 7 days",
|
||||
"insights.month": "Last 30 days",
|
||||
"insights.newQuestions": "new questions",
|
||||
"insights.pv": "views",
|
||||
"insights.uv": "visitors",
|
||||
"insights.trendTitle": "Views over the last 30 days",
|
||||
"insights.trendNote": "One bar per day: the light part is page views, the dark part is unique visitors that day. Hover a bar for its views / visitors / questions.",
|
||||
"insights.legendPV": "Page views",
|
||||
"insights.legendUV": "Unique visitors (per day)",
|
||||
"insights.legendHover": "Hover for daily details",
|
||||
"insights.hourTitle": "When people show up",
|
||||
"insights.hourNote": "When questions arrived during the last 30 days. The brightest bar is your busiest hour.",
|
||||
"insights.funnelTitle": "Question handling",
|
||||
"insights.funnelNote": "All questions split into answered / read but unanswered / untouched.",
|
||||
"insights.funnelTotal": "All questions",
|
||||
"insights.funnelAnswered": "Answered",
|
||||
"insights.funnelReadOnly": "Read, not answered",
|
||||
"insights.funnelUnread": "Not read",
|
||||
"insights.answerRate": "Answer rate: answered ÷ all questions",
|
||||
"insights.privacyTitle": "About these numbers",
|
||||
"insights.privacyNote": "Visit stats only record \"someone visited N times on a given day\". A visitor is an IP hash with a per-day salt — no IP, no user agent, no page path is stored, and the same person cannot be linked across days. The 7-day / 30-day visitor counts are therefore the sum of daily uniques: someone visiting on several days counts several times. Your own visits to your own box are excluded.",
|
||||
|
||||
"insights.insight.empty": "No data yet. This page comes alive once you get your first question or someone opens your box.",
|
||||
"insights.insight.weekViews": "%s views from %s visitors in the last 7 days.",
|
||||
"insights.insight.conversion": "About %s%% of visitors left a message in the last 30 days.",
|
||||
"insights.insight.bestDay": "Your busiest day was %s with %s views.",
|
||||
"insights.insight.peakHour": "Questions cluster around %d:00.",
|
||||
"insights.insight.answerRate": "You have answered %s%% of all questions.",
|
||||
"insights.insight.fastReply": "%s%% of your answers went out within 24 hours.",
|
||||
"insights.insight.unread": "%s questions are still waiting.",
|
||||
"insights.insight.trendUp": "Views this week are up %s%% on last week.",
|
||||
"insights.insight.trendDown": "Views this week are down %s%% on last week.",
|
||||
"insights.insight.trendFlat": "Views this week are about the same as last week.",
|
||||
|
||||
"stats.foot.updated": "Snapshot:",
|
||||
"stats.foot.prevMonth": "Last question:",
|
||||
"stats.foot.prevAnswer": "Last answer:",
|
||||
|
||||
Reference in New Issue
Block a user