simulation_service.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. package simulation_service
  2. import (
  3. "archive/zip"
  4. "context"
  5. "encoding/json"
  6. "fmt"
  7. "github.com/cloudwego/hertz/pkg/app"
  8. "github.com/cloudwego/hertz/pkg/protocol/consts"
  9. uuid "github.com/satori/go.uuid"
  10. "io"
  11. "net/http"
  12. "os"
  13. "path/filepath"
  14. "pji_desktop_http/biz/dal/mysql"
  15. "pji_desktop_http/biz/model"
  16. "pji_desktop_http/common/config"
  17. "pji_desktop_http/common/config/c_log"
  18. "pji_desktop_http/common/entity"
  19. "pji_desktop_http/common/util"
  20. "strconv"
  21. "strings"
  22. )
  23. // DownloadSimulationZipFile 根据请求id从oss拉取并打包仿真测试所需要的文件
  24. // @router /simulation/download/zipfile [GET]
  25. func DownloadSimulationZipFile(ctx context.Context, c *app.RequestContext) {
  26. id := c.Query("id")
  27. fmt.Println(id)
  28. // 根据id生成用于地图更新的压缩包
  29. filePath, tmpDir, err := generateSimulationZipById(ctx, id)
  30. if err != nil {
  31. c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
  32. }
  33. fmt.Println("filePath", filePath)
  34. // 检查文件是否存在
  35. if _, err := os.Stat(filePath); os.IsNotExist(err) {
  36. c.JSON(http.StatusNotFound, map[string]string{"error": "File not found"})
  37. return
  38. }
  39. // 打开文件
  40. f, err := os.Open(filePath)
  41. if err != nil {
  42. c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to open file"})
  43. return
  44. }
  45. defer f.Close()
  46. // 设置响应头
  47. c.Response.Header.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filepath.Base(filePath)))
  48. c.Response.Header.Set("Content-Type", "binary/octet-stream")
  49. // 将文件流式传输回客户端
  50. data, err := io.ReadAll(f)
  51. if err != nil {
  52. panic(err)
  53. }
  54. if _, err := c.Write(data); err != nil {
  55. c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
  56. return
  57. }
  58. defer os.RemoveAll(tmpDir)
  59. }
  60. // 根据id生成用于仿真测试的压缩包
  61. func generateSimulationZipById(ctx context.Context, id string) (file string, tmpDir string, err error) {
  62. // 根据id获取对应的oss文件列表
  63. allFileList, err := util.GetExactedMapFileById(id)
  64. //fmt.Println("Filtered Strings:", fileList)
  65. if err != nil {
  66. return
  67. }
  68. // 创建临时文件夹
  69. tmpDir, err = os.MkdirTemp("", "temp-download-*")
  70. fmt.Println("tmpDir:", tmpDir)
  71. if err != nil {
  72. fmt.Println("Error creating temporary directory:", err)
  73. return "", "", err
  74. }
  75. c_log.GlobalLogger.Info("创建下载-临时文件夹:", tmpDir)
  76. // 创建根文件夹(文件打包的根目录)
  77. baseDir := filepath.Join(tmpDir, "data")
  78. if err := os.Mkdir(baseDir, 0755); err != nil {
  79. fmt.Println("Error creating subdirectory:", err)
  80. return "", "", err
  81. }
  82. c_log.GlobalLogger.Info("创建文件打包根目录:", baseDir)
  83. // 从oss下载data.zip, map.pgm, map.yaml文件到根目录
  84. // 过滤特定后缀的文件列表
  85. simulationFileList := util.FilterBySuffixes(allFileList, config.SimulationFiltersuffixes...)
  86. fmt.Println("simulationFileList", simulationFileList)
  87. // 从oss下载文件到 根目录
  88. for _, file := range simulationFileList {
  89. err = config.OssBucket.GetObjectToFile(file, filepath.Join(baseDir, filepath.Base(file)))
  90. if err != nil {
  91. fmt.Println("Error downloading file - data.zip, map.pgm, map.yaml, map.bag:", err)
  92. return "", "", err
  93. }
  94. }
  95. c_log.GlobalLogger.Info("下载data.zip, map.pgm, map.yaml, map.bag文件到根目录 - 成功")
  96. // 下载world文件&map.stl文件
  97. // 查询状态
  98. world, err := mysql.QueryWorld(ctx, id)
  99. if err != nil || world == nil { // 记录不存在
  100. c_log.GlobalLogger.Info("world记录不存在 - 跳过")
  101. } else { // 记录存在
  102. // 下载world文件
  103. worldURL := world.WorldURL
  104. err = config.OssBucket.GetObjectToFile(worldURL, filepath.Join(baseDir, filepath.Base(worldURL)))
  105. if err != nil {
  106. fmt.Println("Error downloading world file:", err)
  107. return "", "", err
  108. }
  109. c_log.GlobalLogger.Info("下载world文件到根目录 - 成功")
  110. // 下载map.stl文件
  111. stlURL := world.StlURL
  112. if stlURL != nil && *stlURL != "" {
  113. err = config.OssBucket.GetObjectToFile(*stlURL, filepath.Join(baseDir, filepath.Base(*stlURL)))
  114. if err != nil {
  115. fmt.Println("Error downloading stl file:", err)
  116. return "", "", err
  117. }
  118. c_log.GlobalLogger.Info("下载map.stl文件到根目录 - 成功")
  119. } else {
  120. c_log.GlobalLogger.Info("map.stl文件不存在 - 跳过")
  121. }
  122. }
  123. // 下载原始bag
  124. rosField, err := util.GetRosFileById(id)
  125. if err != nil {
  126. fmt.Println("Error querying origin map's bag file:", err)
  127. return "", "", err
  128. }
  129. err = config.OssBucket.GetObjectToFile(rosField, filepath.Join(baseDir, "origin_map.bag"))
  130. if err != nil {
  131. fmt.Println("Error downloading origin map's bag file:", err)
  132. return "", "", err
  133. }
  134. c_log.GlobalLogger.Info("下载origin_map.bag文件到根目录 - 成功")
  135. // 创建压缩文件
  136. zipPath := filepath.Join(tmpDir, "simulationFile-"+id+".zip")
  137. zipFile, err := os.Create(zipPath)
  138. if err != nil {
  139. fmt.Println("Error creating ZIP file:", err)
  140. return "", "", err
  141. }
  142. defer zipFile.Close()
  143. zipWriter := zip.NewWriter(zipFile)
  144. defer zipWriter.Close()
  145. // 压缩文件夹
  146. if err := util.AddDirToZip(baseDir, zipWriter); err != nil {
  147. fmt.Println("Error adding directory to ZIP:", err)
  148. return "", "", err
  149. }
  150. fmt.Println("ZIP file created successfully.")
  151. c_log.GlobalLogger.Info("创建压缩文件 - 成功")
  152. return zipPath, tmpDir, nil
  153. }
  154. // CheckDataFileStatus 检查data目录是否存在
  155. // @router /simulation/check/file/data/status [GET]
  156. func CheckDataFileStatus(ctx context.Context, c *app.RequestContext) {
  157. sceneID := c.Query("id")
  158. fmt.Println("id", sceneID)
  159. // 根据id获取对应的oss文件列表
  160. allFileList, err := util.GetExactedMapFileById(sceneID)
  161. if err != nil {
  162. return
  163. }
  164. // 过滤特定后缀的文件列表(data.zip)
  165. simulationFileList := util.FilterBySuffixes(allFileList, config.DataFiltersuffixes...)
  166. if len(simulationFileList) == 0 {
  167. c.JSON(consts.StatusOK, entity.HttpResult{Status: false, Code: "", Message: "data目录不存在"})
  168. return
  169. }
  170. c.JSON(consts.StatusOK, entity.HttpResult{Status: true, Code: "", Message: "data目录存在"})
  171. }
  172. // UploadPdfFile 将result.pdf文件上传到oss
  173. // @router /simulation/upload/pdf [GET]
  174. func UploadPdfFile(ctx context.Context, c *app.RequestContext) {
  175. equipmentNo := c.Query("equipmentNo")
  176. fmt.Println("equipmentNo", equipmentNo)
  177. sceneNo := c.Query("sceneNo")
  178. fmt.Println("sceneNo", sceneNo)
  179. timeStamp := c.Query("timeStamp")
  180. fmt.Println("timeStamp", timeStamp)
  181. round := c.Query("round")
  182. fmt.Println("round", round)
  183. header, err := c.FormFile("file")
  184. if err != nil {
  185. c.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", err.Error()))
  186. return
  187. }
  188. fileName := header.Filename
  189. fmt.Println("filename", fileName)
  190. ossObjectKey := config.SimulationOssBasePrefix + "/" + equipmentNo + "/" + sceneNo + "/" + timeStamp + "/" + round + "/" + fileName
  191. fmt.Println("ossObjectKey", ossObjectKey)
  192. f, _ := header.Open()
  193. defer f.Close()
  194. config.OssMutex.Lock()
  195. err = config.OssBucket.PutObject(ossObjectKey, f)
  196. config.OssMutex.Unlock()
  197. if err != nil {
  198. c_log.GlobalLogger.Error("程序异常退出。上传文件", fileName, "->", ossObjectKey, "出错:", err)
  199. c.JSON(consts.StatusOK, entity.HttpResult{Status: false, Code: "", Message: "上传文件失败", Details: ""})
  200. return
  201. }
  202. c_log.GlobalLogger.Info("上传文件", fileName, "->", ossObjectKey, "成功。")
  203. c.JSON(consts.StatusOK, entity.HttpResult{Status: true, Code: "", Message: "上传文件成功", Details: ossObjectKey})
  204. }
  205. // UploadBagFile 将test-${i}.bag文件上传到oss
  206. // @router /simulation/upload/bag [GET]
  207. func UploadBagFile(ctx context.Context, c *app.RequestContext) {
  208. equipmentNo := c.Query("equipmentNo")
  209. fmt.Println("equipmentNo", equipmentNo)
  210. sceneNo := c.Query("sceneNo")
  211. fmt.Println("sceneNo", sceneNo)
  212. timeStamp := c.Query("timeStamp")
  213. fmt.Println("timeStamp", timeStamp)
  214. round := c.Query("round")
  215. fmt.Println("round", round)
  216. header, err := c.FormFile("file")
  217. if err != nil {
  218. c.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", err.Error()))
  219. return
  220. }
  221. fileName := header.Filename
  222. fmt.Println("filename", fileName)
  223. ossObjectKey := config.SimulationOssBasePrefix + "/" + equipmentNo + "/" + sceneNo + "/" + timeStamp + "/" + round + "/" + "test.bag"
  224. fmt.Println("ossObjectKey", ossObjectKey)
  225. f, _ := header.Open()
  226. defer f.Close()
  227. config.OssMutex.Lock()
  228. err = config.OssBucket.PutObject(ossObjectKey, f)
  229. config.OssMutex.Unlock()
  230. if err != nil {
  231. c_log.GlobalLogger.Error("程序异常退出。上传文件", fileName, "->", ossObjectKey, "出错:", err)
  232. c.JSON(consts.StatusOK, entity.HttpResult{Status: false, Code: "", Message: "上传文件失败", Details: ""})
  233. return
  234. }
  235. c_log.GlobalLogger.Info("上传文件", fileName, "->", ossObjectKey, "成功。")
  236. c.JSON(consts.StatusOK, entity.HttpResult{Status: true, Code: "", Message: "上传文件成功", Details: ossObjectKey})
  237. }
  238. // UploadPgmFile 将map.pgm文件上传到oss
  239. // @router /simulation/upload/pgm [GET]
  240. func UploadPgmFile(ctx context.Context, c *app.RequestContext) {
  241. equipmentNo := c.Query("equipmentNo")
  242. fmt.Println("equipmentNo", equipmentNo)
  243. sceneNo := c.Query("sceneNo")
  244. fmt.Println("sceneNo", sceneNo)
  245. header, err := c.FormFile("file")
  246. if err != nil {
  247. c.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", err.Error()))
  248. return
  249. }
  250. fileName := header.Filename
  251. fmt.Println("filename", fileName)
  252. ossObjectKey := config.SimulationOssBasePrefix + "/" + equipmentNo + "/" + sceneNo + "/" + fileName
  253. fmt.Println("ossObjectKey", ossObjectKey)
  254. f, _ := header.Open()
  255. defer f.Close()
  256. config.OssMutex.Lock()
  257. err = config.OssBucket.PutObject(ossObjectKey, f)
  258. config.OssMutex.Unlock()
  259. if err != nil {
  260. c_log.GlobalLogger.Error("程序异常退出。上传文件", fileName, "->", ossObjectKey, "出错:", err)
  261. c.JSON(consts.StatusOK, entity.HttpResult{Status: false, Code: "", Message: "上传文件失败", Details: ""})
  262. return
  263. }
  264. c_log.GlobalLogger.Info("上传文件", fileName, "->", ossObjectKey, "成功。")
  265. c.JSON(consts.StatusOK, entity.HttpResult{Status: true, Code: "", Message: "上传文件成功", Details: ossObjectKey})
  266. }
  267. // AddSimulationRecord 添加仿真测试记录
  268. // @router /simulation/add/record [GET]
  269. func AddSimulationRecord(ctx context.Context, c *app.RequestContext) {
  270. var records []*model.SimulationTestRecord
  271. err := c.BindAndValidate(&records)
  272. for _, record := range records {
  273. record.ID = uuid.NewV1().String()
  274. }
  275. fmt.Println("records", records)
  276. if err != nil {
  277. c.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", err.Error()))
  278. return
  279. }
  280. err = mysql.AddSimulationTestRecords(ctx, records)
  281. if err != nil {
  282. c.JSON(consts.StatusOK, entity.HttpResult{Status: false, Code: "", Message: "仿真测试记录添加失败"})
  283. return
  284. }
  285. c.JSON(consts.StatusOK, entity.HttpResult{Status: true, Code: "", Message: "仿真测试记录添加成功"})
  286. }
  287. // QueryTestRecord 根据条件查询仿真测试记录
  288. // @router /simulation/query/test/record [GET]
  289. func QueryTestRecord(ctx context.Context, c *app.RequestContext) {
  290. var record model.SimulationTestRecord
  291. err := c.BindAndValidate(&record)
  292. fmt.Println("record", record)
  293. var pageFlag bool
  294. if c.Query("page") != "" && c.Query("pageSize") != "" {
  295. pageFlag = true
  296. } else {
  297. pageFlag = false
  298. }
  299. page, _ := strconv.Atoi(c.Query("page"))
  300. pageSize, _ := strconv.Atoi(c.Query("pageSize"))
  301. records, count, err := mysql.QuerySimulationTestRecords(ctx, &record, pageFlag, page, pageSize)
  302. if err != nil {
  303. c.JSON(consts.StatusOK, entity.Response{Status: false, Code: "", Message: "仿真测试记录查询失败", Total: 0})
  304. return
  305. }
  306. output, err := json.Marshal(records)
  307. if err != nil {
  308. c.JSON(consts.StatusOK, entity.Response{Status: false, Code: "", Message: "仿真测试记录查询失败", Total: 0})
  309. return
  310. }
  311. c.JSON(consts.StatusOK, entity.Response{Status: true, Code: "", Message: "仿真测试记录查询成功", Data: string(output), Total: int(count)})
  312. }
  313. // DownloadFileByKeys 给定object key数组从oss下载(打包)文件
  314. // @router /simulation/download/oss/key [POST]
  315. func DownloadFileByKeys(ctx context.Context, c *app.RequestContext) {
  316. sceneId := c.Query("sceneId")
  317. fmt.Println("sceneId", sceneId)
  318. typeName := c.Query("typeName")
  319. fmt.Println("typeName", typeName)
  320. var req []string
  321. err := c.BindAndValidate(&req)
  322. if err != nil {
  323. c.String(consts.StatusBadRequest, err.Error())
  324. return
  325. }
  326. fmt.Println("req", req)
  327. if len(req) == 0 {
  328. c.JSON(consts.StatusBadRequest, entity.HttpResult{Status: false, Code: "", Message: "请求数据为空。"})
  329. } else if len(req) == 1 { // 只有一条数据则直接下载文件
  330. objectKey := req[0]
  331. // 从OSS下载文件
  332. reader, err := config.OssBucket.GetObject(objectKey)
  333. if err != nil {
  334. c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
  335. return
  336. }
  337. defer reader.Close()
  338. // 设置响应头
  339. c.Response.Header.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filepath.Base(objectKey)))
  340. c.Response.Header.Set("Content-Type", "binary/octet-stream")
  341. // 将文件流式传输回客户端
  342. data, err := io.ReadAll(reader)
  343. if err != nil {
  344. panic(err)
  345. }
  346. if _, err := c.Write(data); err != nil {
  347. c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
  348. return
  349. }
  350. } else { // 多条数据先打包后下载文件
  351. fileName := sceneId + "_" + typeName + ".zip"
  352. filePath, tmpDir, err := generateZipByKey(ctx, req, fileName)
  353. if err != nil {
  354. c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
  355. }
  356. fmt.Println("filePath", filePath)
  357. // 检查文件是否存在
  358. if _, err := os.Stat(filePath); os.IsNotExist(err) {
  359. c.JSON(http.StatusNotFound, map[string]string{"error": "File not found"})
  360. return
  361. }
  362. // 打开文件
  363. f, err := os.Open(filePath)
  364. if err != nil {
  365. c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to open file"})
  366. return
  367. }
  368. defer f.Close()
  369. // 设置响应头
  370. c.Response.Header.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filepath.Base(filePath)))
  371. c.Response.Header.Set("Content-Type", "binary/octet-stream")
  372. // 将文件流式传输回客户端
  373. data, err := io.ReadAll(f)
  374. if err != nil {
  375. panic(err)
  376. }
  377. if _, err := c.Write(data); err != nil {
  378. c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
  379. return
  380. }
  381. defer os.RemoveAll(tmpDir)
  382. }
  383. }
  384. // 根据object keys生成压缩包
  385. func generateZipByKey(ctx context.Context, keys []string, fileName string) (file string, tmpDir string, err error) {
  386. // 创建临时文件夹
  387. tmpDir, err = os.MkdirTemp("", "temp-download-*")
  388. fmt.Println("tmpDir:", tmpDir)
  389. if err != nil {
  390. fmt.Println("Error creating temporary directory:", err)
  391. return "", "", err
  392. }
  393. c_log.GlobalLogger.Info("创建下载-临时文件夹:", tmpDir)
  394. // 创建根文件夹(文件打包的根目录)
  395. baseDir := filepath.Join(tmpDir, "data")
  396. if err := os.Mkdir(baseDir, 0755); err != nil {
  397. fmt.Println("Error creating subdirectory:", err)
  398. return "", "", err
  399. }
  400. c_log.GlobalLogger.Info("创建文件打包根目录:", baseDir)
  401. // 从oss下载文件到根目录
  402. for i, file := range keys {
  403. baseName := filepath.Base(file)
  404. ext := filepath.Ext(baseName)
  405. name := strings.TrimSuffix(baseName, ext)
  406. newName := name + "-" + strconv.Itoa(i+1) + ext
  407. err = config.OssBucket.GetObjectToFile(file, filepath.Join(baseDir, newName))
  408. if err != nil {
  409. fmt.Println("Error downloading file:", file, err)
  410. return "", "", err
  411. }
  412. }
  413. c_log.GlobalLogger.Info("下载oss文件到根目录 - 成功")
  414. // 创建压缩文件
  415. zipPath := filepath.Join(tmpDir, fileName)
  416. zipFile, err := os.Create(zipPath)
  417. if err != nil {
  418. fmt.Println("Error creating ZIP file:", err)
  419. return "", "", err
  420. }
  421. defer zipFile.Close()
  422. zipWriter := zip.NewWriter(zipFile)
  423. defer zipWriter.Close()
  424. // 压缩文件夹
  425. if err := util.AddDirToZip(baseDir, zipWriter); err != nil {
  426. fmt.Println("Error adding directory to ZIP:", err)
  427. return "", "", err
  428. }
  429. fmt.Println("ZIP file created successfully.")
  430. c_log.GlobalLogger.Info("创建压缩文件 - 成功")
  431. return zipPath, tmpDir, nil
  432. }