simulation_service.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  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. // QueryEvalRecord 查询算法评价记录(根据场景id及时间戳)
  288. // @router /simulation/query/eval/record [GET]
  289. func QueryEvalRecord(ctx context.Context, c *app.RequestContext) {
  290. sceneId := c.Query("sceneId")
  291. fmt.Println("sceneId", sceneId)
  292. testTime := c.Query("testTime")
  293. fmt.Println("testTime", testTime)
  294. records, err := mysql.QuerySimulationTestRecords(ctx, sceneId, testTime)
  295. if err != nil {
  296. c.JSON(consts.StatusOK, entity.HttpResult{Status: false, Code: "", Message: "仿真测试记录查询失败", Details: ""})
  297. return
  298. }
  299. output, err := json.Marshal(records)
  300. if err != nil {
  301. c.JSON(consts.StatusOK, entity.HttpResult{Status: false, Code: "", Message: "仿真测试记录查询失败", Details: ""})
  302. return
  303. }
  304. c.JSON(consts.StatusOK, entity.HttpResult{Status: true, Code: "", Message: "仿真测试记录查询成功", Details: string(output)})
  305. }
  306. // DownloadFileByKeys 给定object key数组从oss下载(打包)文件
  307. // @router /simulation/download/oss/key [POST]
  308. func DownloadFileByKeys(ctx context.Context, c *app.RequestContext) {
  309. sceneId := c.Query("sceneId")
  310. fmt.Println("sceneId", sceneId)
  311. typeName := c.Query("typeName")
  312. fmt.Println("typeName", typeName)
  313. var req []string
  314. err := c.BindAndValidate(&req)
  315. if err != nil {
  316. c.String(consts.StatusBadRequest, err.Error())
  317. return
  318. }
  319. fmt.Println("req", req)
  320. if len(req) == 0 {
  321. c.JSON(consts.StatusBadRequest, entity.HttpResult{Status: false, Code: "", Message: "请求数据为空。"})
  322. } else if len(req) == 1 { // 只有一条数据则直接下载文件
  323. objectKey := req[0]
  324. // 从OSS下载文件
  325. reader, err := config.OssBucket.GetObject(objectKey)
  326. if err != nil {
  327. c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
  328. return
  329. }
  330. defer reader.Close()
  331. // 设置响应头
  332. c.Response.Header.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filepath.Base(objectKey)))
  333. c.Response.Header.Set("Content-Type", "binary/octet-stream")
  334. // 将文件流式传输回客户端
  335. data, err := io.ReadAll(reader)
  336. if err != nil {
  337. panic(err)
  338. }
  339. if _, err := c.Write(data); err != nil {
  340. c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
  341. return
  342. }
  343. } else { // 多条数据先打包后下载文件
  344. fileName := sceneId + "_" + typeName + ".zip"
  345. filePath, tmpDir, err := generateZipByKey(ctx, req, fileName)
  346. if err != nil {
  347. c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
  348. }
  349. fmt.Println("filePath", filePath)
  350. // 检查文件是否存在
  351. if _, err := os.Stat(filePath); os.IsNotExist(err) {
  352. c.JSON(http.StatusNotFound, map[string]string{"error": "File not found"})
  353. return
  354. }
  355. // 打开文件
  356. f, err := os.Open(filePath)
  357. if err != nil {
  358. c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to open file"})
  359. return
  360. }
  361. defer f.Close()
  362. // 设置响应头
  363. c.Response.Header.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filepath.Base(filePath)))
  364. c.Response.Header.Set("Content-Type", "binary/octet-stream")
  365. // 将文件流式传输回客户端
  366. data, err := io.ReadAll(f)
  367. if err != nil {
  368. panic(err)
  369. }
  370. if _, err := c.Write(data); err != nil {
  371. c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
  372. return
  373. }
  374. defer os.RemoveAll(tmpDir)
  375. }
  376. }
  377. // 根据object keys生成压缩包
  378. func generateZipByKey(ctx context.Context, keys []string, fileName string) (file string, tmpDir string, err error) {
  379. // 创建临时文件夹
  380. tmpDir, err = os.MkdirTemp("", "temp-download-*")
  381. fmt.Println("tmpDir:", tmpDir)
  382. if err != nil {
  383. fmt.Println("Error creating temporary directory:", err)
  384. return "", "", err
  385. }
  386. c_log.GlobalLogger.Info("创建下载-临时文件夹:", tmpDir)
  387. // 创建根文件夹(文件打包的根目录)
  388. baseDir := filepath.Join(tmpDir, "data")
  389. if err := os.Mkdir(baseDir, 0755); err != nil {
  390. fmt.Println("Error creating subdirectory:", err)
  391. return "", "", err
  392. }
  393. c_log.GlobalLogger.Info("创建文件打包根目录:", baseDir)
  394. // 从oss下载文件到根目录
  395. for i, file := range keys {
  396. baseName := filepath.Base(file)
  397. ext := filepath.Ext(baseName)
  398. name := strings.TrimSuffix(baseName, ext)
  399. newName := name + "-" + strconv.Itoa(i+1) + ext
  400. err = config.OssBucket.GetObjectToFile(file, filepath.Join(baseDir, newName))
  401. if err != nil {
  402. fmt.Println("Error downloading file:", file, err)
  403. return "", "", err
  404. }
  405. }
  406. c_log.GlobalLogger.Info("下载oss文件到根目录 - 成功")
  407. // 创建压缩文件
  408. zipPath := filepath.Join(tmpDir, fileName)
  409. zipFile, err := os.Create(zipPath)
  410. if err != nil {
  411. fmt.Println("Error creating ZIP file:", err)
  412. return "", "", err
  413. }
  414. defer zipFile.Close()
  415. zipWriter := zip.NewWriter(zipFile)
  416. defer zipWriter.Close()
  417. // 压缩文件夹
  418. if err := util.AddDirToZip(baseDir, zipWriter); err != nil {
  419. fmt.Println("Error adding directory to ZIP:", err)
  420. return "", "", err
  421. }
  422. fmt.Println("ZIP file created successfully.")
  423. c_log.GlobalLogger.Info("创建压缩文件 - 成功")
  424. return zipPath, tmpDir, nil
  425. }