main.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. package main
  2. import (
  3. "crypto/rand"
  4. "encoding/json"
  5. "fmt"
  6. "log"
  7. "net/http"
  8. "os"
  9. "path/filepath"
  10. "sync"
  11. "time"
  12. "network-topology-discovery/internal/config"
  13. "network-topology-discovery/internal/device"
  14. "network-topology-discovery/internal/scanner"
  15. "network-topology-discovery/internal/storage"
  16. "network-topology-discovery/internal/topology"
  17. "network-topology-discovery/pkg/models"
  18. )
  19. // App 应用
  20. type App struct {
  21. config *config.Config
  22. builder *topology.Builder
  23. storage *storage.Storage
  24. tasks map[string]*models.ScanTask
  25. mu sync.RWMutex
  26. httpServer *http.Server
  27. }
  28. // NewApp 创建应用
  29. func NewApp(cfg *config.Config) *App {
  30. // 初始化存储(使用JSON文件)
  31. store, err := storage.NewStorage("devices.json")
  32. if err != nil {
  33. log.Printf("Warning: failed to initialize storage: %v", err)
  34. }
  35. app := &App{
  36. config: cfg,
  37. builder: topology.NewBuilder(),
  38. storage: store,
  39. tasks: make(map[string]*models.ScanTask),
  40. }
  41. // 从数据库加载设备到拓扑构建器
  42. if store != nil {
  43. devices, err := store.GetAllDevices()
  44. if err != nil {
  45. log.Printf("Warning: failed to load devices from database: %v", err)
  46. } else {
  47. log.Printf("Loaded %d devices from storage", len(devices))
  48. for _, dev := range devices {
  49. app.builder.AddDevice(dev)
  50. }
  51. }
  52. }
  53. return app
  54. }
  55. // Start 启动应用
  56. func (app *App) Start() error {
  57. // 设置路由
  58. mux := http.NewServeMux()
  59. // 静态文件服务 - 使用文件系统而非embed
  60. webDir := getWebDir()
  61. if _, err := os.Stat(webDir); err == nil {
  62. mux.Handle("/", http.FileServer(http.Dir(webDir)))
  63. } else {
  64. log.Printf("警告: web目录不存在,静态文件服务不可用")
  65. mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  66. w.Write([]byte("<h1>网络拓扑发现系统</h1><p>Web界面文件未找到</p>"))
  67. })
  68. }
  69. // API路由
  70. mux.HandleFunc("/api/scan", app.handleScan)
  71. mux.HandleFunc("/api/scan/{id}", app.handleScanProgress)
  72. mux.HandleFunc("/api/topology", app.handleTopology)
  73. mux.HandleFunc("/api/devices", app.handleGetDevices)
  74. mux.HandleFunc("/api/device", app.handleAddDevice)
  75. mux.HandleFunc("/api/device/{id}", app.handleDeviceDetail)
  76. addr := fmt.Sprintf("%s:%d", app.config.Web.Host, app.config.Web.Port)
  77. app.httpServer = &http.Server{
  78. Addr: addr,
  79. Handler: mux,
  80. }
  81. log.Printf("服务启动在 %s", addr)
  82. return app.httpServer.ListenAndServe()
  83. }
  84. // 生成唯一ID
  85. func generateID() string {
  86. b := make([]byte, 16)
  87. rand.Read(b)
  88. return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
  89. }
  90. // getWebDir 获取web目录路径
  91. func getWebDir() string {
  92. // 尝试多个可能的路径
  93. possiblePaths := []string{
  94. "web",
  95. filepath.Join("cmd", "web"),
  96. filepath.Join("..", "web"),
  97. }
  98. for _, path := range possiblePaths {
  99. if _, err := os.Stat(path); err == nil {
  100. absPath, _ := filepath.Abs(path)
  101. return absPath
  102. }
  103. }
  104. // 默认返回web
  105. return "web"
  106. }
  107. // 处理扫描请求
  108. func (app *App) handleScan(w http.ResponseWriter, r *http.Request) {
  109. if r.Method != http.MethodPost {
  110. http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
  111. return
  112. }
  113. var req struct {
  114. ScanRange string `json:"scan_range"`
  115. SSHPort int `json:"ssh_port"`
  116. Username string `json:"username"`
  117. Password string `json:"password"`
  118. }
  119. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  120. http.Error(w, err.Error(), http.StatusBadRequest)
  121. return
  122. }
  123. if req.SSHPort == 0 {
  124. req.SSHPort = 22
  125. }
  126. // 创建扫描任务
  127. taskID := generateID()
  128. task := &models.ScanTask{
  129. ID: taskID,
  130. Status: "running",
  131. StartTime: time.Now(),
  132. Devices: []models.Device{},
  133. }
  134. app.mu.Lock()
  135. app.tasks[taskID] = task
  136. app.mu.Unlock()
  137. // 异步执行扫描
  138. go app.runScan(task, req.ScanRange, req.SSHPort, req.Username, req.Password)
  139. w.Header().Set("Content-Type", "application/json")
  140. json.NewEncoder(w).Encode(map[string]string{"task_id": taskID})
  141. }
  142. // 执行扫描
  143. func (app *App) runScan(task *models.ScanTask, cidr string, sshPort int, username, password string) {
  144. defer func() {
  145. task.EndTime = time.Now()
  146. }()
  147. // 创建扫描器
  148. sc := scanner.NewScanner(app.config.Scanner.Concurrency, time.Duration(app.config.Scanner.Timeout)*time.Second)
  149. // 扫描SSH主机
  150. sshHosts, err := sc.ScanAndDiscover(cidr, sshPort)
  151. if err != nil {
  152. task.Status = "failed"
  153. task.ErrorMessage = err.Error()
  154. return
  155. }
  156. task.TotalDevices = len(sshHosts)
  157. // 采集设备信息
  158. var devices []models.Device
  159. for i, ip := range sshHosts {
  160. // 尝试不同设备类型
  161. deviceTypes := []models.DeviceType{
  162. models.DeviceTypeCisco,
  163. models.DeviceTypeHuawei,
  164. models.DeviceTypeH3C,
  165. models.DeviceTypeASA,
  166. models.DeviceTypeLinux,
  167. models.DeviceTypeWindows,
  168. }
  169. var discoveredDevice *models.Device
  170. for _, dtype := range deviceTypes {
  171. dev, err := device.DiscoverDevice(ip, dtype, username, password)
  172. if err == nil && dev.ScanStatus == "success" {
  173. discoveredDevice = dev
  174. break
  175. }
  176. }
  177. if discoveredDevice != nil {
  178. devices = append(devices, *discoveredDevice)
  179. app.builder.AddDevice(*discoveredDevice)
  180. // 保存到数据库
  181. if app.storage != nil {
  182. if err := app.storage.SaveDevice(discoveredDevice); err != nil {
  183. log.Printf("Warning: failed to save device %s to database: %v", ip, err)
  184. }
  185. }
  186. }
  187. // 更新进度
  188. task.ScannedDevices = i + 1
  189. task.Progress = (i + 1) * 100 / len(sshHosts)
  190. task.Devices = devices
  191. }
  192. task.Status = "completed"
  193. task.Progress = 100
  194. task.Devices = devices
  195. }
  196. // 处理扫描进度查询
  197. func (app *App) handleScanProgress(w http.ResponseWriter, r *http.Request) {
  198. id := r.PathValue("id")
  199. app.mu.RLock()
  200. task, exists := app.tasks[id]
  201. app.mu.RUnlock()
  202. if !exists {
  203. http.Error(w, "Task not found", http.StatusNotFound)
  204. return
  205. }
  206. w.Header().Set("Content-Type", "application/json")
  207. json.NewEncoder(w).Encode(task)
  208. }
  209. // 处理拓扑查询
  210. func (app *App) handleTopology(w http.ResponseWriter, r *http.Request) {
  211. graph := app.builder.Build()
  212. w.Header().Set("Content-Type", "application/json")
  213. json.NewEncoder(w).Encode(graph)
  214. }
  215. // 处理获取所有设备
  216. func (app *App) handleGetDevices(w http.ResponseWriter, r *http.Request) {
  217. var devices []models.Device
  218. // 优先从存储获取
  219. if app.storage != nil {
  220. var err error
  221. devices, err = app.storage.GetAllDevices()
  222. if err != nil {
  223. log.Printf("Error: failed to get devices from storage: %v", err)
  224. // 降级到 builder获取
  225. devices = app.builder.GetDevices()
  226. }
  227. log.Printf("Returning %d devices from storage", len(devices))
  228. } else {
  229. devices = app.builder.GetDevices()
  230. log.Printf("Returning %d devices from builder", len(devices))
  231. }
  232. w.Header().Set("Content-Type", "application/json")
  233. json.NewEncoder(w).Encode(devices)
  234. }
  235. // 处理添加设备
  236. func (app *App) handleAddDevice(w http.ResponseWriter, r *http.Request) {
  237. if r.Method != http.MethodPost {
  238. http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
  239. return
  240. }
  241. var req struct {
  242. IP string `json:"ip"`
  243. Type string `json:"type"`
  244. Username string `json:"username"`
  245. Password string `json:"password"`
  246. }
  247. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  248. http.Error(w, err.Error(), http.StatusBadRequest)
  249. return
  250. }
  251. deviceType := models.DeviceType(req.Type)
  252. log.Printf("Adding device: %s (type: %s)", req.IP, req.Type)
  253. dev, err := device.DiscoverDevice(req.IP, deviceType, req.Username, req.Password)
  254. if err != nil {
  255. log.Printf("Failed to discover device %s: %v", req.IP, err)
  256. w.Header().Set("Content-Type", "application/json")
  257. w.WriteHeader(http.StatusInternalServerError)
  258. json.NewEncoder(w).Encode(map[string]string{"message": err.Error()})
  259. return
  260. }
  261. log.Printf("Device discovered: %s, interfaces: %d, neighbors: %d",
  262. dev.IP, len(dev.Interfaces), len(dev.Neighbors))
  263. app.builder.AddDevice(*dev)
  264. // 保存到存储
  265. if app.storage != nil {
  266. if err := app.storage.SaveDevice(dev); err != nil {
  267. log.Printf("Error: failed to save device %s to storage: %v", req.IP, err)
  268. } else {
  269. log.Printf("Device %s saved to storage successfully", req.IP)
  270. }
  271. }
  272. w.Header().Set("Content-Type", "application/json")
  273. json.NewEncoder(w).Encode(dev)
  274. }
  275. // 处理设备详情查询
  276. func (app *App) handleDeviceDetail(w http.ResponseWriter, r *http.Request) {
  277. id := r.PathValue("id")
  278. devices := app.builder.GetDevices()
  279. for _, dev := range devices {
  280. if dev.ID == id || dev.IP == id {
  281. w.Header().Set("Content-Type", "application/json")
  282. json.NewEncoder(w).Encode(dev)
  283. return
  284. }
  285. }
  286. http.Error(w, "Device not found", http.StatusNotFound)
  287. }
  288. func main() {
  289. // 加载配置
  290. configFile := "config.json"
  291. if len(os.Args) > 1 {
  292. configFile = os.Args[1]
  293. }
  294. var cfg *config.Config
  295. if _, err := os.Stat(configFile); err == nil {
  296. cfg, err = config.LoadConfig(configFile)
  297. if err != nil {
  298. log.Printf("加载配置文件失败: %v, 使用默认配置", err)
  299. cfg = config.DefaultConfig()
  300. }
  301. } else {
  302. log.Printf("配置文件不存在, 使用默认配置")
  303. cfg = config.DefaultConfig()
  304. }
  305. // 创建并启动应用
  306. app := NewApp(cfg)
  307. log.Println("网络拓扑发现系统启动...")
  308. if err := app.Start(); err != nil {
  309. log.Fatalf("服务启动失败: %v", err)
  310. }
  311. }