feat(dashboard): v4 support

This commit is contained in:
Symb0x76
2025-09-24 15:46:28 +08:00
parent fcf17a79b3
commit d66e1eb23c
3 changed files with 82 additions and 8 deletions
+11 -3
View File
@@ -404,7 +404,7 @@ func (s *Service) handleDashboard(c *gin.Context) {
WeeklyAvg int `json:"weekly_avg"`
MostActiveHour string `json:"most_active_hour"`
}
type ContentAnalysis struct { Text int64 `json:"text_messages"`; Images int64 `json:"images"`; Voice int64 `json:"voice_messages"`; Files int64 `json:"files"`; Links int64 `json:"links"`; Others int64 `json:"others"` }
type ContentAnalysis struct { Text int64 `json:"text_messages"`; Images int64 `json:"images"`; Voice int64 `json:"voice_messages"`; Files int64 `json:"files"`; Links int64 `json:"links"`; FileLinkTotal int64 `json:"file_link_total"`; Others int64 `json:"others"` }
type GroupListItem struct { Name string `json:"name"`; Members int `json:"members"`; Messages int64 `json:"messages"`; Active bool `json:"active"` }
type HourlyActivity struct { Hour string `json:"hour"`; Messages int64 `json:"messages"` }
type GroupAnalysis struct {
@@ -510,8 +510,16 @@ func (s *Service) handleDashboard(c *gin.Context) {
GroupAnalysis: GroupAnalysis{
Title: "群聊分析",
Overview: GroupOverview{ TotalGroups: len(overviewGroups), ActiveGroups: activeGroups, TodayMessages: int(todayMessages), WeeklyAvg: weeklyAvg, MostActiveHour: mostActiveHour },
// 扩展:增加 links 字段(结构体需更新)
ContentAnalysis: ContentAnalysis{ Text: msgTypes["文本消息"], Images: msgTypes["图片消息"], Voice: msgTypes["语音消息"], Files: msgTypes["文件消息"], Links: msgTypes["链接消息"], Others: totalMsgs - (msgTypes["文本消息"]+msgTypes["图片消息"]+msgTypes["语音消息"]+msgTypes["文件消息"]+msgTypes["链接消息"]) },
// 扩展:增加 links 与汇总 file_link_total
ContentAnalysis: ContentAnalysis{
Text: msgTypes["文本消息"],
Images: msgTypes["图片消息"],
Voice: msgTypes["语音消息"],
Files: msgTypes["文件消息"],
Links: msgTypes["链接消息"],
FileLinkTotal: msgTypes["文件消息"] + msgTypes["链接消息"],
Others: totalMsgs - (msgTypes["文本消息"] + msgTypes["图片消息"] + msgTypes["语音消息"] + msgTypes["文件消息"] + msgTypes["链接消息"]),
},
GroupList: glist,
HourlyActivity: hacts,
},
@@ -774,19 +774,44 @@ func (ds *DataSource) GlobalMessageStats(ctx context.Context) (*model.GlobalMess
if stats.EarliestUnix == 0 || (minct > 0 && minct < stats.EarliestUnix) { stats.EarliestUnix = minct }
if maxct > stats.LatestUnix { stats.LatestUnix = maxct }
}
// by type
q2 := fmt.Sprintf(`SELECT messageType, COUNT(*) FROM %s GROUP BY messageType`, tbl)
// by type(先统计非 49)
q2 := fmt.Sprintf(`SELECT messageType, COUNT(*) FROM %s WHERE messageType != 49 GROUP BY messageType`, tbl)
rows, err := db.QueryContext(ctx, q2)
if err == nil {
for rows.Next() {
var t int64; var cnt int64
if err := rows.Scan(&t, &cnt); err == nil {
label := mapV4TypeToLabel(t) // 复用 v4 的类型映射
label := mapV4TypeToLabel(t)
if label != "" { stats.ByType[label] += cnt }
}
}
rows.Close()
}
// 细分 49
q49 := fmt.Sprintf(`SELECT msgContent FROM %s WHERE messageType = 49`, tbl)
orows, err := db.QueryContext(ctx, q49)
if err == nil {
for orows.Next() {
var mc string
if err := orows.Scan(&mc); err == nil {
lc := strings.ToLower(mc)
if strings.Contains(lc, "<appmsg") {
if strings.Contains(lc, "<type>") && strings.Contains(lc, "</type>") {
i1 := strings.Index(lc, "<type>")
i2 := strings.Index(lc[i1+6:], "</type>")
if i1 >= 0 && i2 > 0 {
val := lc[i1+6 : i1+6+i2]
if strings.TrimSpace(val) == "6" { stats.ByType["文件消息"]++; continue }
if strings.TrimSpace(val) == "5" || strings.TrimSpace(val) == "33" { stats.ByType["链接消息"]++; continue }
}
}
}
if strings.Contains(lc, "http://") || strings.Contains(lc, "https://") { stats.ByType["链接消息"]++; continue }
stats.ByType["XML消息"]++
}
}
orows.Close()
}
}
}
return stats, nil
+43 -2
View File
@@ -802,8 +802,9 @@ func (ds *DataSource) GlobalMessageStats(ctx context.Context) (*model.GlobalMess
if stats.EarliestUnix == 0 || (minct > 0 && minct < stats.EarliestUnix) { stats.EarliestUnix = minct }
if maxct > stats.LatestUnix { stats.LatestUnix = maxct }
}
// by type
q2 := fmt.Sprintf(`SELECT local_type, COUNT(*) FROM %s GROUP BY local_type`, tbl)
// by type (细分 49)
// 先统计除 49 之外类型
q2 := fmt.Sprintf(`SELECT local_type, COUNT(*) FROM %s WHERE local_type != 49 GROUP BY local_type`, tbl)
rows, err := db.QueryContext(ctx, q2)
if err == nil {
for rows.Next() {
@@ -815,6 +816,46 @@ func (ds *DataSource) GlobalMessageStats(ctx context.Context) (*model.GlobalMess
}
rows.Close()
}
// 针对 49 类型再做细分:简单解析 message_content 判断是文件、链接或通用 XML
q49 := fmt.Sprintf(`SELECT message_content FROM %s WHERE local_type = 49`, tbl)
orows, err := db.QueryContext(ctx, q49)
if err == nil {
for orows.Next() {
var mc []byte
if err := orows.Scan(&mc); err == nil {
content := string(mc)
// 可能压缩,简单特征判断(保持轻量;深度解压需额外性能,可后续拓展)
lc := strings.ToLower(content)
if strings.Contains(lc, "<appmsg") {
if strings.Contains(lc, "<type>") && strings.Contains(lc, "</type>") {
// 简单提取 type 数字
i1 := strings.Index(lc, "<type>")
i2 := strings.Index(lc[i1+6:], "</type>")
if i1 >= 0 && i2 > 0 {
val := lc[i1+6 : i1+6+i2]
// 常见:6=文件, 5/33=链接(网页), 3=音乐, 4=视频, 其他归类为 XML
if strings.TrimSpace(val) == "6" {
stats.ByType["文件消息"]++
continue
}
if strings.TrimSpace(val) == "5" || strings.TrimSpace(val) == "33" {
stats.ByType["链接消息"]++
continue
}
}
}
// 兜底:若包含 url 或 http(s) 关键词也认为链接
if strings.Contains(lc, "http://") || strings.Contains(lc, "https://") {
stats.ByType["链接消息"]++
continue
}
// 再兜底为 XML消息
stats.ByType["XML消息"]++
}
}
}
orows.Close()
}
}
}
return stats, nil