| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- package main
- import (
- "context"
- "fmt"
- "os"
- "sort"
- "strings"
- "time"
- "github.com/xuri/excelize/v2"
- "go.mongodb.org/mongo-driver/bson"
- "go.mongodb.org/mongo-driver/mongo"
- "go.mongodb.org/mongo-driver/mongo/options"
- )
- // SiteVersionInfo 用于存储站点版本相关字段
- type SiteVersionInfo struct {
- Sn string `bson:"_id"`
- Version string `bson:"version"`
- ProtocolVer string `bson:"protocolVer"`
- SysHdVersion int `bson:"sysHdVersion"`
- }
- // VersionCombination 版本组合
- type VersionCombination struct {
- Version string
- ProtocolVer string
- SysHdVersion int
- Count int
- Sites []string // 记录前几个站点SN作为示例
- }
- const (
- collectionName = "SiteInfoManageData"
- maxExampleSites = 5 // 每个组合最多记录的示例站点数
- )
- func main() {
- // 从环境变量获取 MongoDB DSN
- mongoDSN := os.Getenv("MONGO_DSN")
- if mongoDSN == "" {
- fmt.Println("请设置 MONGO_DSN 环境变量")
- fmt.Println("例如: export MONGO_DSN=mongodb://127.0.0.1:27017/FwhToolboxDb")
- os.Exit(1)
- }
- // 连接 MongoDB
- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
- defer cancel()
- client, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoDSN))
- if err != nil {
- fmt.Printf("MongoDB 连接失败: %v\n", err)
- os.Exit(1)
- }
- defer client.Disconnect(ctx)
- if err := client.Ping(ctx, nil); err != nil {
- fmt.Printf("MongoDB Ping 失败: %v\n", err)
- os.Exit(1)
- }
- fmt.Println("MongoDB 连接成功")
- // 从 URI 中解析数据库名
- parts := strings.Split(mongoDSN, "/")
- dbName := parts[len(parts)-1]
- // 移除可能的查询参数
- if idx := strings.Index(dbName, "?"); idx != -1 {
- dbName = dbName[:idx]
- }
- db := client.Database(dbName)
- collection := db.Collection(collectionName)
- // 只查询需要的字段以提高性能
- projection := bson.M{
- "_id": 1,
- "version": 1,
- "protocolVer": 1,
- "sysHdVersion": 1,
- }
- findOpts := options.Find().SetProjection(projection)
- cursor, err := collection.Find(ctx, bson.M{}, findOpts)
- if err != nil {
- fmt.Printf("查询失败: %v\n", err)
- os.Exit(1)
- }
- defer cursor.Close(ctx)
- // 统计版本组合
- type comboKey struct {
- Version string
- ProtocolVer string
- SysHdVersion int
- }
- comboMap := make(map[comboKey]*VersionCombination)
- totalCount := 0
- for cursor.Next(ctx) {
- var site SiteVersionInfo
- if err := cursor.Decode(&site); err != nil {
- fmt.Printf("解码失败 (sn=%s): %v\n", site.Sn, err)
- continue
- }
- totalCount++
- key := comboKey{
- Version: site.Version,
- ProtocolVer: site.ProtocolVer,
- SysHdVersion: site.SysHdVersion,
- }
- if combo, exists := comboMap[key]; exists {
- combo.Count++
- if len(combo.Sites) < maxExampleSites {
- combo.Sites = append(combo.Sites, site.Sn)
- }
- } else {
- comboMap[key] = &VersionCombination{
- Version: site.Version,
- ProtocolVer: site.ProtocolVer,
- SysHdVersion: site.SysHdVersion,
- Count: 1,
- Sites: []string{site.Sn},
- }
- }
- }
- if err := cursor.Err(); err != nil {
- fmt.Printf("游标遍历出错: %v\n", err)
- os.Exit(1)
- }
- // 将结果转为切片并排序(按数量降序)
- combos := make([]*VersionCombination, 0, len(comboMap))
- for _, combo := range comboMap {
- combos = append(combos, combo)
- }
- sort.Slice(combos, func(i, j int) bool {
- return combos[i].Count > combos[j].Count
- })
- // 输出结果
- fmt.Printf("\n========== 站点版本组合统计 ==========\n")
- fmt.Printf("总站点数: %d\n", totalCount)
- fmt.Printf("唯一版本组合数: %d\n", len(combos))
- fmt.Printf("======================================\n\n")
- fmt.Printf("%-6s | %-20s | %-15s | %-12s | %s\n",
- "序号", "Version", "ProtocolVer", "SysHdVersion", "站点数量")
- fmt.Println(strings.Repeat("-", 80))
- for i, combo := range combos {
- fmt.Printf("%-6d | %-20s | %-15s | %-12d | %d\n",
- i+1, combo.Version, combo.ProtocolVer, combo.SysHdVersion, combo.Count)
- }
- // 输出详细信息(包含示例站点)
- fmt.Printf("\n\n========== 详细信息(含示例站点)==========\n\n")
- for i, combo := range combos {
- fmt.Printf("[%d] Version=%s, ProtocolVer=%s, SysHdVersion=%d (共 %d 个站点)\n",
- i+1, combo.Version, combo.ProtocolVer, combo.SysHdVersion, combo.Count)
- fmt.Printf(" 示例站点: %s\n", strings.Join(combo.Sites, ", "))
- fmt.Println()
- }
- // 生成 Excel 表格
- if err := exportToExcel(combos, totalCount); err != nil {
- fmt.Printf("生成 Excel 失败: %v\n", err)
- os.Exit(1)
- }
- }
- // exportToExcel 将版本组合统计结果导出为 Excel 文件
- func exportToExcel(combos []*VersionCombination, totalCount int) error {
- f := excelize.NewFile()
- defer f.Close()
- sheetName := "版本组合统计"
- // 重命名默认 Sheet
- f.SetSheetName("Sheet1", sheetName)
- // 设置表头样式
- headerStyle, _ := f.NewStyle(&excelize.Style{
- Font: &excelize.Font{Bold: true, Size: 12},
- Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"#DCE6F1"}},
- Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center"},
- Border: []excelize.Border{
- {Type: "left", Color: "#000000", Style: 1},
- {Type: "top", Color: "#000000", Style: 1},
- {Type: "bottom", Color: "#000000", Style: 1},
- {Type: "right", Color: "#000000", Style: 1},
- },
- })
- // 设置列宽
- f.SetColWidth(sheetName, "A", "A", 8)
- f.SetColWidth(sheetName, "B", "B", 22)
- f.SetColWidth(sheetName, "C", "C", 18)
- f.SetColWidth(sheetName, "D", "D", 15)
- f.SetColWidth(sheetName, "E", "E", 12)
- f.SetColWidth(sheetName, "F", "F", 60)
- // 写入汇总信息
- f.SetCellValue(sheetName, "A1", "站点版本组合统计报告")
- f.SetCellValue(sheetName, "A2", fmt.Sprintf("总站点数: %d", totalCount))
- f.SetCellValue(sheetName, "A3", fmt.Sprintf("唯一版本组合数: %d", len(combos)))
- f.SetCellValue(sheetName, "A4", fmt.Sprintf("生成时间: %s", time.Now().Format("2006-01-02 15:04:05")))
- // 写入表头 (从第6行开始)
- headers := []string{"序号", "Version", "ProtocolVer", "SysHdVersion", "站点数量", "示例站点"}
- for i, header := range headers {
- cell := fmt.Sprintf("%s%d", columnName(i+1), 6)
- f.SetCellValue(sheetName, cell, header)
- f.SetCellStyle(sheetName, cell, cell, headerStyle)
- }
- // 写入数据
- for i, combo := range combos {
- row := i + 7 // 数据从第7行开始
- f.SetCellValue(sheetName, fmt.Sprintf("A%d", row), i+1)
- f.SetCellValue(sheetName, fmt.Sprintf("B%d", row), combo.Version)
- f.SetCellValue(sheetName, fmt.Sprintf("C%d", row), combo.ProtocolVer)
- f.SetCellValue(sheetName, fmt.Sprintf("D%d", row), combo.SysHdVersion)
- f.SetCellValue(sheetName, fmt.Sprintf("E%d", row), combo.Count)
- f.SetCellValue(sheetName, fmt.Sprintf("F%d", row), strings.Join(combo.Sites, ", "))
- }
- // 保存文件
- fileName := fmt.Sprintf("site_version_combinations_%s.xlsx", time.Now().Format("20060102_150405"))
- if err := f.SaveAs(fileName); err != nil {
- return fmt.Errorf("保存 Excel 文件失败: %w", err)
- }
- fmt.Printf("\n✅ Excel 文件已生成: %s\n", fileName)
- return nil
- }
- // columnName 将列号(1-based)转为Excel列名
- func columnName(col int) string {
- name, _ := excelize.ColumnNumberToName(col)
- return name
- }
|