rpc_handler.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. package main
  2. import (
  3. "archive/tar"
  4. "compress/gzip"
  5. "context"
  6. "crypto/md5"
  7. "encoding/hex"
  8. "encoding/json"
  9. "fmt"
  10. "io"
  11. "os"
  12. "path"
  13. "path/filepath"
  14. "strings"
  15. "sync"
  16. "time"
  17. "hnyfkj.com.cn/rtu/linux/baseapp"
  18. "hnyfkj.com.cn/rtu/linux/utils/jsonrpc2"
  19. )
  20. type PkgInfo struct {
  21. FileName string `json:"fileName"`
  22. FileSize int64 `json:"fileSize"`
  23. FileMD5Hash string `json:"fileHash"`
  24. UploadTime string `json:"uploadTime"`
  25. }
  26. type AppInfo struct {
  27. Name string `json:"name"`
  28. Version string `json:"version"`
  29. InstallPath string `json:"installPath"`
  30. LibraryPath string `json:"libraryPath"`
  31. Executable string `json:"executable"`
  32. ReadmeFile string `json:"readmeFile"`
  33. Description string `json:"description"`
  34. }
  35. type InsInfo struct {
  36. Installed bool `json:"installed"`
  37. InstallTime string `json:"installTime"`
  38. }
  39. type AppIns struct {
  40. Pkg PkgInfo `json:"pkg"` // 应用的安装包
  41. App AppInfo `json:"app"` // 应用详细信息
  42. Ins InsInfo `json:"ins"` // 应用是否安装
  43. }
  44. var appMutex sync.Mutex
  45. const ErrConflict jsonrpc2.ErrCode = -32099 // 资源访问冲突, 拒绝执行
  46. // 导入用户安装包
  47. func pkgImport(ctx context.Context, req *jsonrpc2.Request) *jsonrpc2.Response {
  48. appMutex.Lock()
  49. defer appMutex.Unlock()
  50. var params map[string]string
  51. if err := json.Unmarshal(req.Params, &params); err != nil {
  52. return jsonrpc2.BuildError(req, jsonrpc2.ErrInvalidParams, err.Error())
  53. }
  54. file, ok := params["file"]
  55. if !ok || file == "" {
  56. return jsonrpc2.BuildError(req, jsonrpc2.ErrInvalidParams, "missing or empty 'file' parameter")
  57. }
  58. if !strings.HasSuffix(strings.ToLower(filepath.Base(file)), ".tar.gz") {
  59. return jsonrpc2.BuildError(req, jsonrpc2.ErrInvalidParams, "invalid install package format")
  60. }
  61. info, err := os.Stat(file)
  62. if err != nil {
  63. return jsonrpc2.BuildError(req, jsonrpc2.ErrInvalidParams, err.Error())
  64. }
  65. if !info.Mode().IsRegular() {
  66. return jsonrpc2.BuildError(req, jsonrpc2.ErrInvalidParams, "invalid install package file")
  67. }
  68. if err := os.MkdirAll(userAppDir, 0755); err != nil {
  69. return jsonrpc2.BuildError(req, jsonrpc2.ErrInternal, err.Error())
  70. }
  71. if hasImportedPackage(userAppDir) {
  72. return jsonrpc2.BuildError(req, ErrConflict, "application package already exists")
  73. }
  74. app, err := readAppJSON(file)
  75. if err != nil {
  76. return jsonrpc2.BuildError(req, jsonrpc2.ErrInvalidParams, err.Error())
  77. }
  78. hash, err := fileMD5(file)
  79. if err != nil {
  80. return jsonrpc2.BuildError(req, jsonrpc2.ErrInternal, err.Error())
  81. }
  82. savePkgFile := filepath.Join(userAppDir, filepath.Base(file))
  83. if err := os.Rename(file, savePkgFile); err != nil {
  84. return jsonrpc2.BuildError(req, jsonrpc2.ErrInternal, err.Error())
  85. }
  86. appIns := AppIns{
  87. Pkg: PkgInfo{
  88. FileName: filepath.Base(file),
  89. FileSize: info.Size(),
  90. FileMD5Hash: hash,
  91. UploadTime: time.Now().Format("2006-01-02 15:04:05"),
  92. },
  93. App: app,
  94. Ins: InsInfo{
  95. Installed: false,
  96. },
  97. }
  98. data, err := json.MarshalIndent(appIns, "", " ")
  99. if err != nil {
  100. os.Remove(savePkgFile)
  101. return jsonrpc2.BuildError(req, jsonrpc2.ErrInternal, err.Error())
  102. }
  103. appInsFile := filepath.Join(userAppDir, "appins.json")
  104. if err := writeFileAtomic(appInsFile, data, 0644); err != nil {
  105. os.Remove(savePkgFile)
  106. return jsonrpc2.BuildError(req, jsonrpc2.ErrInternal, err.Error())
  107. }
  108. baseapp.Logger.Infof("[PkgImport] 导入用户应用安装包成功: %s", savePkgFile)
  109. return jsonrpc2.BuildResponse(req, "success", nil)
  110. }
  111. // 删除用户安装包
  112. func pkgRemove(ctx context.Context, req *jsonrpc2.Request) *jsonrpc2.Response {
  113. appMutex.Lock()
  114. defer appMutex.Unlock()
  115. appInsFile := filepath.Join(userAppDir, "appins.json")
  116. data, err := os.ReadFile(appInsFile)
  117. if err == nil {
  118. var appIns AppIns
  119. if err := json.Unmarshal(data, &appIns); err == nil {
  120. if appIns.Ins.Installed { // 用户应用已安装
  121. return jsonrpc2.BuildError(req, ErrConflict, "application is installed, please uninstall it first")
  122. }
  123. }
  124. } else if !os.IsNotExist(err) {
  125. return jsonrpc2.BuildError(req, jsonrpc2.ErrInternal, err.Error())
  126. }
  127. if err := os.RemoveAll(userAppDir); err != nil {
  128. return jsonrpc2.BuildError(req, jsonrpc2.ErrInternal, err.Error())
  129. }
  130. if err := os.MkdirAll(userAppDir, 0755); err != nil {
  131. return jsonrpc2.BuildError(req, jsonrpc2.ErrInternal, err.Error())
  132. }
  133. baseapp.Logger.Infof("[PkgRemove] 删除用户应用安装包成功")
  134. return jsonrpc2.BuildResponse(req, "success", nil)
  135. }
  136. // 安装包应用安装
  137. func appInstall(ctx context.Context, req *jsonrpc2.Request) *jsonrpc2.Response {
  138. appMutex.Lock()
  139. defer appMutex.Unlock()
  140. return nil
  141. }
  142. // 卸载已安装应用
  143. func appUninstall(ctx context.Context, req *jsonrpc2.Request) *jsonrpc2.Response {
  144. appMutex.Lock()
  145. defer appMutex.Unlock()
  146. return nil
  147. }
  148. // 安装包应用信息
  149. func getInstallInfo(ctx context.Context, req *jsonrpc2.Request) *jsonrpc2.Response {
  150. appInsFile := filepath.Join(userAppDir, "appins.json")
  151. data, err := os.ReadFile(appInsFile)
  152. if err != nil {
  153. if os.IsNotExist(err) {
  154. return jsonrpc2.BuildResponse(req, nil, nil)
  155. }
  156. return jsonrpc2.BuildError(req, jsonrpc2.ErrInternal, err.Error())
  157. }
  158. var appIns AppIns
  159. if err := json.Unmarshal(data, &appIns); err != nil {
  160. return jsonrpc2.BuildError(req, jsonrpc2.ErrInternal, err.Error())
  161. }
  162. return jsonrpc2.BuildResponse(req, appIns, nil)
  163. }
  164. func readAppJSON(file string) (AppInfo, error) {
  165. var app AppInfo
  166. f, err := os.Open(file)
  167. if err != nil {
  168. return app, err
  169. }
  170. defer f.Close()
  171. gz, err := gzip.NewReader(f)
  172. if err != nil {
  173. return app, err
  174. }
  175. defer gz.Close()
  176. tr := tar.NewReader(gz)
  177. for {
  178. header, err := tr.Next()
  179. if err == io.EOF {
  180. break
  181. }
  182. if err != nil {
  183. return app, err
  184. }
  185. if header.Typeflag != tar.TypeReg {
  186. continue
  187. }
  188. if path.Base(header.Name) != "app.json" {
  189. continue
  190. }
  191. if header.Size > 2<<20 { // 2MB 上限
  192. return app, fmt.Errorf("app.json 文件过大")
  193. }
  194. data, err := io.ReadAll(io.LimitReader(tr, header.Size))
  195. if err != nil {
  196. return app, err
  197. }
  198. err = json.Unmarshal(data, &app)
  199. if err != nil {
  200. return app, err
  201. }
  202. if app.Name == "" || app.Version == "" || app.InstallPath == "" ||
  203. app.Executable == "" || app.ReadmeFile == "" {
  204. return app, fmt.Errorf("app.json 参数不完整")
  205. }
  206. if !filepath.IsAbs(app.InstallPath) {
  207. return app, fmt.Errorf("installPath必须是绝对路径")
  208. }
  209. clean := filepath.Clean(app.InstallPath)
  210. if clean != app.InstallPath {
  211. return app, fmt.Errorf("installPath路径非法")
  212. }
  213. return app, nil
  214. }
  215. return app, fmt.Errorf("安装包中未找到 app.json")
  216. }
  217. func fileMD5(file string) (string, error) {
  218. f, err := os.Open(file)
  219. if err != nil {
  220. return "", err
  221. }
  222. defer f.Close()
  223. h := md5.New()
  224. if _, err := io.Copy(h, f); err != nil {
  225. return "", err
  226. }
  227. return hex.EncodeToString(h.Sum(nil)), nil
  228. }
  229. func isRegularFile(name string) bool {
  230. info, err := os.Stat(name)
  231. if err != nil {
  232. return false
  233. }
  234. return info.Mode().IsRegular()
  235. }
  236. func hasImportedPackage(dir string) bool {
  237. if isRegularFile(filepath.Join(dir, "appins.json")) {
  238. return true
  239. }
  240. entries, err := os.ReadDir(dir)
  241. if err != nil {
  242. return false
  243. }
  244. for _, entry := range entries {
  245. if entry.IsDir() {
  246. continue
  247. }
  248. if strings.HasSuffix(strings.ToLower(entry.Name()), ".tar.gz") {
  249. return true
  250. }
  251. }
  252. return false
  253. }
  254. func writeFileAtomic(name string, data []byte, perm os.FileMode) error {
  255. tmp := name + ".tmp"
  256. defer os.Remove(tmp)
  257. f, err := os.OpenFile(tmp, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, perm)
  258. if err != nil {
  259. return err
  260. }
  261. if _, err := f.Write(data); err != nil {
  262. f.Close()
  263. return err
  264. }
  265. if err := f.Sync(); err != nil {
  266. f.Close()
  267. return err
  268. }
  269. if err := f.Close(); err != nil {
  270. return err
  271. }
  272. return os.Rename(tmp, name)
  273. }