package main import ( "encoding/json" "fmt" "io" "net/http" "os" "os/exec" "path/filepath" "strings" "time" "hnyfkj.com.cn/rtu/linux/baseapp" ) const maxUploadSize = 1 << 30 // 1GB func downloadAppInstallLog(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } filename := fmt.Sprintf("app-install.log.%s.tar.gz", time.Now().Format("20060102150405")) tmpFile := fmt.Sprintf("/tmp/%d.tar.gz", time.Now().UnixNano()) defer os.Remove(tmpFile) cmd := exec.Command( "tar", "--warning=no-file-changed", "-czf", tmpFile, "-C", "/opt/yfkj/app-install.service/log", ".", ) out, err := cmd.CombinedOutput() if err != nil { if exitErr, ok := err.(*exec.ExitError); !ok || exitErr.ExitCode() != 1 { http.Error(w, string(out), http.StatusInternalServerError) return } } w.Header().Set("Content-Type", "application/gzip") w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename)) http.ServeFile(w, r, tmpFile) } func restartAppInstallService(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } cmd := exec.Command("systemctl", "restart", "yfkj-app-install.service") if out, err := cmd.CombinedOutput(); err != nil { http.Error(w, string(out)+err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.Write([]byte(`{"status":"ok"}`)) } func getUserAppStatus() (string, string) { status := noValue pid := noValue out, err := exec.Command("systemctl", "show", "yfkj-user-app.service", "-p", "LoadState", "-p", "ActiveState", "-p", "SubState", "-p", "MainPID").Output() if err != nil { return status, pid } v := string(out) switch { case strings.Contains(v, "ActiveState=active") && strings.Contains(v, "SubState=running"): status = "🟢运行中" case strings.Contains(v, "ActiveState=active") && strings.Contains(v, "SubState=exited"): status = "🔵已完成" case strings.Contains(v, "LoadState=loaded") && strings.Contains(v, "ActiveState=inactive"): status = "🟡已停止" } for _, line := range strings.Split(v, "\n") { if after, ok := strings.CutPrefix(line, "MainPID="); ok { if after != "" && after != "0" { pid = after } break } } return status, pid } func appInstallStatusHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } var info *struct { Pkg struct { FileName string `json:"fileName"` FileSize int64 `json:"fileSize"` FileMD5 string `json:"fileMD5"` UploadTime string `json:"uploadTime"` } `json:"pkg"` App struct { Name string `json:"name"` Version string `json:"version"` Executable string `json:"executable"` Description string `json:"description"` ReadmeFile string `json:"readmeFile"` } `json:"app"` Ins struct { Installed bool `json:"installed"` InstallTime string `json:"installTime"` } `json:"ins"` } callErr := callRPCResult(r.Context(), 7004, "core.getInstallInfo", nil, &info) data := map[string]string{ "state": "0", "avar1": noValue, "avar2": noValue, "avar3": noValue, "avar4": noValue, "bvar1": noValue, "bvar2": noValue, "bvar3": noValue, "bvar4": noValue, "bvar5": noValue, "cvar1": noValue, "cvar2": noValue, "cvar3": noValue, "cvar4": noValue, "cvar5": noValue, } if callErr == nil { data["avar1"] = "🟢正常" if info != nil { data["bvar1"] = info.Pkg.FileName data["bvar2"] = fmt.Sprintf("%d 字节", info.Pkg.FileSize) data["bvar3"] = info.Pkg.FileMD5 data["bvar4"] = info.Pkg.UploadTime data["cvar1"] = info.App.Name data["cvar2"] = info.App.Version data["cvar3"] = info.App.Executable data["cvar4"] = info.Ins.InstallTime data["cvar5"] = info.App.ReadmeFile if info.Ins.Installed { data["state"] = "2" data["avar2"] = info.App.Name data["avar3"], data["avar4"] = getUserAppStatus() data["bvar5"] = "已安装" } else { data["state"] = "1" data["bvar5"] = "未安装" } } } w.Header().Set("Content-Type", "application/json; charset=utf-8") json.NewEncoder(w).Encode(data) } func uploadUserAppPkg(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize) mr, err := r.MultipartReader() if err != nil { http.Error(w, "解析上传请求失败: "+err.Error(), http.StatusBadRequest) return } for { part, err := mr.NextPart() if err == io.EOF { break } if err != nil { http.Error(w, "读取上传数据失败: "+err.Error(), http.StatusBadRequest) return } if part.FormName() != "file" { part.Close() continue } filename := filepath.Base(part.FileName()) if filename == "" || filename == "." { part.Close() http.Error(w, "文件名为空", http.StatusBadRequest) return } if !strings.HasSuffix(strings.ToLower(filename), ".tar.gz") { part.Close() http.Error(w, "只允许上传 tar.gz 类型的文件", http.StatusBadRequest) return } path := filepath.Join("/tmp", filename) err = saveUploadFile(part, path) part.Close() if err != nil { http.Error(w, "保存上传文件失败: "+err.Error(), http.StatusInternalServerError) return } size := int64(0) if stat, err := os.Stat(path); err == nil { size = stat.Size() } baseapp.Logger.Infof( "[上传文件成功] 上传文件名: %s, 上传文件大小: %d 字节, 本地存储路径: %s", filename, size, path) break } w.Header().Set("Content-Type", "application/json; charset=utf-8") json.NewEncoder(w).Encode(map[string]string{"result": "success"}) } func saveUploadFile(src io.Reader, path string) error { dst, err := os.Create(path) if err != nil { return err } defer dst.Close() _, err = io.Copy(dst, src) if err != nil { os.Remove(path) return err } return nil } func appPkgImportHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } var params struct { File string `json:"file"` } if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } if params.File == "" { http.Error(w, "missing file", http.StatusBadRequest) return } callErr := callRPCResult(r.Context(), 7004, "core.package.import", map[string]string{"file": params.File}, nil) if callErr != nil { http.Error(w, callErr.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json; charset=utf-8") json.NewEncoder(w).Encode(map[string]string{"result": "success"}) } func appPkgRemoveHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } callErr := callRPCResult(r.Context(), 7004, "core.package.remove", nil, nil) if callErr != nil { http.Error(w, callErr.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json; charset=utf-8") json.NewEncoder(w).Encode(map[string]string{"result": "success"}) } func appInstallHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } callErr := callRPCResult(r.Context(), 7004, "core.app.install", nil, nil) if callErr != nil { http.Error(w, callErr.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json; charset=utf-8") json.NewEncoder(w).Encode(map[string]string{"result": "success"}) } func appUninstallHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } callErr := callRPCResult(r.Context(), 7004, "core.app.uninstall", nil, nil) if callErr != nil { http.Error(w, callErr.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json; charset=utf-8") json.NewEncoder(w).Encode(map[string]string{"result": "success"}) } func restartUserAppService(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } cmd := exec.Command("systemctl", "restart", "yfkj-user-app.service") if out, err := cmd.CombinedOutput(); err != nil { http.Error(w, string(out)+err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.Write([]byte(`{"status":"ok"}`)) } var userAppPkgDir = "/opt/yfkj/app-install.service/user-app-pkg" func downloadUserAppPkg(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } var info struct { Pkg struct { FileName string `json:"fileName"` } `json:"pkg"` } data, err := os.ReadFile(filepath.Join(userAppPkgDir, "appins.json")) if err != nil { http.Error(w, "用户应用安装包不存在", http.StatusNotFound) return } if err := json.Unmarshal(data, &info); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if info.Pkg.FileName == "" { http.Error(w, "用户应用安装包不存在", http.StatusNotFound) return } filename := filepath.Base(info.Pkg.FileName) file := filepath.Join(userAppPkgDir, filename) stat, err := os.Stat(file) if err != nil || !stat.Mode().IsRegular() { http.Error(w, "用户应用安装包不存在", http.StatusNotFound) return } w.Header().Set("Content-Type", "application/gzip") w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename)) http.ServeFile(w, r, file) }