diff --git a/internal/chatlog/http/route.go b/internal/chatlog/http/route.go index 3154a17..24dd57b 100644 --- a/internal/chatlog/http/route.go +++ b/internal/chatlog/http/route.go @@ -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, }, diff --git a/internal/wechatdb/datasource/darwinv3/datasource.go b/internal/wechatdb/datasource/darwinv3/datasource.go index be550fd..759a99d 100644 --- a/internal/wechatdb/datasource/darwinv3/datasource.go +++ b/internal/wechatdb/datasource/darwinv3/datasource.go @@ -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, "") && strings.Contains(lc, "") { + i1 := strings.Index(lc, "") + i2 := strings.Index(lc[i1+6:], "") + 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 diff --git a/internal/wechatdb/datasource/v4/datasource.go b/internal/wechatdb/datasource/v4/datasource.go index 2afa135..2ea531b 100644 --- a/internal/wechatdb/datasource/v4/datasource.go +++ b/internal/wechatdb/datasource/v4/datasource.go @@ -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, "") && strings.Contains(lc, "") { + // 简单提取 type 数字 + i1 := strings.Index(lc, "") + i2 := strings.Index(lc[i1+6:], "") + 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