h_exam.go 24 KB

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