main.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "os"
  6. "sort"
  7. "strings"
  8. "time"
  9. "github.com/xuri/excelize/v2"
  10. "go.mongodb.org/mongo-driver/bson"
  11. "go.mongodb.org/mongo-driver/mongo"
  12. "go.mongodb.org/mongo-driver/mongo/options"
  13. )
  14. // SiteVersionInfo 用于存储站点版本相关字段
  15. type SiteVersionInfo struct {
  16. Sn string `bson:"_id"`
  17. Version string `bson:"version"`
  18. ProtocolVer string `bson:"protocolVer"`
  19. SysHdVersion int `bson:"sysHdVersion"`
  20. }
  21. // VersionCombination 版本组合
  22. type VersionCombination struct {
  23. Version string
  24. ProtocolVer string
  25. SysHdVersion int
  26. Count int
  27. Sites []string // 记录前几个站点SN作为示例
  28. }
  29. const (
  30. collectionName = "SiteInfoManageData"
  31. maxExampleSites = 5 // 每个组合最多记录的示例站点数
  32. )
  33. func main() {
  34. // 从环境变量获取 MongoDB DSN
  35. mongoDSN := os.Getenv("MONGO_DSN")
  36. if mongoDSN == "" {
  37. fmt.Println("请设置 MONGO_DSN 环境变量")
  38. fmt.Println("例如: export MONGO_DSN=mongodb://127.0.0.1:27017/FwhToolboxDb")
  39. os.Exit(1)
  40. }
  41. // 连接 MongoDB
  42. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
  43. defer cancel()
  44. client, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoDSN))
  45. if err != nil {
  46. fmt.Printf("MongoDB 连接失败: %v\n", err)
  47. os.Exit(1)
  48. }
  49. defer client.Disconnect(ctx)
  50. if err := client.Ping(ctx, nil); err != nil {
  51. fmt.Printf("MongoDB Ping 失败: %v\n", err)
  52. os.Exit(1)
  53. }
  54. fmt.Println("MongoDB 连接成功")
  55. // 从 URI 中解析数据库名
  56. parts := strings.Split(mongoDSN, "/")
  57. dbName := parts[len(parts)-1]
  58. // 移除可能的查询参数
  59. if idx := strings.Index(dbName, "?"); idx != -1 {
  60. dbName = dbName[:idx]
  61. }
  62. db := client.Database(dbName)
  63. collection := db.Collection(collectionName)
  64. // 只查询需要的字段以提高性能
  65. projection := bson.M{
  66. "_id": 1,
  67. "version": 1,
  68. "protocolVer": 1,
  69. "sysHdVersion": 1,
  70. }
  71. findOpts := options.Find().SetProjection(projection)
  72. cursor, err := collection.Find(ctx, bson.M{}, findOpts)
  73. if err != nil {
  74. fmt.Printf("查询失败: %v\n", err)
  75. os.Exit(1)
  76. }
  77. defer cursor.Close(ctx)
  78. // 统计版本组合
  79. type comboKey struct {
  80. Version string
  81. ProtocolVer string
  82. SysHdVersion int
  83. }
  84. comboMap := make(map[comboKey]*VersionCombination)
  85. totalCount := 0
  86. for cursor.Next(ctx) {
  87. var site SiteVersionInfo
  88. if err := cursor.Decode(&site); err != nil {
  89. fmt.Printf("解码失败 (sn=%s): %v\n", site.Sn, err)
  90. continue
  91. }
  92. totalCount++
  93. key := comboKey{
  94. Version: site.Version,
  95. ProtocolVer: site.ProtocolVer,
  96. SysHdVersion: site.SysHdVersion,
  97. }
  98. if combo, exists := comboMap[key]; exists {
  99. combo.Count++
  100. if len(combo.Sites) < maxExampleSites {
  101. combo.Sites = append(combo.Sites, site.Sn)
  102. }
  103. } else {
  104. comboMap[key] = &VersionCombination{
  105. Version: site.Version,
  106. ProtocolVer: site.ProtocolVer,
  107. SysHdVersion: site.SysHdVersion,
  108. Count: 1,
  109. Sites: []string{site.Sn},
  110. }
  111. }
  112. }
  113. if err := cursor.Err(); err != nil {
  114. fmt.Printf("游标遍历出错: %v\n", err)
  115. os.Exit(1)
  116. }
  117. // 将结果转为切片并排序(按数量降序)
  118. combos := make([]*VersionCombination, 0, len(comboMap))
  119. for _, combo := range comboMap {
  120. combos = append(combos, combo)
  121. }
  122. sort.Slice(combos, func(i, j int) bool {
  123. return combos[i].Count > combos[j].Count
  124. })
  125. // 输出结果
  126. fmt.Printf("\n========== 站点版本组合统计 ==========\n")
  127. fmt.Printf("总站点数: %d\n", totalCount)
  128. fmt.Printf("唯一版本组合数: %d\n", len(combos))
  129. fmt.Printf("======================================\n\n")
  130. fmt.Printf("%-6s | %-20s | %-15s | %-12s | %s\n",
  131. "序号", "Version", "ProtocolVer", "SysHdVersion", "站点数量")
  132. fmt.Println(strings.Repeat("-", 80))
  133. for i, combo := range combos {
  134. fmt.Printf("%-6d | %-20s | %-15s | %-12d | %d\n",
  135. i+1, combo.Version, combo.ProtocolVer, combo.SysHdVersion, combo.Count)
  136. }
  137. // 输出详细信息(包含示例站点)
  138. fmt.Printf("\n\n========== 详细信息(含示例站点)==========\n\n")
  139. for i, combo := range combos {
  140. fmt.Printf("[%d] Version=%s, ProtocolVer=%s, SysHdVersion=%d (共 %d 个站点)\n",
  141. i+1, combo.Version, combo.ProtocolVer, combo.SysHdVersion, combo.Count)
  142. fmt.Printf(" 示例站点: %s\n", strings.Join(combo.Sites, ", "))
  143. fmt.Println()
  144. }
  145. // 生成 Excel 表格
  146. if err := exportToExcel(combos, totalCount); err != nil {
  147. fmt.Printf("生成 Excel 失败: %v\n", err)
  148. os.Exit(1)
  149. }
  150. }
  151. // exportToExcel 将版本组合统计结果导出为 Excel 文件
  152. func exportToExcel(combos []*VersionCombination, totalCount int) error {
  153. f := excelize.NewFile()
  154. defer f.Close()
  155. sheetName := "版本组合统计"
  156. // 重命名默认 Sheet
  157. f.SetSheetName("Sheet1", sheetName)
  158. // 设置表头样式
  159. headerStyle, _ := f.NewStyle(&excelize.Style{
  160. Font: &excelize.Font{Bold: true, Size: 12},
  161. Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"#DCE6F1"}},
  162. Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center"},
  163. Border: []excelize.Border{
  164. {Type: "left", Color: "#000000", Style: 1},
  165. {Type: "top", Color: "#000000", Style: 1},
  166. {Type: "bottom", Color: "#000000", Style: 1},
  167. {Type: "right", Color: "#000000", Style: 1},
  168. },
  169. })
  170. // 设置列宽
  171. f.SetColWidth(sheetName, "A", "A", 8)
  172. f.SetColWidth(sheetName, "B", "B", 22)
  173. f.SetColWidth(sheetName, "C", "C", 18)
  174. f.SetColWidth(sheetName, "D", "D", 15)
  175. f.SetColWidth(sheetName, "E", "E", 12)
  176. f.SetColWidth(sheetName, "F", "F", 60)
  177. // 写入汇总信息
  178. f.SetCellValue(sheetName, "A1", "站点版本组合统计报告")
  179. f.SetCellValue(sheetName, "A2", fmt.Sprintf("总站点数: %d", totalCount))
  180. f.SetCellValue(sheetName, "A3", fmt.Sprintf("唯一版本组合数: %d", len(combos)))
  181. f.SetCellValue(sheetName, "A4", fmt.Sprintf("生成时间: %s", time.Now().Format("2006-01-02 15:04:05")))
  182. // 写入表头 (从第6行开始)
  183. headers := []string{"序号", "Version", "ProtocolVer", "SysHdVersion", "站点数量", "示例站点"}
  184. for i, header := range headers {
  185. cell := fmt.Sprintf("%s%d", columnName(i+1), 6)
  186. f.SetCellValue(sheetName, cell, header)
  187. f.SetCellStyle(sheetName, cell, cell, headerStyle)
  188. }
  189. // 写入数据
  190. for i, combo := range combos {
  191. row := i + 7 // 数据从第7行开始
  192. f.SetCellValue(sheetName, fmt.Sprintf("A%d", row), i+1)
  193. f.SetCellValue(sheetName, fmt.Sprintf("B%d", row), combo.Version)
  194. f.SetCellValue(sheetName, fmt.Sprintf("C%d", row), combo.ProtocolVer)
  195. f.SetCellValue(sheetName, fmt.Sprintf("D%d", row), combo.SysHdVersion)
  196. f.SetCellValue(sheetName, fmt.Sprintf("E%d", row), combo.Count)
  197. f.SetCellValue(sheetName, fmt.Sprintf("F%d", row), strings.Join(combo.Sites, ", "))
  198. }
  199. // 保存文件
  200. fileName := fmt.Sprintf("site_version_combinations_%s.xlsx", time.Now().Format("20060102_150405"))
  201. if err := f.SaveAs(fileName); err != nil {
  202. return fmt.Errorf("保存 Excel 文件失败: %w", err)
  203. }
  204. fmt.Printf("\n✅ Excel 文件已生成: %s\n", fileName)
  205. return nil
  206. }
  207. // columnName 将列号(1-based)转为Excel列名
  208. func columnName(col int) string {
  209. name, _ := excelize.ColumnNumberToName(col)
  210. return name
  211. }