h_exam.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. package handler
  2. import (
  3. webServerEntity "cicv-data-closedloop/amd64/web_server/entity"
  4. "cicv-data-closedloop/common/config/c_db"
  5. "cicv-data-closedloop/common/config/c_log"
  6. commonEntity "cicv-data-closedloop/common/entity"
  7. "cicv-data-closedloop/common/util"
  8. "fmt"
  9. "github.com/gin-gonic/gin"
  10. "github.com/signintech/gopdf"
  11. "io"
  12. "log"
  13. "math"
  14. "net/http"
  15. "os"
  16. "sync"
  17. "time"
  18. )
  19. var (
  20. defaultTime = time.Date(2006, time.January, 2, 15, 4, 5, 0, time.Local)
  21. cacheMutex sync.Mutex
  22. cacheTeamName = make(map[string]time.Time)
  23. heartBeatTimeThreshold = 5 * time.Second // 心跳时间
  24. InitialPositionX = 0.00 // todo 需要比赛确认起点
  25. InitialPositionY = 0.00 // todo 需要比赛确认起点
  26. // todo 比赛阶段
  27. trialBegin = time.Date(2024, time.June, 16, 13, 00, 00, 0, time.Local)
  28. trialEnd = time.Date(2024, time.June, 19, 23, 59, 59, 0, time.Local)
  29. )
  30. // 考试心跳
  31. func Tick(c *gin.Context) {
  32. param := new(webServerEntity.ExamPao)
  33. // 映射到结构体
  34. if err := c.ShouldBindJSON(&param); err != nil {
  35. c_log.GlobalLogger.Error("请求体解析失败,错误信息为:", err)
  36. c.JSON(http.StatusBadRequest, commonEntity.Response{
  37. Code: 500,
  38. Msg: "请求体解析失败。",
  39. })
  40. return
  41. }
  42. teamName := param.TeamName
  43. positionX := param.PositionX
  44. positionY := param.PositionY
  45. if !util.ContainsKey(cacheTeamName, teamName) && math.Abs(positionX-InitialPositionX) < 5.00 && math.Abs(positionY-InitialPositionY) < 5.00 { // (在起点开始)
  46. sqlTemplate, _ := util.ReadFile(c_db.SqlFilesMap["exam-insert-begin_time-and-topic-by-team_name.sql"])
  47. c_log.GlobalLogger.Info("保存比赛开始时间和比赛阶段", sqlTemplate)
  48. stage := "表演赛"
  49. if time.Now().After(trialBegin) && time.Now().Before(trialEnd) {
  50. stage = "预赛"
  51. } else {
  52. stage = "决赛"
  53. }
  54. if err := c_db.DoTx(sqlTemplate, []any{
  55. param.TeamName,
  56. stage,
  57. time.Now(),
  58. }); err != nil {
  59. c_log.GlobalLogger.Error("保存比赛开始时间报错:", err)
  60. c.JSON(http.StatusBadRequest, commonEntity.Response{
  61. Code: 500,
  62. Msg: "保存比赛开始时间报错。",
  63. })
  64. return
  65. }
  66. } else if !util.ContainsKey(cacheTeamName, teamName) && math.Abs(positionX-InitialPositionX) < 5.00 && math.Abs(positionY-InitialPositionY) < 5.00 { // 不在起点(开始)
  67. // 车辆不是在起点启动的自动驾驶模式
  68. selectSql, err := util.ReadFile(c_db.SqlFilesMap["exam-select-latest-by-team_name.sql"])
  69. if err != nil {
  70. c_log.GlobalLogger.Error("读取sql文件报错:", err)
  71. return
  72. }
  73. // 可以传参数
  74. var result []webServerEntity.ExamPo
  75. if err = c_db.MysqlDb.Select(&result, selectSql, teamName); err != nil {
  76. c_log.GlobalLogger.Error("数据库查询报错:", err)
  77. return
  78. }
  79. if len(result) == 1 {
  80. c_log.GlobalLogger.Info("上一条数据记录为:", result[0])
  81. }
  82. // 添加队伍名到缓存中
  83. cacheTeamName[teamName] = time.Now()
  84. // 更新记录结束时间为默认时间
  85. sqlTemplate, _ := util.ReadFile(c_db.SqlFilesMap["exam-update-end_time-by-team_name.sql"])
  86. if err = c_db.DoTx(sqlTemplate, []any{
  87. defaultTime,
  88. teamName,
  89. }); err != nil {
  90. c_log.GlobalLogger.Error("插入数据报错:", err)
  91. return
  92. }
  93. c_log.GlobalLogger.Infof("队伍 %v 的考试在中途中断后重新开始。", teamName)
  94. } else if util.ContainsKey(cacheTeamName, teamName) { // 进行中
  95. cacheTeamName[teamName] = time.Now()
  96. }
  97. c.JSON(http.StatusOK, commonEntity.Response{
  98. Code: 200,
  99. Msg: "心跳接收成功。",
  100. })
  101. }
  102. func ExamEndTicker() {
  103. // 创建一个定时器,每隔一秒触发一次
  104. ticker := time.NewTicker(1 * time.Second)
  105. for {
  106. select {
  107. // 定时器触发时执行的代码
  108. case <-ticker.C:
  109. cacheMutex.Lock()
  110. {
  111. var keysToDelete []string
  112. for teamName, heartBeatTime := range cacheTeamName {
  113. if time.Since(heartBeatTime) > heartBeatTimeThreshold { // 检查缓存中的队名,如果超过心跳时间,则代表考试结束,删除缓存中的队名
  114. keysToDelete = append(keysToDelete, teamName)
  115. }
  116. }
  117. for _, teamName := range keysToDelete { // 检查缓存中的队名,如果超过心跳时间,则代表考试结束,删除缓存中的队名
  118. delete(cacheTeamName, teamName)
  119. // 1 查询指定队伍的开始时间最新的考试是否有结束时间,如果有则不在处理,如果没有则更新
  120. var result []webServerEntity.ExamPo
  121. selectSql, err := util.ReadFile(c_db.SqlFilesMap["exam-select-latest-by-team_name.sql"])
  122. if err != nil {
  123. c_log.GlobalLogger.Error("读取sql文件报错:", err)
  124. return
  125. }
  126. // 可以传参数
  127. if err = c_db.MysqlDb.Select(&result, selectSql, teamName); err != nil {
  128. c_log.GlobalLogger.Error("数据库查询报错:", err)
  129. return
  130. }
  131. c_log.GlobalLogger.Info("数据库查询成功:", result)
  132. if !result[0].EndTime.Equal(defaultTime) {
  133. c_log.GlobalLogger.Error("赛队", teamName, "考试已结束!")
  134. return
  135. }
  136. // 更新到数据库
  137. sqlTemplate, _ := util.ReadFile(c_db.SqlFilesMap["exam-update-end_time-by-team_name.sql"])
  138. if err := c_db.DoTx(sqlTemplate, []any{
  139. time.Now(),
  140. teamName,
  141. }); err != nil {
  142. c_log.GlobalLogger.Error("插入数据报错:", err)
  143. return
  144. }
  145. c_log.GlobalLogger.Infof("队伍 %v 的考试结束。", teamName)
  146. }
  147. }
  148. cacheMutex.Unlock()
  149. }
  150. }
  151. }
  152. // 考试开始时间
  153. func Begin(c *gin.Context) {
  154. param := new(webServerEntity.ExamPao)
  155. // 映射到结构体
  156. if err := c.ShouldBindJSON(&param); err != nil {
  157. c_log.GlobalLogger.Error("项目启动接收请求参数报错:", err)
  158. c.JSON(http.StatusBadRequest, commonEntity.Response{
  159. Code: 500,
  160. Msg: "请求体解析失败。",
  161. })
  162. return
  163. }
  164. // 插入到数据库
  165. sqlTemplate, _ := util.ReadFile(c_db.SqlFilesMap["exam-insert-begin_time-by-team_name.sql"])
  166. c_log.GlobalLogger.Info("插入比赛开始时间", sqlTemplate)
  167. if err := c_db.DoTx(sqlTemplate, []any{
  168. param.TeamName,
  169. time.Now(),
  170. }); err != nil {
  171. c_log.GlobalLogger.Error("插入数据报错:", err)
  172. c.JSON(http.StatusBadRequest, commonEntity.Response{
  173. Code: 500,
  174. Msg: "插入数据报错。",
  175. })
  176. return
  177. }
  178. c.JSON(http.StatusOK, commonEntity.Response{
  179. Code: 200,
  180. Msg: "插入数据成功。",
  181. })
  182. }
  183. // 考试结束时间
  184. func End(c *gin.Context) {
  185. param := new(webServerEntity.ExamPao)
  186. // 映射到结构体
  187. if err := c.ShouldBindJSON(&param); err != nil {
  188. c_log.GlobalLogger.Error("项目启动接收请求参数报错:", err)
  189. c.JSON(http.StatusBadRequest, commonEntity.Response{
  190. Code: 500,
  191. Msg: "请求体解析失败。",
  192. })
  193. return
  194. }
  195. // 1 查询指定队伍的开始时间最新的考试是否有结束时间,如果有则不在处理,如果没有则更新
  196. var result []webServerEntity.ExamPo
  197. selectSql, err := util.ReadFile(c_db.SqlFilesMap["exam-select-latest-by-team_name.sql"])
  198. if err != nil {
  199. c_log.GlobalLogger.Error("读取sql文件报错:", err)
  200. c.JSON(http.StatusBadRequest, commonEntity.Response{
  201. Code: 500,
  202. Msg: "读取sql文件报错。",
  203. })
  204. return
  205. }
  206. // 可以传参数
  207. if err = c_db.MysqlDb.Select(&result, selectSql, param.TeamName); err != nil {
  208. c_log.GlobalLogger.Error("数据库查询报错:", err)
  209. c.JSON(http.StatusBadRequest, commonEntity.Response{
  210. Code: 500,
  211. Msg: "数据库查询报错。",
  212. })
  213. return
  214. }
  215. c_log.GlobalLogger.Info("数据库查询成功:", result)
  216. if !result[0].EndTime.Equal(defaultTime) {
  217. c_log.GlobalLogger.Error("赛队", param.TeamName, "重复请求考试结束接口!")
  218. c.JSON(http.StatusBadRequest, commonEntity.Response{
  219. Code: 500,
  220. Msg: "重复请求。",
  221. })
  222. return
  223. }
  224. // 更新到数据库
  225. sqlTemplate, _ := util.ReadFile(c_db.SqlFilesMap["exam-update-end_time-by-team_name.sql"])
  226. if err := c_db.DoTx(sqlTemplate, []any{
  227. time.Now(),
  228. param.TeamName,
  229. }); err != nil {
  230. c_log.GlobalLogger.Error("插入数据报错:", err)
  231. c.JSON(http.StatusBadRequest, commonEntity.Response{
  232. Code: 500,
  233. Msg: "插入数据报错。",
  234. })
  235. return
  236. }
  237. c.JSON(http.StatusOK, commonEntity.Response{
  238. Code: 200,
  239. Msg: "插入数据成功。",
  240. })
  241. }
  242. // 分页查询
  243. // todo 如果日期为默认值,则返回空""
  244. func Page(c *gin.Context) {
  245. param := new(webServerEntity.ExamPagePao)
  246. _ = c.ShouldBindJSON(&param)
  247. var resultPos []webServerEntity.ExamPo
  248. var resultPosTotal []int
  249. var pageSql string
  250. var totalSql string
  251. offset := (param.CurrentPage - 1) * param.PageSize
  252. size := param.PageSize
  253. if param.TeamName == "" && param.Topic == "" {
  254. pageSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-page.sql"])
  255. totalSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-total.sql"])
  256. err := c_db.MysqlDb.Select(&resultPos, pageSql, offset, size)
  257. if err != nil {
  258. c_log.GlobalLogger.Error(err)
  259. }
  260. err = c_db.MysqlDb.Select(&resultPosTotal, totalSql)
  261. if err != nil {
  262. c_log.GlobalLogger.Error(err)
  263. }
  264. }
  265. if param.TeamName != "" && param.Topic == "" {
  266. pageSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-page-by-team_name.sql"])
  267. totalSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-total-by-team_name.sql"])
  268. _ = c_db.MysqlDb.Select(&resultPos, pageSql, "%"+param.TeamName+"%", offset, size)
  269. _ = c_db.MysqlDb.Select(&resultPosTotal, totalSql, "%"+param.TeamName+"%")
  270. }
  271. if param.TeamName == "" && param.Topic != "" {
  272. pageSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-page-by-topic.sql"])
  273. totalSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-total-by-topic.sql"])
  274. _ = c_db.MysqlDb.Select(&resultPos, pageSql, "%"+param.Topic+"%", offset, size)
  275. _ = c_db.MysqlDb.Select(&resultPosTotal, totalSql, "%"+param.Topic+"%")
  276. }
  277. if param.TeamName != "" && param.Topic != "" {
  278. pageSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-page-by-team_name-and-topic.sql"])
  279. totalSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-total-by-team_name-and-topic.sql"])
  280. _ = c_db.MysqlDb.Select(&resultPos, pageSql, "%"+param.TeamName+"%", "%"+param.Topic+"%", offset, size)
  281. _ = c_db.MysqlDb.Select(&resultPosTotal, totalSql, "%"+param.TeamName+"%", "%"+param.Topic+"%")
  282. }
  283. var resultVos []webServerEntity.ExamVo
  284. for _, po := range resultPos {
  285. resultVos = append(resultVos, webServerEntity.ExamVo{
  286. Id: po.Id,
  287. TeamName: po.TeamName,
  288. Topic: po.Topic,
  289. BeginTime: util.GetTimeString(po.BeginTime),
  290. EndTime: util.GetTimeString(po.EndTime),
  291. ScoreOnline: po.ScoreOnline,
  292. ScoreOffline: po.ScoreOffline,
  293. ScoreFinal: po.ScoreFinal,
  294. Details: po.Details,
  295. ScoreReportPath: po.ScoreReportPath,
  296. })
  297. }
  298. c.JSON(http.StatusOK, commonEntity.Response{
  299. Code: 200,
  300. Msg: "分页查询成功!",
  301. Data: resultVos,
  302. Total: resultPosTotal[0],
  303. })
  304. }
  305. // 评分报告pdf下载
  306. func Report(c *gin.Context) {
  307. //param := new(webServerEntity.ExamReportPao)
  308. //// 映射到结构体
  309. //if err := c.ShouldBindJSON(&param); err != nil {
  310. // c_log.GlobalLogger.Error("项目启动接收请求参数报错:", err)
  311. // c.JSON(http.StatusBadRequest, commonEntity.Response{
  312. // Code: 500,
  313. // Msg: "请求体解析失败。",
  314. // })
  315. // return
  316. //}
  317. // 1 根据ID查询数据
  318. // 2 根据数据生成pdf
  319. // 3 创建pdf文件
  320. {
  321. // 初始化 pdf 对象
  322. pdf := gopdf.GoPdf{}
  323. pdf.Start(gopdf.Config{PageSize: *gopdf.PageSizeA4})
  324. // 添加字体文件到pdf
  325. err := pdf.AddTTFFont("simfang", "D:\\code\\cicv-data-closedloop\\amd64\\web_server\\simfang.ttf")
  326. if err != nil {
  327. log.Print(err.Error())
  328. return
  329. }
  330. leftMargin := 70.0
  331. topMargin := 100.0
  332. lineHeight := 30.0
  333. tableWidth := 400.0
  334. pdf.SetLeftMargin(leftMargin) // 设置左边距
  335. // ------- 封面页 -------
  336. pdf.AddPage()
  337. {
  338. err = pdf.SetFont("simfang", "", 36)
  339. if err != nil {
  340. fmt.Print(err.Error())
  341. }
  342. err = pdf.Image("D:\\code\\cicv-data-closedloop\\amd64\\web_server\\background.png", 0, 0, nil) // 背景图片
  343. if err != nil {
  344. fmt.Print(err.Error())
  345. }
  346. err = pdf.Image("D:\\code\\cicv-data-closedloop\\amd64\\web_server\\logo.png", 20, 20, &gopdf.Rect{W: 320, H: 100})
  347. if err != nil {
  348. fmt.Print(err.Error())
  349. }
  350. {
  351. pdf.SetXY(100, 250)
  352. _ = pdf.Text("车路云一体化算法挑战赛")
  353. }
  354. {
  355. pdf.SetXY(220, 315) // 200
  356. _ = pdf.Text("评分报告")
  357. }
  358. _ = pdf.SetFont("simfang", "", 14)
  359. {
  360. pdf.SetXY(200, 525)
  361. _ = pdf.Text("赛队编号:01")
  362. }
  363. {
  364. pdf.SetXY(200, 545)
  365. _ = pdf.Cell(nil, "评测车型:多功能车平台")
  366. }
  367. {
  368. pdf.SetXY(200, 565)
  369. _ = pdf.Cell(nil, "评测地点:天津生态城")
  370. }
  371. {
  372. pdf.SetXY(200, 585)
  373. _ = pdf.Cell(nil, "报告时间:2023年10月12日")
  374. }
  375. {
  376. pdf.SetXY(165, 775)
  377. _ = pdf.Cell(nil, "国汽(北京)智能网联汽车研究院有限公司")
  378. }
  379. }
  380. // ------- 目录页 -------
  381. {
  382. pdf.AddPage()
  383. currentLine := 0.0
  384. {
  385. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  386. currentLine++
  387. err = pdf.Text("目录")
  388. }
  389. {
  390. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  391. currentLine++
  392. err = pdf.Text("1. 总体情况\t22")
  393. }
  394. }
  395. // ------- 详情页 -------
  396. {
  397. pdf.AddPage()
  398. currentLine := 0.0
  399. // 1. 总体分数情况
  400. {
  401. {
  402. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  403. currentLine++
  404. err = pdf.Text("1. 总体分数情况")
  405. }
  406. {
  407. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  408. currentLine++
  409. err = pdf.Text("整体得分:")
  410. }
  411. {
  412. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  413. currentLine++
  414. err = pdf.Text("线下得分:")
  415. }
  416. {
  417. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  418. currentLine++
  419. err = pdf.Text("整体耗时:")
  420. }
  421. }
  422. // 2. 分数详情
  423. {
  424. columnNumber := 4.0 // 列数
  425. tableCellWidth := tableWidth / columnNumber // 单元格宽度
  426. {
  427. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  428. currentLine++
  429. err = pdf.Cell(nil, "2. 分数详情")
  430. }
  431. // 分数详情表格
  432. {
  433. {
  434. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  435. err = pdf.Cell(nil, "序号")
  436. }
  437. {
  438. pdf.SetXY(leftMargin+1.0*tableCellWidth, topMargin+currentLine*lineHeight)
  439. err = pdf.Cell(nil, "场景名称")
  440. }
  441. {
  442. pdf.SetXY(leftMargin+2.0*tableCellWidth, topMargin+currentLine*lineHeight)
  443. err = pdf.Cell(nil, "线上得分")
  444. }
  445. {
  446. pdf.SetXY(leftMargin+3.0*tableCellWidth, topMargin+currentLine*lineHeight)
  447. err = pdf.Cell(nil, "线下得分")
  448. }
  449. currentLine++
  450. }
  451. {
  452. {
  453. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  454. err = pdf.Cell(nil, "1")
  455. }
  456. {
  457. pdf.SetXY(leftMargin+1.0*tableCellWidth, topMargin+currentLine*lineHeight)
  458. err = pdf.Cell(nil, "xxx")
  459. }
  460. {
  461. pdf.SetXY(leftMargin+2.0*tableCellWidth, topMargin+currentLine*lineHeight)
  462. err = pdf.Cell(nil, "xxx")
  463. }
  464. {
  465. pdf.SetXY(leftMargin+3.0*tableCellWidth, topMargin+currentLine*lineHeight)
  466. err = pdf.Cell(nil, "xxx")
  467. }
  468. currentLine++
  469. }
  470. }
  471. // 3. 线上扣分详情
  472. {
  473. columnNumber := 3.0 // 列数
  474. tableCellWidth := tableWidth / columnNumber // 单元格宽度
  475. {
  476. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  477. currentLine++
  478. err = pdf.Cell(nil, "3. 线上扣分详情")
  479. }
  480. // 场景1
  481. {
  482. {
  483. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  484. currentLine++
  485. err = pdf.Cell(nil, "(1)xxxx场景")
  486. }
  487. // 场景1 表格
  488. {
  489. {
  490. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  491. err = pdf.Cell(nil, "序号")
  492. }
  493. {
  494. pdf.SetXY(leftMargin+1.0*tableCellWidth, topMargin+currentLine*lineHeight)
  495. err = pdf.Cell(nil, "扣分点")
  496. }
  497. {
  498. pdf.SetXY(leftMargin+2.0*tableCellWidth, topMargin+currentLine*lineHeight)
  499. err = pdf.Cell(nil, "扣分说明")
  500. }
  501. currentLine++
  502. }
  503. {
  504. {
  505. pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight)
  506. err = pdf.Cell(nil, "1")
  507. }
  508. {
  509. pdf.SetXY(leftMargin+1.0*tableCellWidth, topMargin+currentLine*lineHeight)
  510. err = pdf.Cell(nil, "xxx")
  511. }
  512. {
  513. pdf.SetXY(leftMargin+2.0*tableCellWidth, topMargin+currentLine*lineHeight)
  514. err = pdf.Cell(nil, "xxx")
  515. }
  516. currentLine++
  517. }
  518. }
  519. }
  520. }
  521. // ------- 附录页 -------
  522. pdf.AddPage()
  523. {
  524. pdf.SetXY(leftMargin, topMargin)
  525. _ = pdf.Text("附件:评分规则")
  526. pdf.AddExternalLink("http://www.baidu.com/", 27.5, 28, 125, 15)
  527. }
  528. // 写入本地
  529. pdf.WritePdf("D:\\hello.pdf")
  530. }
  531. // 打开要发送的文件
  532. file, err := os.Open("D:\\hello.pdf")
  533. if err != nil {
  534. c.String(http.StatusNotFound, "文件未找到")
  535. return
  536. }
  537. defer file.Close()
  538. // 将文件复制到响应主体
  539. _, err = io.Copy(c.Writer, file)
  540. if err != nil {
  541. c.String(http.StatusInternalServerError, "无法复制文件到响应体")
  542. return
  543. }
  544. }