h_exam.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  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 = 0.00 // 需要比赛确认起点
  24. InitialPositionY = 0.00 // 需要比赛确认起点
  25. )
  26. // 考试心跳
  27. func Tick(c *gin.Context) {
  28. param := new(webServerEntity.ExamPao)
  29. // 映射到结构体
  30. if err := c.ShouldBindJSON(&param); err != nil {
  31. c_log.GlobalLogger.Error("请求体解析失败,错误信息为:", err)
  32. c.JSON(http.StatusBadRequest, commonEntity.Response{
  33. Code: 500,
  34. Msg: "请求体解析失败。",
  35. })
  36. return
  37. }
  38. teamName := param.TeamName
  39. positionX := param.PositionX
  40. positionY := param.PositionY
  41. if !util.ContainsKey(cacheTeamName, teamName) && math.Abs(positionX-InitialPositionX) < 5.00 && math.Abs(positionY-InitialPositionY) < 5.00 { // (在起点开始)
  42. sqlTemplate, _ := util.ReadFile(c_db.SqlFilesMap["exam-insert-begin_time-by-team_name.sql"])
  43. c_log.GlobalLogger.Info("保存比赛开始时间", sqlTemplate)
  44. if err := c_db.DoTx(sqlTemplate, []any{
  45. param.TeamName,
  46. time.Now(),
  47. }); err != nil {
  48. c_log.GlobalLogger.Error("保存比赛开始时间报错:", err)
  49. c.JSON(http.StatusBadRequest, commonEntity.Response{
  50. Code: 500,
  51. Msg: "保存比赛开始时间报错。",
  52. })
  53. return
  54. }
  55. } else if !util.ContainsKey(cacheTeamName, teamName) && math.Abs(positionX-InitialPositionX) < 5.00 && math.Abs(positionY-InitialPositionY) < 5.00 { // 不在起点(开始)
  56. // todo 查询队伍上一个记录,修改结束时间为初始时间,并添加心跳到缓存
  57. } else if util.ContainsKey(cacheTeamName, teamName) { // 进行中
  58. cacheTeamName[teamName] = time.Now()
  59. }
  60. c.JSON(http.StatusOK, commonEntity.Response{
  61. Code: 200,
  62. Msg: "心跳接收成功。",
  63. })
  64. }
  65. func ExamEndTicker() {
  66. // 创建一个定时器,每隔一秒触发一次
  67. ticker := time.NewTicker(1 * time.Second)
  68. for {
  69. select {
  70. // 定时器触发时执行的代码
  71. case <-ticker.C:
  72. cacheMutex.Lock()
  73. {
  74. var keysToDelete []string
  75. for teamName, heartBeatTime := range cacheTeamName {
  76. if time.Since(heartBeatTime) > heartBeatTimeThreshold { // 检查缓存中的队名,如果超过心跳时间,则代表考试结束,删除缓存中的队名
  77. keysToDelete = append(keysToDelete, teamName)
  78. }
  79. }
  80. for _, teamName := range keysToDelete { // 检查缓存中的队名,如果超过心跳时间,则代表考试结束,删除缓存中的队名
  81. delete(cacheTeamName, teamName)
  82. // 1 查询指定队伍的开始时间最新的考试是否有结束时间,如果有则不在处理,如果没有则更新
  83. var result []webServerEntity.ExamPo
  84. selectSql, err := util.ReadFile(c_db.SqlFilesMap["exam-select-latest-by-team_name.sql"])
  85. if err != nil {
  86. c_log.GlobalLogger.Error("读取sql文件报错:", err)
  87. return
  88. }
  89. // 可以传参数
  90. if err = c_db.MysqlDb.Select(&result, selectSql, teamName); err != nil {
  91. c_log.GlobalLogger.Error("数据库查询报错:", err)
  92. return
  93. }
  94. c_log.GlobalLogger.Info("数据库查询成功:", result)
  95. if !result[0].EndTime.Equal(defaultTime) {
  96. c_log.GlobalLogger.Error("赛队", teamName, "考试已结束!")
  97. return
  98. }
  99. // 更新到数据库
  100. sqlTemplate, _ := util.ReadFile(c_db.SqlFilesMap["exam-update-end_time-by-team_name.sql"])
  101. if err := c_db.DoTx(sqlTemplate, []any{
  102. time.Now(),
  103. teamName,
  104. }); err != nil {
  105. c_log.GlobalLogger.Error("插入数据报错:", err)
  106. return
  107. }
  108. c_log.GlobalLogger.Infof("队伍 %v 的考试结束。", teamName)
  109. }
  110. }
  111. cacheMutex.Unlock()
  112. }
  113. }
  114. }
  115. // 考试开始时间
  116. func Begin(c *gin.Context) {
  117. param := new(webServerEntity.ExamPao)
  118. // 映射到结构体
  119. if err := c.ShouldBindJSON(&param); err != nil {
  120. c_log.GlobalLogger.Error("项目启动接收请求参数报错:", err)
  121. c.JSON(http.StatusBadRequest, commonEntity.Response{
  122. Code: 500,
  123. Msg: "请求体解析失败。",
  124. })
  125. return
  126. }
  127. // 插入到数据库
  128. sqlTemplate, _ := util.ReadFile(c_db.SqlFilesMap["exam-insert-begin_time-by-team_name.sql"])
  129. c_log.GlobalLogger.Info("插入比赛开始时间", sqlTemplate)
  130. if err := c_db.DoTx(sqlTemplate, []any{
  131. param.TeamName,
  132. time.Now(),
  133. }); err != nil {
  134. c_log.GlobalLogger.Error("插入数据报错:", err)
  135. c.JSON(http.StatusBadRequest, commonEntity.Response{
  136. Code: 500,
  137. Msg: "插入数据报错。",
  138. })
  139. return
  140. }
  141. c.JSON(http.StatusOK, commonEntity.Response{
  142. Code: 200,
  143. Msg: "插入数据成功。",
  144. })
  145. }
  146. // 考试结束时间
  147. func End(c *gin.Context) {
  148. param := new(webServerEntity.ExamPao)
  149. // 映射到结构体
  150. if err := c.ShouldBindJSON(&param); err != nil {
  151. c_log.GlobalLogger.Error("项目启动接收请求参数报错:", err)
  152. c.JSON(http.StatusBadRequest, commonEntity.Response{
  153. Code: 500,
  154. Msg: "请求体解析失败。",
  155. })
  156. return
  157. }
  158. // 1 查询指定队伍的开始时间最新的考试是否有结束时间,如果有则不在处理,如果没有则更新
  159. var result []webServerEntity.ExamPo
  160. selectSql, err := util.ReadFile(c_db.SqlFilesMap["exam-select-latest-by-team_name.sql"])
  161. if err != nil {
  162. c_log.GlobalLogger.Error("读取sql文件报错:", err)
  163. c.JSON(http.StatusBadRequest, commonEntity.Response{
  164. Code: 500,
  165. Msg: "读取sql文件报错。",
  166. })
  167. return
  168. }
  169. // 可以传参数
  170. if err = c_db.MysqlDb.Select(&result, selectSql, param.TeamName); 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_log.GlobalLogger.Info("数据库查询成功:", result)
  179. if !result[0].EndTime.Equal(defaultTime) {
  180. c_log.GlobalLogger.Error("赛队", param.TeamName, "重复请求考试结束接口!")
  181. c.JSON(http.StatusBadRequest, commonEntity.Response{
  182. Code: 500,
  183. Msg: "重复请求。",
  184. })
  185. return
  186. }
  187. // 更新到数据库
  188. sqlTemplate, _ := util.ReadFile(c_db.SqlFilesMap["exam-update-end_time-by-team_name.sql"])
  189. if err := c_db.DoTx(sqlTemplate, []any{
  190. time.Now(),
  191. param.TeamName,
  192. }); err != nil {
  193. c_log.GlobalLogger.Error("插入数据报错:", err)
  194. c.JSON(http.StatusBadRequest, commonEntity.Response{
  195. Code: 500,
  196. Msg: "插入数据报错。",
  197. })
  198. return
  199. }
  200. c.JSON(http.StatusOK, commonEntity.Response{
  201. Code: 200,
  202. Msg: "插入数据成功。",
  203. })
  204. }
  205. // 分页查询
  206. // todo 如果日期为默认值,则返回空""
  207. func Page(c *gin.Context) {
  208. param := new(webServerEntity.ExamPagePao)
  209. _ = c.ShouldBindJSON(&param)
  210. var resultPos []webServerEntity.ExamPo
  211. var resultPosTotal []int
  212. var pageSql string
  213. var totalSql string
  214. offset := (param.CurrentPage - 1) * param.PageSize
  215. size := param.PageSize
  216. if param.TeamName == "" && param.Topic == "" {
  217. pageSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-page.sql"])
  218. totalSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-total.sql"])
  219. err := c_db.MysqlDb.Select(&resultPos, pageSql, offset, size)
  220. if err != nil {
  221. c_log.GlobalLogger.Error(err)
  222. }
  223. err = c_db.MysqlDb.Select(&resultPosTotal, totalSql)
  224. if err != nil {
  225. c_log.GlobalLogger.Error(err)
  226. }
  227. }
  228. if param.TeamName != "" && param.Topic == "" {
  229. pageSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-page-by-team_name.sql"])
  230. totalSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-total-by-team_name.sql"])
  231. _ = c_db.MysqlDb.Select(&resultPos, pageSql, "%"+param.TeamName+"%", offset, size)
  232. _ = c_db.MysqlDb.Select(&resultPosTotal, totalSql, "%"+param.TeamName+"%")
  233. }
  234. if param.TeamName == "" && param.Topic != "" {
  235. pageSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-page-by-topic.sql"])
  236. totalSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-total-by-topic.sql"])
  237. _ = c_db.MysqlDb.Select(&resultPos, pageSql, "%"+param.Topic+"%", offset, size)
  238. _ = c_db.MysqlDb.Select(&resultPosTotal, totalSql, "%"+param.Topic+"%")
  239. }
  240. if param.TeamName != "" && param.Topic != "" {
  241. pageSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-page-by-team_name-and-topic.sql"])
  242. totalSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-total-by-team_name-and-topic.sql"])
  243. _ = c_db.MysqlDb.Select(&resultPos, pageSql, "%"+param.TeamName+"%", "%"+param.Topic+"%", offset, size)
  244. _ = c_db.MysqlDb.Select(&resultPosTotal, totalSql, "%"+param.TeamName+"%", "%"+param.Topic+"%")
  245. }
  246. var resultVos []webServerEntity.ExamVo
  247. for _, po := range resultPos {
  248. resultVos = append(resultVos, webServerEntity.ExamVo{
  249. Id: po.Id,
  250. TeamName: po.TeamName,
  251. Topic: po.Topic,
  252. BeginTime: util.GetTimeString(po.BeginTime),
  253. EndTime: util.GetTimeString(po.EndTime),
  254. ScoreOnline: po.ScoreOnline,
  255. ScoreOffline: po.ScoreOffline,
  256. ScoreFinal: po.ScoreFinal,
  257. Details: po.Details,
  258. ScoreReportPath: po.ScoreReportPath,
  259. })
  260. }
  261. c.JSON(http.StatusOK, commonEntity.Response{
  262. Code: 200,
  263. Msg: "分页查询成功!",
  264. Data: resultVos,
  265. Total: resultPosTotal[0],
  266. })
  267. }
  268. // 评分报告pdf下载
  269. func Report(c *gin.Context) {
  270. //param := new(webServerEntity.ExamReportPao)
  271. //// 映射到结构体
  272. //if err := c.ShouldBindJSON(&param); err != nil {
  273. // c_log.GlobalLogger.Error("项目启动接收请求参数报错:", err)
  274. // c.JSON(http.StatusBadRequest, commonEntity.Response{
  275. // Code: 500,
  276. // Msg: "请求体解析失败。",
  277. // })
  278. // return
  279. //}
  280. // 1 根据ID查询数据
  281. // 2 根据数据生成pdf
  282. // 3 创建pdf文件
  283. {
  284. // 1 初始化 pdf 对象
  285. pdf := gopdf.GoPdf{}
  286. pdf.Start(gopdf.Config{PageSize: *gopdf.PageSizeA4})
  287. // 2 添加一页
  288. pdf.AddPage()
  289. // 3
  290. err := pdf.AddTTFFont("simfang", "D:\\code\\cicv-data-closedloop\\test\\pdf\\ttf\\simfang.ttf")
  291. if err != nil {
  292. log.Print(err.Error())
  293. return
  294. }
  295. err = pdf.SetFont("simfang", "", 14)
  296. if err != nil {
  297. log.Print(err.Error())
  298. return
  299. }
  300. err = pdf.Cell(nil, "您好")
  301. if err != nil {
  302. return
  303. }
  304. err = pdf.WritePdf("D:\\hello.pdf")
  305. if err != nil {
  306. return
  307. }
  308. }
  309. // 打开要发送的文件
  310. file, err := os.Open("D:\\hello.pdf")
  311. if err != nil {
  312. c.String(http.StatusNotFound, "文件未找到")
  313. return
  314. }
  315. defer file.Close()
  316. // 将文件复制到响应主体
  317. _, err = io.Copy(c.Writer, file)
  318. if err != nil {
  319. c.String(http.StatusInternalServerError, "无法复制文件到响应体")
  320. return
  321. }
  322. }