package httpserver import ( "context" "encoding/json" "fmt" "io" "net/http" "strings" "time" "quickip/internal/auth" "quickip/internal/config" "quickip/internal/deviceinfo" "quickip/internal/logger" "quickip/internal/model" applyexecsvc "quickip/internal/network/applyexec" configreadersvc "quickip/internal/network/configreader" interfacesvc "quickip/internal/network/interfaces" netplansvc "quickip/internal/network/netplan" validatorsvc "quickip/internal/network/validator" verifysvc "quickip/internal/network/verify" "quickip/internal/tasks" ) type Server struct { cfg config.Config log *logger.Logger deviceSvc *deviceinfo.Service interfaceSvc *interfacesvc.Service configSvc *configreadersvc.Service validatorSvc *validatorsvc.Service netplanSvc *netplansvc.Service applySvc *applyexecsvc.Service verifySvc *verifysvc.Service taskSvc *tasks.Service } func New(cfg config.Config, log *logger.Logger, deviceSvc *deviceinfo.Service, interfaceSvc *interfacesvc.Service, configSvc *configreadersvc.Service, validatorSvc *validatorsvc.Service, netplanSvc *netplansvc.Service, applySvc *applyexecsvc.Service, verifySvc *verifysvc.Service, taskSvc *tasks.Service) *Server { return &Server{cfg: cfg, log: log, deviceSvc: deviceSvc, interfaceSvc: interfaceSvc, configSvc: configSvc, validatorSvc: validatorSvc, netplanSvc: netplanSvc, applySvc: applySvc, verifySvc: verifySvc, taskSvc: taskSvc} } func (s *Server) Run(ctx context.Context) error { mux := http.NewServeMux() mux.Handle("/api/health", auth.Middleware(s.cfg, http.HandlerFunc(s.handleHealth))) mux.Handle("/api/device/info", auth.Middleware(s.cfg, http.HandlerFunc(s.handleDeviceInfo))) mux.Handle("/api/network/interfaces", auth.Middleware(s.cfg, http.HandlerFunc(s.handleInterfaces))) mux.Handle("/api/network/config", auth.Middleware(s.cfg, http.HandlerFunc(s.handleConfig))) mux.Handle("/api/network/validate", auth.Middleware(s.cfg, http.HandlerFunc(s.handleValidate))) mux.Handle("/api/network/apply", auth.Middleware(s.cfg, http.HandlerFunc(s.handleApply))) mux.Handle("/api/network/rollback", auth.Middleware(s.cfg, http.HandlerFunc(s.handleRollback))) mux.Handle("/api/tasks/", auth.Middleware(s.cfg, http.HandlerFunc(s.handleTaskGet))) handler := s.withAccessLog(mux) server := &http.Server{ Addr: fmt.Sprintf("%s:%d", s.cfg.HTTPHost, s.cfg.HTTPPort), Handler: handler, ReadHeaderTimeout: 5 * time.Second, } go func() { <-ctx.Done() shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() _ = server.Shutdown(shutdownCtx) }() s.log.Info("http server listening", "addr", server.Addr) err := server.ListenAndServe() if err == http.ErrServerClosed { return nil } return err } func (s *Server) withAccessLog(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { started := time.Now() rw := &statusRecorder{ResponseWriter: w, statusCode: http.StatusOK} next.ServeHTTP(rw, r) s.log.Info( "http request completed", "method", r.Method, "path", r.URL.Path, "query", r.URL.RawQuery, "remote", r.RemoteAddr, "status", rw.statusCode, "duration_ms", time.Since(started).Milliseconds(), ) }) } type statusRecorder struct { http.ResponseWriter statusCode int } func (r *statusRecorder) WriteHeader(statusCode int) { r.statusCode = statusCode r.ResponseWriter.WriteHeader(statusCode) } func (s *Server) handleHealth(w http.ResponseWriter, _ *http.Request) { writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "成功", Data: map[string]any{"status": "运行中", "agent_version": s.cfg.AgentVersion}}) } func (s *Server) handleDeviceInfo(w http.ResponseWriter, _ *http.Request) { writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "成功", Data: s.deviceSvc.Get()}) } func (s *Server) handleInterfaces(w http.ResponseWriter, _ *http.Request) { data, err := s.interfaceSvc.List() if err != nil { writeJSON(w, http.StatusInternalServerError, model.APIResponse{Code: 4001, Message: "系统执行失败", Data: map[string]string{"error": err.Error()}}) return } writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "成功", Data: data}) } func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) { interfaceName := r.URL.Query().Get("interface") if interfaceName == "" { writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": []string{"缺少 interface 参数。"}}}) return } if !s.interfaceExists(interfaceName) { writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": []string{"目标接口不存在。"}}}) return } data, err := s.configSvc.Read(interfaceName) if err != nil { writeJSON(w, http.StatusInternalServerError, model.APIResponse{Code: 4001, Message: "系统执行失败", Data: map[string]string{"error": err.Error()}}) return } writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "成功", Data: data}) } func (s *Server) handleValidate(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeJSON(w, http.StatusMethodNotAllowed, model.APIResponse{Code: 2002, Message: "资源不存在", Data: nil}) return } body, err := io.ReadAll(r.Body) if err != nil { writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": []string{"请求体读取失败。"}}}) return } var input model.InterfaceConfig if err := json.Unmarshal(body, &input); err != nil { writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": []string{"请求体格式不正确。"}}}) return } if input.Interface != "" && !s.interfaceExists(input.Interface) { result := model.ValidateResponse{Valid: false, Errors: []string{"目标接口不存在。"}, Warnings: []string{}} writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 3001, Message: "配置校验失败", Data: result}) return } result := s.validatorSvc.Validate(input) if !result.Valid { writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 3001, Message: "配置校验失败", Data: result}) return } writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "校验通过", Data: result}) } func (s *Server) handleApply(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeJSON(w, http.StatusMethodNotAllowed, model.APIResponse{Code: 2002, Message: "资源不存在", Data: nil}) return } var input model.InterfaceConfig if err := json.NewDecoder(r.Body).Decode(&input); err != nil { writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": []string{"请求体格式不正确。"}}}) return } if !s.interfaceExists(input.Interface) { writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 3001, Message: "配置校验失败", Data: model.ValidateResponse{Valid: false, Errors: []string{"目标接口不存在。"}}}) return } result := s.validatorSvc.Validate(input) if !result.Valid { writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 3001, Message: "配置校验失败", Data: result}) return } management := s.currentManagementInterface() if management == "" { writeJSON(w, http.StatusInternalServerError, model.APIResponse{Code: 4001, Message: "系统执行失败", Data: map[string][]string{"errors": []string{"未能识别管理接口。"}}}) return } task := s.taskSvc.Create() go s.runApplyTask(task.TaskID, input, management) writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "配置任务已提交", Data: map[string]any{"interface": input.Interface, "task_id": task.TaskID}}) } func (s *Server) handleRollback(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeJSON(w, http.StatusMethodNotAllowed, model.APIResponse{Code: 2002, Message: "资源不存在", Data: nil}) return } var input model.RollbackRequest if err := json.NewDecoder(r.Body).Decode(&input); err != nil { writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": {"请求体格式不正确。"}}}) return } filePath, err := s.netplanSvc.FindSingleFile() if err != nil { writeJSON(w, http.StatusInternalServerError, model.APIResponse{Code: 4001, Message: "系统执行失败", Data: map[string]string{"error": err.Error()}}) return } backupPath := filePath + ".quickip.bak" if err := s.netplanSvc.Restore(filePath, backupPath); err != nil { writeJSON(w, http.StatusInternalServerError, model.APIResponse{Code: 3004, Message: "回滚失败", Data: map[string]string{"error": err.Error()}}) return } if err := s.applySvc.Apply(); err != nil { writeJSON(w, http.StatusInternalServerError, model.APIResponse{Code: 3004, Message: "回滚失败", Data: map[string]string{"error": err.Error()}}) return } writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "回滚成功", Data: map[string]any{"interface": input.Interface, "rolled_back": true}}) } func (s *Server) handleTaskGet(w http.ResponseWriter, r *http.Request) { taskID := strings.TrimPrefix(r.URL.Path, "/api/tasks/") if taskID == "" { writeJSON(w, http.StatusBadRequest, model.APIResponse{Code: 2001, Message: "参数错误", Data: map[string][]string{"errors": []string{"缺少 task_id。"}}}) return } item, ok := s.taskSvc.Get(taskID) if !ok { writeJSON(w, http.StatusNotFound, model.APIResponse{Code: 2002, Message: "资源不存在", Data: nil}) return } writeJSON(w, http.StatusOK, model.APIResponse{Code: 0, Message: "成功", Data: item}) } func (s *Server) runApplyTask(taskID string, input model.InterfaceConfig, managementInterface string) { s.taskSvc.Update(taskID, "running", "validating", "正在校验配置。", false) result := s.validatorSvc.Validate(input) if !result.Valid { s.taskSvc.Update(taskID, "failed", "validating", "配置校验失败。", false) return } filePath, err := s.netplanSvc.FindSingleFile() if err != nil { s.taskSvc.Update(taskID, "failed", "writing_netplan", err.Error(), false) return } s.taskSvc.Update(taskID, "running", "writing_netplan", "正在写入 netplan 配置。", false) backupPath, err := s.netplanSvc.Backup(filePath) if err != nil { s.taskSvc.Update(taskID, "failed", "writing_netplan", err.Error(), false) return } if err := s.netplanSvc.Write(filePath, input.Interface, input, managementInterface, s.cfg.MaintenanceCIDR); err != nil { s.taskSvc.Update(taskID, "failed", "writing_netplan", err.Error(), false) return } s.taskSvc.Update(taskID, "running", "applying", "正在应用 netplan 配置。", false) if err := s.applySvc.Apply(); err != nil { _ = s.netplanSvc.Restore(filePath, backupPath) _ = s.applySvc.Apply() s.taskSvc.Update(taskID, "rolled_back", "rolling_back", "配置失败,已自动回滚。", true) return } s.taskSvc.Update(taskID, "running", "verifying", "正在验证配置结果。", false) if err := s.verifySvc.Verify(input); err != nil { _ = s.netplanSvc.Restore(filePath, backupPath) _ = s.applySvc.Apply() s.taskSvc.Update(taskID, "rolled_back", "rolling_back", "配置失败,已自动回滚。", true) return } s.taskSvc.Update(taskID, "success", "completed", "目标接口配置已成功应用。", false) } func (s *Server) interfaceExists(name string) bool { data, err := s.interfaceSvc.List() if err != nil { return false } for _, item := range data.Interfaces { if item.SystemName == name { return true } } return false } func (s *Server) currentManagementInterface() string { data, err := s.interfaceSvc.List() if err != nil { return "" } return data.ManagementInterface } func writeJSON(w http.ResponseWriter, status int, payload model.APIResponse) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(payload) }