| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344 |
- package main
- import (
- "archive/tar"
- "compress/gzip"
- "context"
- "crypto/md5"
- "encoding/hex"
- "encoding/json"
- "fmt"
- "io"
- "os"
- "path"
- "path/filepath"
- "strings"
- "sync"
- "time"
- "hnyfkj.com.cn/rtu/linux/baseapp"
- "hnyfkj.com.cn/rtu/linux/utils/jsonrpc2"
- )
- type PkgInfo struct {
- FileName string `json:"fileName"`
- FileSize int64 `json:"fileSize"`
- FileMD5Hash string `json:"fileHash"`
- UploadTime string `json:"uploadTime"`
- }
- type AppInfo struct {
- Name string `json:"name"`
- Version string `json:"version"`
- InstallPath string `json:"installPath"`
- LibraryPath string `json:"libraryPath"`
- Executable string `json:"executable"`
- ReadmeFile string `json:"readmeFile"`
- Description string `json:"description"`
- }
- type InsInfo struct {
- Installed bool `json:"installed"`
- InstallTime string `json:"installTime"`
- }
- type AppIns struct {
- Pkg PkgInfo `json:"pkg"` // 应用的安装包
- App AppInfo `json:"app"` // 应用详细信息
- Ins InsInfo `json:"ins"` // 应用是否安装
- }
- var appMutex sync.Mutex
- const ErrConflict jsonrpc2.ErrCode = -32099 // 资源访问冲突, 拒绝执行
- // 导入用户安装包
- func pkgImport(ctx context.Context, req *jsonrpc2.Request) *jsonrpc2.Response {
- appMutex.Lock()
- defer appMutex.Unlock()
- var params map[string]string
- if err := json.Unmarshal(req.Params, ¶ms); err != nil {
- return jsonrpc2.BuildError(req, jsonrpc2.ErrInvalidParams, err.Error())
- }
- file, ok := params["file"]
- if !ok || file == "" {
- return jsonrpc2.BuildError(req, jsonrpc2.ErrInvalidParams, "missing or empty 'file' parameter")
- }
- if !strings.HasSuffix(strings.ToLower(filepath.Base(file)), ".tar.gz") {
- return jsonrpc2.BuildError(req, jsonrpc2.ErrInvalidParams, "invalid install package format")
- }
- info, err := os.Stat(file)
- if err != nil {
- return jsonrpc2.BuildError(req, jsonrpc2.ErrInvalidParams, err.Error())
- }
- if !info.Mode().IsRegular() {
- return jsonrpc2.BuildError(req, jsonrpc2.ErrInvalidParams, "invalid install package file")
- }
- if err := os.MkdirAll(userAppDir, 0755); err != nil {
- return jsonrpc2.BuildError(req, jsonrpc2.ErrInternal, err.Error())
- }
- if hasImportedPackage(userAppDir) {
- return jsonrpc2.BuildError(req, ErrConflict, "application package already exists")
- }
- app, err := readAppJSON(file)
- if err != nil {
- return jsonrpc2.BuildError(req, jsonrpc2.ErrInvalidParams, err.Error())
- }
- hash, err := fileMD5(file)
- if err != nil {
- return jsonrpc2.BuildError(req, jsonrpc2.ErrInternal, err.Error())
- }
- savePkgFile := filepath.Join(userAppDir, filepath.Base(file))
- if err := os.Rename(file, savePkgFile); err != nil {
- return jsonrpc2.BuildError(req, jsonrpc2.ErrInternal, err.Error())
- }
- appIns := AppIns{
- Pkg: PkgInfo{
- FileName: filepath.Base(file),
- FileSize: info.Size(),
- FileMD5Hash: hash,
- UploadTime: time.Now().Format("2006-01-02 15:04:05"),
- },
- App: app,
- Ins: InsInfo{
- Installed: false,
- },
- }
- data, err := json.MarshalIndent(appIns, "", " ")
- if err != nil {
- os.Remove(savePkgFile)
- return jsonrpc2.BuildError(req, jsonrpc2.ErrInternal, err.Error())
- }
- appInsFile := filepath.Join(userAppDir, "appins.json")
- if err := writeFileAtomic(appInsFile, data, 0644); err != nil {
- os.Remove(savePkgFile)
- return jsonrpc2.BuildError(req, jsonrpc2.ErrInternal, err.Error())
- }
- baseapp.Logger.Infof("[PkgImport] 导入用户应用安装包成功: %s", savePkgFile)
- return jsonrpc2.BuildResponse(req, "success", nil)
- }
- // 删除用户安装包
- func pkgRemove(ctx context.Context, req *jsonrpc2.Request) *jsonrpc2.Response {
- appMutex.Lock()
- defer appMutex.Unlock()
- appInsFile := filepath.Join(userAppDir, "appins.json")
- data, err := os.ReadFile(appInsFile)
- if err == nil {
- var appIns AppIns
- if err := json.Unmarshal(data, &appIns); err == nil {
- if appIns.Ins.Installed { // 用户应用已安装
- return jsonrpc2.BuildError(req, ErrConflict, "application is installed, please uninstall it first")
- }
- }
- } else if !os.IsNotExist(err) {
- return jsonrpc2.BuildError(req, jsonrpc2.ErrInternal, err.Error())
- }
- if err := os.RemoveAll(userAppDir); err != nil {
- return jsonrpc2.BuildError(req, jsonrpc2.ErrInternal, err.Error())
- }
- if err := os.MkdirAll(userAppDir, 0755); err != nil {
- return jsonrpc2.BuildError(req, jsonrpc2.ErrInternal, err.Error())
- }
- baseapp.Logger.Infof("[PkgRemove] 删除用户应用安装包成功")
- return jsonrpc2.BuildResponse(req, "success", nil)
- }
- // 安装包应用安装
- func appInstall(ctx context.Context, req *jsonrpc2.Request) *jsonrpc2.Response {
- appMutex.Lock()
- defer appMutex.Unlock()
- return nil
- }
- // 卸载已安装应用
- func appUninstall(ctx context.Context, req *jsonrpc2.Request) *jsonrpc2.Response {
- appMutex.Lock()
- defer appMutex.Unlock()
- return nil
- }
- // 安装包应用信息
- func getInstallInfo(ctx context.Context, req *jsonrpc2.Request) *jsonrpc2.Response {
- appInsFile := filepath.Join(userAppDir, "appins.json")
- data, err := os.ReadFile(appInsFile)
- if err != nil {
- if os.IsNotExist(err) {
- return jsonrpc2.BuildResponse(req, nil, nil)
- }
- return jsonrpc2.BuildError(req, jsonrpc2.ErrInternal, err.Error())
- }
- var appIns AppIns
- if err := json.Unmarshal(data, &appIns); err != nil {
- return jsonrpc2.BuildError(req, jsonrpc2.ErrInternal, err.Error())
- }
- return jsonrpc2.BuildResponse(req, appIns, nil)
- }
- func readAppJSON(file string) (AppInfo, error) {
- var app AppInfo
- f, err := os.Open(file)
- if err != nil {
- return app, err
- }
- defer f.Close()
- gz, err := gzip.NewReader(f)
- if err != nil {
- return app, err
- }
- defer gz.Close()
- tr := tar.NewReader(gz)
- for {
- header, err := tr.Next()
- if err == io.EOF {
- break
- }
- if err != nil {
- return app, err
- }
- if header.Typeflag != tar.TypeReg {
- continue
- }
- if path.Base(header.Name) != "app.json" {
- continue
- }
- if header.Size > 2<<20 { // 2MB 上限
- return app, fmt.Errorf("app.json 文件过大")
- }
- data, err := io.ReadAll(io.LimitReader(tr, header.Size))
- if err != nil {
- return app, err
- }
- err = json.Unmarshal(data, &app)
- if err != nil {
- return app, err
- }
- if app.Name == "" || app.Version == "" || app.InstallPath == "" ||
- app.Executable == "" || app.ReadmeFile == "" {
- return app, fmt.Errorf("app.json 参数不完整")
- }
- if !filepath.IsAbs(app.InstallPath) {
- return app, fmt.Errorf("installPath必须是绝对路径")
- }
- clean := filepath.Clean(app.InstallPath)
- if clean != app.InstallPath {
- return app, fmt.Errorf("installPath路径非法")
- }
- return app, nil
- }
- return app, fmt.Errorf("安装包中未找到 app.json")
- }
- func fileMD5(file string) (string, error) {
- f, err := os.Open(file)
- if err != nil {
- return "", err
- }
- defer f.Close()
- h := md5.New()
- if _, err := io.Copy(h, f); err != nil {
- return "", err
- }
- return hex.EncodeToString(h.Sum(nil)), nil
- }
- func isRegularFile(name string) bool {
- info, err := os.Stat(name)
- if err != nil {
- return false
- }
- return info.Mode().IsRegular()
- }
- func hasImportedPackage(dir string) bool {
- if isRegularFile(filepath.Join(dir, "appins.json")) {
- return true
- }
- entries, err := os.ReadDir(dir)
- if err != nil {
- return false
- }
- for _, entry := range entries {
- if entry.IsDir() {
- continue
- }
- if strings.HasSuffix(strings.ToLower(entry.Name()), ".tar.gz") {
- return true
- }
- }
- return false
- }
- func writeFileAtomic(name string, data []byte, perm os.FileMode) error {
- tmp := name + ".tmp"
- defer os.Remove(tmp)
- f, err := os.OpenFile(tmp, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, perm)
- if err != nil {
- return err
- }
- if _, err := f.Write(data); err != nil {
- f.Close()
- return err
- }
- if err := f.Sync(); err != nil {
- f.Close()
- return err
- }
- if err := f.Close(); err != nil {
- return err
- }
- return os.Rename(tmp, name)
- }
|