h_exam.go 25 KB

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