package handler import ( webServerEntity "cicv-data-closedloop/amd64/web_server/entity" "cicv-data-closedloop/amd64/web_server/infra" "cicv-data-closedloop/common/config/c_db" "cicv-data-closedloop/common/config/c_log" commonEntity "cicv-data-closedloop/common/entity" "cicv-data-closedloop/common/util" "encoding/json" "fmt" "github.com/gin-gonic/gin" "github.com/signintech/gopdf" "io" "log" "math" "net/http" "os" "strconv" "sync" "time" ) var ( defaultTime = time.Date(2006, time.January, 2, 15, 4, 5, 0, time.Local) cacheMutex sync.Mutex cacheTeamName = make(map[string]time.Time) heartBeatTimeThreshold = 5 * time.Second // 心跳时间 InitialPositionX = 456292.00 // todo 需要比赛确认起点 InitialPositionY = 4397957.00 // todo 需要比赛确认起点 trialBegin = time.Date(2024, time.June, 16, 13, 00, 00, 0, time.Local) trialEnd = time.Date(2024, time.June, 19, 23, 59, 59, 0, time.Local) ) // 考试心跳 func Tick(c *gin.Context) { param := new(webServerEntity.ExamPao) // 映射到结构体 if err := c.ShouldBindJSON(¶m); err != nil { c_log.GlobalLogger.Error("请求体解析失败,错误信息为:", err) c.JSON(http.StatusBadRequest, commonEntity.Response{ Code: 500, Msg: "请求体解析失败。", }) return } teamName := param.TeamName positionX, _ := strconv.ParseFloat(param.PositionX, 64) var positionY float64 numStr := param.PositionY _, _ = fmt.Sscanf(numStr, "%e", &positionY) if !util.ContainsKey(cacheTeamName, teamName) && math.Abs(positionX-InitialPositionX) < 5.00 && math.Abs(positionY-InitialPositionY) < 5.00 { // (在起点开始) sqlTemplate, _ := util.ReadFile(c_db.SqlFilesMap["exam-insert-begin_time-and-topic-and-equipment_no-by-team_name.sql"]) stage := "" if time.Now().Before(trialBegin) { stage = "表演赛" } else if time.Now().After(trialBegin) && time.Now().Before(trialEnd) { stage = "预赛" } else { stage = "决赛" } competitionBegin := time.Now() cacheTeamName[teamName] = competitionBegin c_log.GlobalLogger.Infof("当前比赛阶段为 %v ,队伍 %v 在起始点范围内第一次启动,保存新一条比赛记录的开始时间 %v", stage, teamName, competitionBegin) if err := c_db.DoTx(sqlTemplate, []any{param.TeamName, stage, competitionBegin, param.EquipmentNo}); err != nil { c_log.GlobalLogger.Error("保存比赛开始时间报错:", err) c.JSON(http.StatusBadRequest, commonEntity.Response{Code: 500, Msg: "保存比赛开始时间报错。"}) return } c.JSON(http.StatusOK, commonEntity.Response{ Code: 200, Msg: "队伍 " + teamName + " 的考试开始。", }) } else if !util.ContainsKey(cacheTeamName, teamName) && (math.Abs(positionX-InitialPositionX) > 5.00 || math.Abs(positionY-InitialPositionY) > 5.00) { // 不在起点(开始) // 车辆不是在起点启动的自动驾驶模式 var result []webServerEntity.ExamPo { selectSql, err := util.ReadFile(c_db.SqlFilesMap["exam-select-latest-by-team_name.sql"]) if err != nil { c_log.GlobalLogger.Error("读取sql文件报错:", err) return } if err = c_db.MysqlDb.Select(&result, selectSql, teamName); err != nil { c_log.GlobalLogger.Error("数据库查询报错:", err) return } if len(result) == 1 { c_log.GlobalLogger.Info("上一条数据记录为:", result[0]) // 添加队伍名到缓存中 cacheTeamName[teamName] = time.Now() } else { c_log.GlobalLogger.Errorf("队伍 %v 的考试车辆未在起点开启自动驾驶模式,且今日无考试记录,错误启动自动驾驶模式。", param.TeamName) c.JSON(http.StatusOK, commonEntity.Response{ Code: 400, Msg: "车辆未在起点开启自动驾驶模式,且今日无考试记录,错误启动自动驾驶模式。", }) } } // 更新记录结束时间为默认时间 sqlTemplate, _ := util.ReadFile(c_db.SqlFilesMap["exam-update-end_time-by-id.sql"]) if err := c_db.DoTx(sqlTemplate, []any{ defaultTime, result[0].Id, }); err != nil { c_log.GlobalLogger.Error("插入数据报错:", err) return } c_log.GlobalLogger.Infof("队伍 %v 的考试在中途中断后重新开始。", teamName) c.JSON(http.StatusOK, commonEntity.Response{ Code: 200, Msg: "队伍 " + teamName + " 的考试在中途中断后重新开始。", }) } else if util.ContainsKey(cacheTeamName, teamName) { // 进行中 cacheTeamName[teamName] = time.Now() c_log.GlobalLogger.Errorf("接收到心跳信息,队伍名字为 %s,x坐标为 %.f,y坐标为 %.f ", teamName, positionX, positionY) c.JSON(http.StatusOK, commonEntity.Response{ Code: 200, Msg: "队伍 " + teamName + " 的考试进行中,心跳接收成功。", }) } else { c_log.GlobalLogger.Errorf("接收到心跳信息但考试并未开始,队伍名字为 %s,x坐标为 %.f,y坐标为 %.f ", teamName, positionX, positionY) c.JSON(http.StatusOK, commonEntity.Response{ Code: 200, Msg: "队伍 " + teamName + " 的考试未开始。", }) } } func ExamEndTicker() { // 创建一个定时器,每隔一秒触发一次 ticker := time.NewTicker(1 * time.Second) for { select { // 定时器触发时执行的代码 case <-ticker.C: cacheMutex.Lock() { var keysToDelete []string for teamName, heartBeatTime := range cacheTeamName { if time.Since(heartBeatTime) > heartBeatTimeThreshold { // 检查缓存中的队名,如果超过心跳时间,则代表考试结束,删除缓存中的队名 c_log.GlobalLogger.Infof("队伍 %v 心跳超时,比赛结束。", teamName) keysToDelete = append(keysToDelete, teamName) } } for _, teamName := range keysToDelete { // 检查缓存中的队名,如果超过心跳时间,则代表考试结束,删除缓存中的队名 delete(cacheTeamName, teamName) // 1 查询指定队伍的开始时间最新的考试是否有结束时间,如果有则不在处理,如果没有则更新 var result []webServerEntity.ExamPo selectSql, err := util.ReadFile(c_db.SqlFilesMap["exam-select-latest-by-team_name.sql"]) if err != nil { c_log.GlobalLogger.Error("读取sql文件报错:", err) return } // 可以传参数 if err = c_db.MysqlDb.Select(&result, selectSql, teamName); err != nil { c_log.GlobalLogger.Error("数据库查询报错:", err) return } if !result[0].EndTime.Equal(defaultTime) { c_log.GlobalLogger.Error("赛队", teamName, "考试已结束!") return } // 更新到数据库(只更新最新一条) stage := "" if time.Now().Before(trialBegin) { stage = "表演赛" } else if time.Now().After(trialBegin) && time.Now().Before(trialEnd) { stage = "预赛" } else { stage = "决赛" } // 查询最新一条的id selectSql, err = util.ReadFile(c_db.SqlFilesMap["exam-select-max-id-by-team_name-and-topic.sql"]) if err != nil { c_log.GlobalLogger.Error("读取sql文件报错:", err) return } // 可以传参数 var resultId []int if err = c_db.MysqlDb.Select(&resultId, selectSql, teamName, stage); err != nil { c_log.GlobalLogger.Error("数据库查询报错:", err) return } if len(resultId) == 1 { c_log.GlobalLogger.Info("更新数据结束时间,id为:", resultId) sqlTemplate, _ := util.ReadFile(c_db.SqlFilesMap["exam-update-end_time-by-id.sql"]) if err := c_db.DoTx(sqlTemplate, []any{ time.Now(), resultId[0], }); err != nil { c_log.GlobalLogger.Error("插入数据报错:", err) return } } else { c_log.GlobalLogger.Error("查询id失败:", resultId) } c_log.GlobalLogger.Infof("队伍 %v 的考试结束。", teamName) } } cacheMutex.Unlock() } } } //// 考试开始时间 //func Begin(c *gin.Context) { // param := new(webServerEntity.ExamPao) // // 映射到结构体 // if err := c.ShouldBindJSON(¶m); err != nil { // c_log.GlobalLogger.Error("项目启动接收请求参数报错:", err) // c.JSON(http.StatusBadRequest, commonEntity.Response{ // Code: 500, // Msg: "请求体解析失败。", // }) // return // } // // 插入到数据库 // sqlTemplate, _ := util.ReadFile(c_db.SqlFilesMap["exam-insert-begin_time-by-team_name.sql"]) // c_log.GlobalLogger.Info("插入比赛开始时间", sqlTemplate) // if err := c_db.DoTx(sqlTemplate, []any{ // param.TeamName, // time.Now(), // }); err != nil { // c_log.GlobalLogger.Error("插入数据报错:", err) // c.JSON(http.StatusBadRequest, commonEntity.Response{ // Code: 500, // Msg: "插入数据报错。", // }) // return // } // c.JSON(http.StatusOK, commonEntity.Response{ // Code: 200, // Msg: "插入数据成功。", // }) //} // //// 考试结束时间 //func End(c *gin.Context) { // param := new(webServerEntity.ExamPao) // // 映射到结构体 // if err := c.ShouldBindJSON(¶m); err != nil { // c_log.GlobalLogger.Error("接收请求参数报错:", err) // c.JSON(http.StatusBadRequest, commonEntity.Response{ // Code: 500, // Msg: "请求体解析失败。", // }) // return // } // // 1 查询指定队伍的开始时间最新的考试是否有结束时间,如果有则不在处理,如果没有则更新 // var result []webServerEntity.ExamPo // selectSql, err := util.ReadFile(c_db.SqlFilesMap["exam-select-latest-by-team_name.sql"]) // if err != nil { // c_log.GlobalLogger.Error("读取sql文件报错:", err) // c.JSON(http.StatusBadRequest, commonEntity.Response{ // Code: 500, // Msg: "读取sql文件报错。", // }) // return // } // // 可以传参数 // if err = c_db.MysqlDb.Select(&result, selectSql, param.TeamName); err != nil { // c_log.GlobalLogger.Error("数据库查询报错:", err) // c.JSON(http.StatusBadRequest, commonEntity.Response{ // Code: 500, // Msg: "数据库查询报错。", // }) // return // } // c_log.GlobalLogger.Info("数据库查询成功:", result) // if !result[0].EndTime.Equal(defaultTime) { // c_log.GlobalLogger.Error("赛队", param.TeamName, "重复请求考试结束接口!") // c.JSON(http.StatusBadRequest, commonEntity.Response{ // Code: 500, // Msg: "重复请求。", // }) // return // } // // 更新到数据库 // sqlTemplate, _ := util.ReadFile(c_db.SqlFilesMap["exam-update-end_time-by-team_name.sql"]) // if err := c_db.DoTx(sqlTemplate, []any{ // time.Now(), // param.TeamName, // }); err != nil { // c_log.GlobalLogger.Error("插入数据报错:", err) // c.JSON(http.StatusBadRequest, commonEntity.Response{ // Code: 500, // Msg: "插入数据报错。", // }) // return // } // c.JSON(http.StatusOK, commonEntity.Response{ // Code: 200, // Msg: "插入数据成功。", // }) //} // 分页查询 // 如果日期为默认值,则返回空"" func Page(c *gin.Context) { param := new(webServerEntity.ExamPagePao) _ = c.ShouldBindJSON(¶m) var resultPos []webServerEntity.ExamPo var resultPosTotal []int var pageSql string var totalSql string offset := (param.CurrentPage - 1) * param.PageSize size := param.PageSize if param.TeamName == "" && param.Topic == "" { pageSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-page.sql"]) totalSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-total.sql"]) err := c_db.MysqlDb.Select(&resultPos, pageSql, offset, size) if err != nil { c_log.GlobalLogger.Error(err) } err = c_db.MysqlDb.Select(&resultPosTotal, totalSql) if err != nil { c_log.GlobalLogger.Error(err) } } if param.TeamName != "" && param.Topic == "" { pageSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-page-by-team_name.sql"]) totalSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-total-by-team_name.sql"]) _ = c_db.MysqlDb.Select(&resultPos, pageSql, "%"+param.TeamName+"%", offset, size) _ = c_db.MysqlDb.Select(&resultPosTotal, totalSql, "%"+param.TeamName+"%") } if param.TeamName == "" && param.Topic != "" { pageSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-page-by-topic.sql"]) totalSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-total-by-topic.sql"]) _ = c_db.MysqlDb.Select(&resultPos, pageSql, "%"+param.Topic+"%", offset, size) _ = c_db.MysqlDb.Select(&resultPosTotal, totalSql, "%"+param.Topic+"%") } if param.TeamName != "" && param.Topic != "" { pageSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-page-by-team_name-and-topic.sql"]) totalSql, _ = util.ReadFile(c_db.SqlFilesMap["exam-select-total-by-team_name-and-topic.sql"]) _ = c_db.MysqlDb.Select(&resultPos, pageSql, "%"+param.TeamName+"%", "%"+param.Topic+"%", offset, size) _ = c_db.MysqlDb.Select(&resultPosTotal, totalSql, "%"+param.TeamName+"%", "%"+param.Topic+"%") } var resultVos []webServerEntity.ExamVo for _, po := range resultPos { resultVos = append(resultVos, webServerEntity.ExamVo{ Id: po.Id, TeamName: po.TeamName, Topic: po.Topic, BeginTime: util.GetTimeString(po.BeginTime), EndTime: util.GetTimeString(po.EndTime), ScoreOnline: po.ScoreOnline, ScoreOffline: po.ScoreOffline, ScoreFinal: po.ScoreFinal, Details: po.Details, ScoreReportPath: po.ScoreReportPath, }) } c.JSON(http.StatusOK, commonEntity.Response{ Code: 200, Msg: "分页查询成功!", Data: resultVos, Total: resultPosTotal[0], }) } // 评分报告pdf下载 func Report(c *gin.Context) { param := new(webServerEntity.ExamReportPao) // 映射到结构体 if err := c.ShouldBindJSON(¶m); err != nil { c_log.GlobalLogger.Error("接收请求参数报错:", err) c.JSON(http.StatusBadRequest, commonEntity.Response{ Code: 500, Msg: "请求体解析失败。", }) return } // 查询打分详情 var detailsResult []string selectSql, err := util.ReadFile(c_db.SqlFilesMap["exam-select-details-by-id.sql"]) if err != nil { c_log.GlobalLogger.Error("读取sql文件报错:", err) c.JSON(http.StatusBadRequest, commonEntity.Response{ Code: 500, Msg: "读取sql文件报错。", }) return } // 可以传参数 if err = c_db.MysqlDb.Select(&detailsResult, selectSql, param.Id); err != nil { c_log.GlobalLogger.Error("数据库查询报错:", err) c.JSON(http.StatusBadRequest, commonEntity.Response{ Code: 500, Msg: "数据库查询报错。", }) return } details := detailsResult[0] // 解析详情为数组 var detailsDtos []webServerEntity.DetailsDto _ = json.Unmarshal([]byte(details), &detailsDtos) layout := "2006-01-02 15:04:05" beginTime, _ := time.Parse(layout, param.BeginTime) endTime, _ := time.Parse(layout, param.EndTime) // 1 根据ID查询数据 // 2 根据数据生成pdf // 3 创建pdf文件 { // 初始化 pdf 对象 pdf := gopdf.GoPdf{} pdf.Start(gopdf.Config{PageSize: *gopdf.PageSizeA4}) // 添加字体文件到pdf err := pdf.AddTTFFont("simfang", infra.ApplicationYaml.Pdf.Ttf) if err != nil { log.Print(err.Error()) return } leftMargin := 70.0 topMargin := 100.0 lineHeight := 25.0 tableWidth := 450.0 pdf.SetLeftMargin(leftMargin) // 设置左边距 alignOptionText := gopdf.CellOption{Align: gopdf.Left | gopdf.Middle} //alignOptionText2 := gopdf.CellOption{Align: gopdf.Center | gopdf.Middle} alignOptionTable := gopdf.CellOption{Align: gopdf.Center | gopdf.Middle, Border: gopdf.Left | gopdf.Right | gopdf.Bottom | gopdf.Top} alignOptionTable2 := gopdf.CellOption{Align: gopdf.Left | gopdf.Middle} // ------- 封面页 ------- pdf.AddPage() { err = pdf.SetFont("simfang", "", 36) err = pdf.Image(infra.ApplicationYaml.Pdf.BackgroundPng, 0, 0, nil) // 背景图片 err = pdf.Image(infra.ApplicationYaml.Pdf.LogoPng, 20, 20, &gopdf.Rect{W: 320, H: 100}) { pdf.SetXY(100, 250) _ = pdf.Text("车路云一体化算法挑战赛") } { pdf.SetXY(220, 315) // 200 _ = pdf.Text("评分报告") } _ = pdf.SetFont("simfang", "", 14) currentLine := 0.0 tableCellWidth := 100.0 tableLeftMartin := 190.0 tableTopMartin := 500.0 { { pdf.SetXY(tableLeftMartin, tableTopMartin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: 100, H: lineHeight}, "赛队编号:", alignOptionTable2) } { pdf.SetXY(tableLeftMartin+1.0*tableCellWidth, tableTopMartin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: 100, H: lineHeight}, param.TeamName, alignOptionTable2) } currentLine++ } { { pdf.SetXY(tableLeftMartin, tableTopMartin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: 100, H: lineHeight}, "评测车型:", alignOptionTable2) } { pdf.SetXY(tableLeftMartin+1.0*tableCellWidth, tableTopMartin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: 100, H: lineHeight}, "多功能车平台", alignOptionTable2) } currentLine++ } { { pdf.SetXY(tableLeftMartin, tableTopMartin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: 100, H: lineHeight}, "评测地点:", alignOptionTable2) } { pdf.SetXY(tableLeftMartin+1.0*tableCellWidth, tableTopMartin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: 100, H: lineHeight}, "天津生态城", alignOptionTable2) } currentLine++ } { { pdf.SetXY(tableLeftMartin, tableTopMartin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: 100, H: lineHeight}, "报告时间:", alignOptionTable2) } { pdf.SetXY(tableLeftMartin+1.0*tableCellWidth, tableTopMartin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: 100, H: lineHeight}, time.Now().Format("2006年01月02日"), alignOptionTable2) } currentLine++ } { pdf.SetXY(165, 775) _ = pdf.Cell(nil, "国汽(北京)智能网联汽车研究院有限公司") } } // todo 先不要目录页 //// ------- 目录页 ------- //{ // pdf.AddPage() // currentLine := 0.0 // { // err = pdf.SetFont("simfang", "", 18) // pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight) // _ = pdf.CellWithOption(&gopdf.Rect{W: tableWidth, H: lineHeight}, "目录", alignOptionText2) // currentLine++ // currentLine++ // } // { // err = pdf.SetFont("simfang", "", 14) // pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight) // currentLine++ // _ = pdf.CellWithOption(&gopdf.Rect{W: tableWidth, H: lineHeight}, "1. 总体情况 ............................... 1", alignOptionText) // } //} // ------- 详情页 第一页 ------- { pdf.AddPage() currentLine := 0.0 // 1. 总体分数情况 { { pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: tableWidth, H: lineHeight}, "1. 总体分数情况", alignOptionText) currentLine++ } { pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: tableWidth, H: lineHeight}, "整体得分:"+util.ToString(param.ScoreFinal), alignOptionText) currentLine++ } { pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: tableWidth, H: lineHeight}, "线上得分:"+util.ToString(param.ScoreFinal), alignOptionText) currentLine++ } { pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: tableWidth, H: lineHeight}, "线下得分:", alignOptionText) currentLine++ } { pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: tableWidth, H: lineHeight}, "整体耗时:"+endTime.Sub(beginTime).String(), alignOptionText) currentLine++ } } // 2. 分数详情 { //columnNumber := 4.0 // 列数 //tableCellWidth := tableWidth / columnNumber // 单元格宽度 tableCellWidth1 := 50.0 tableCellWidth2 := 300.0 tableCellWidth3 := 70.0 { pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: tableWidth, H: lineHeight}, "2. 分数详情", alignOptionText) currentLine++ } // 分数详情表格 { { pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth1, H: lineHeight}, "序号", alignOptionTable) } { pdf.SetXY(leftMargin+tableCellWidth1, topMargin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth2, H: lineHeight}, "场景名称", alignOptionTable) } { pdf.SetXY(leftMargin+tableCellWidth1+tableCellWidth2, topMargin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth3, H: lineHeight}, "线上得分", alignOptionTable) } currentLine++ } for i := 0; i < len(detailsDtos); i++ { { { pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth1, H: lineHeight}, util.ToString(i+1), alignOptionTable) } { pdf.SetXY(leftMargin+tableCellWidth1, topMargin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth2, H: lineHeight}, detailsDtos[i].SceneName, alignOptionTable) } { pdf.SetXY(leftMargin+tableCellWidth1+tableCellWidth2, topMargin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth3, H: lineHeight}, util.ToString(detailsDtos[i].TotalScore+detailsDtos[i].DeductedScore), alignOptionTable) } currentLine++ } } } } // ------- 详情页 第二页 ------- var detailsDtos2 []webServerEntity.DetailsDto // 扣分的场景 for _, detailsDto := range detailsDtos { if detailsDto.Reason != "" { detailsDtos2 = append(detailsDtos2, detailsDto) } } size := len(detailsDtos2) { pdf.AddPage() currentLine := 0.0 // 3. 线上扣分详情 { //columnNumber := 3.0 // 列数 //tableCellWidth := tableWidth / columnNumber // 单元格宽度 tableCellWidth := 450.0 // 单元格宽度 { pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: tableWidth, H: lineHeight}, "3. 线上扣分详情", alignOptionText) currentLine++ } for i := 0; i < 8; i++ { // 场景1 { { pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: tableWidth, H: lineHeight}, "("+util.ToString(i+1)+")"+detailsDtos2[i].SceneName, alignOptionText) currentLine++ } // 场景1 表格 { //{ // pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight) // _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, "序号", alignOptionTable) //} //{ // pdf.SetXY(leftMargin+1.0*tableCellWidth, topMargin+currentLine*lineHeight) // _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, "扣分点", alignOptionTable) //} { pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, "扣分说明", alignOptionTable) } currentLine++ } //for j := 0; j < 1; j++ { { //{ // pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight) // _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, util.ToString(j+1), alignOptionTable) //} //{ // pdf.SetXY(leftMargin+1.0*tableCellWidth, topMargin+currentLine*lineHeight) // _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, "xxx", alignOptionTable) //} { pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, detailsDtos2[i].Reason, alignOptionTable) } currentLine++ } //} } } } //if size <= 8 { // // 4 作品优化建议 // { // pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight) // _ = pdf.CellWithOption(&gopdf.Rect{W: tableWidth, H: lineHeight}, "4. 作品优化建议", alignOptionText) // currentLine++ // } //} } // ------- 详情页 第三页 ------- if size > 8 { pdf.AddPage() currentLine := 0.0 // 3. 线上扣分详情 { columnNumber := 3.0 // 列数 tableCellWidth := tableWidth / columnNumber // 单元格宽度 for i := 5; i < 10; i++ { // 场景1 { { pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: tableWidth, H: lineHeight}, "("+util.ToString(i+1)+")xxxx场景", alignOptionText) currentLine++ } // 场景1 表格 { { pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, "序号", alignOptionTable) } { pdf.SetXY(leftMargin+1.0*tableCellWidth, topMargin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, "扣分点", alignOptionTable) } { pdf.SetXY(leftMargin+2.0*tableCellWidth, topMargin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, "扣分说明", alignOptionTable) } currentLine++ } for j := 0; j < 2; j++ { { { pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, util.ToString(j+1), alignOptionTable) } { pdf.SetXY(leftMargin+1.0*tableCellWidth, topMargin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, "xxx", alignOptionTable) } { pdf.SetXY(leftMargin+2.0*tableCellWidth, topMargin+currentLine*lineHeight) _ = pdf.CellWithOption(&gopdf.Rect{W: tableCellWidth, H: lineHeight}, "xxx", alignOptionTable) } currentLine++ } } } } } //// 4 作品优化建议 //{ // pdf.SetXY(leftMargin, topMargin+currentLine*lineHeight) // _ = pdf.CellWithOption(&gopdf.Rect{W: tableWidth, H: lineHeight}, "4. 作品优化建议", alignOptionText) // currentLine++ //} } //// ------- 附录页 ------- //pdf.AddPage() //{ // pdf.SetXY(leftMargin, topMargin) // _ = pdf.Text("附件:评分规则") // pdf.AddExternalLink("https://www.baidu.com/", leftMargin, topMargin-lineHeight, 6*14, lineHeight) //} // 写入本地 _ = pdf.WritePdf("D:\\hello.pdf") } // 打开要发送的文件 file, err := os.Open("D:\\hello.pdf") if err != nil { c.String(http.StatusNotFound, "文件未找到") return } defer file.Close() // 将文件复制到响应主体 _, err = io.Copy(c.Writer, file) if err != nil { c.String(http.StatusInternalServerError, "无法复制文件到响应体") return } }