h_exam.go 23 KB

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