package children_competition import ( "archive/zip" "bytes" "context" "dubbo.apache.org/dubbo-go/v3/common/logger" "encoding/base64" "errors" "fmt" service "github.com/fonchain_enterprise/fonchain-main/api" "github.com/fonchain_enterprise/fonchain-main/api/children_competition" "github.com/fonchain_enterprise/fonchain-main/api/equity" "github.com/fonchain_enterprise/fonchain-main/pkg/config" "github.com/fonchain_enterprise/fonchain-main/pkg/e" "github.com/fonchain_enterprise/fonchain-main/pkg/model" service2 "github.com/fonchain_enterprise/fonchain-main/pkg/service" "github.com/fonchain_enterprise/fonchain-main/pkg/utils" "github.com/gin-gonic/gin" "github.com/skip2/go-qrcode" "golang.org/x/image/font" "golang.org/x/image/font/opentype" "golang.org/x/image/math/fixed" "image" "image/color" "image/draw" "image/jpeg" "io" "os" "strings" "sync" "time" ) // GetRegistrationRecordInfo 获取所有的作品信息 func GetRegistrationRecordInfo(c *gin.Context) { var req children_competition.GetRegistrationRecordInfoRequest if err := c.ShouldBind(&req); err != nil { logger.Errorf("GetRegistrationRecordInfoRequest ShouldBind err", err) service2.ResponseQuickMsg(c, e.Failed, err.Error(), nil) return } resp, err := service2.ChildrenCompetitionProvider.GetRegistrationRecordInfo(context.Background(), &req) if err != nil { service2.ResponseQuickMsg(c, e.Failed, err.Error(), nil) return } service2.ResponseQuickMsg(c, e.Ok, resp.Msg, resp) return } func GetParticipantRegistrationRecords(c *gin.Context) { var req children_competition.GetParticipantRegistrationRecordsRequest if err := c.ShouldBind(&req); err != nil { logger.Errorf("GetParticipantRegistrationRecordsRequest ShouldBind err", err) service2.ResponseQuickMsg(c, e.Failed, err.Error(), nil) return } resp, err := service2.ChildrenCompetitionProvider.GetParticipantRegistrationRecords(context.Background(), &req) if err != nil { service2.ResponseQuickMsg(c, e.Failed, err.Error(), nil) return } service2.ResponseQuickMsg(c, e.Ok, resp.Msg, resp) return } // SetRegistrationDeadline 获取所有的作品信息 func SetRegistrationDeadline(c *gin.Context) { var req children_competition.SetRegistrationDeadlineRequest if err := c.ShouldBind(&req); err != nil { logger.Errorf("SetRegistrationDeadlineRequest ShouldBind err", err) service2.ResponseQuickMsg(c, e.Failed, err.Error(), nil) return } resp, err := service2.ChildrenCompetitionProvider.SetRegistrationDeadline(context.Background(), &req) if err != nil { service2.ResponseQuickMsg(c, e.Failed, err.Error(), nil) return } service2.ResponseQuickMsg(c, e.Ok, resp.Msg, resp) return } // UpdateWorkStatus 更新作品状态 func UpdateWorkStatus(c *gin.Context) { var req children_competition.UpdateWorkStatusRequest if err := c.ShouldBind(&req); err != nil { logger.Errorf("UpdateWorkStatusRequest ShouldBind err", err) service2.ResponseQuickMsg(c, e.Failed, err.Error(), nil) return } resp, err := service2.ChildrenCompetitionProvider.UpdateWorkStatus(context.Background(), &req) if err != nil { service2.ResponseQuickMsg(c, e.Failed, err.Error(), nil) return } service2.ResponseQuickMsg(c, e.Ok, resp.Msg, resp) return } // GenerateQrCode 生成投票二维码 func GenerateQrCode(c *gin.Context) { var req children_competition.GenerateQrCodeRequest if err := c.ShouldBind(&req); err != nil { logger.Errorf("GenerateQrCode ShouldBind err", err) service2.ResponseQuickMsg(c, e.Failed, err.Error(), nil) return } getWorkInfoReq := &children_competition.GetWorkInfoRequest{ WorkUid: req.WorkUid, } getWorkInfoResp, err := service2.ChildrenCompetitionProvider.GetWorkInfo(context.Background(), getWorkInfoReq) if err != nil { service2.ResponseQuickMsg(c, e.Failed, err.Error(), nil) return } var contentUrl string if config.Env == "test" || config.Env == "dev" { contentUrl = "https://kid-art-test.szjixun.cn/vote?uid=" + getWorkInfoResp.WorkUid } else if config.Env == "prod" { contentUrl = "https://kid-art.szjixun.cn/vote?uid=" + getWorkInfoResp.WorkUid } else { service2.ResponseQuickMsg(c, e.Failed, "未知的环境", nil) return } //生成图片base64编码 var jpegBytes []byte var base64Str string if req.Type == 1 { // 使用模版 jpegBytes, err = composeImage("./data/儿童画展模版图片.png", "", contentUrl, "《"+getWorkInfoResp.WorkName+"》", getWorkInfoResp.Name, "./data/儿童画展字体.ttf") if err != nil { service2.ResponseQuickMsg(c, e.Failed, err.Error(), nil) return } base64Str = base64.StdEncoding.EncodeToString(jpegBytes) } else if req.Type == 2 { // 不使用模版 prefix := "data:image/png;base64," base64Str, err = utils.CreateQrBase64(contentUrl, "") if err != nil { service2.ResponseQuickMsg(c, e.Failed, err.Error(), nil) return } // 上次生成的格式为 "data:image/png;base64,/9j/2wCEAAgGBgcGBQgHBwcJCQgKDBQNDAs" 去掉前缀才是标准的base64编码 base64Str = strings.TrimPrefix(base64Str, prefix) } else { service2.ResponseQuickMsg(c, e.Failed, "未知的类型", nil) } generateQrCodeRespond := &children_competition.QrCodeInfo{ WorkUid: getWorkInfoResp.WorkUid, WorkName: getWorkInfoResp.WorkName, PicUrl: getWorkInfoResp.PicUrl, Name: getWorkInfoResp.Name, Phone: getWorkInfoResp.Phone, Length: getWorkInfoResp.Length, Wide: getWorkInfoResp.Wide, Status: getWorkInfoResp.Status, QrBase64: base64Str, } service2.ResponseQuickMsg(c, e.Ok, "SUCCESS", generateQrCodeRespond) return } // BatchGenerateQrCode 生成全部投票二维码 func BatchGenerateQrCode(c *gin.Context) { var req children_competition.BatchGenerateQrCodeRequest if err := c.ShouldBind(&req); err != nil { logger.Errorf("BatchGenerateQrCodeRequest ShouldBind err", err) service2.ResponseQuickMsg(c, e.Failed, err.Error(), nil) return } resp, err := service2.ChildrenCompetitionProvider.BatchGenerateQrCode(context.Background(), &req) if err != nil { service2.ResponseQuickMsg(c, e.Failed, err.Error(), nil) return } currentTimeFormat := time.Now().Format("20060102") templatePath := "./runtime/儿童美术作品" + currentTimeFormat + ".zip" zipFile, err := os.Create(templatePath) if err != nil { service2.ResponseQuickMsg(c, e.Failed, err.Error(), nil) return } defer zipFile.Close() zipWriter := zip.NewWriter(zipFile) defer zipWriter.Close() // 接口响应时间太长,使用并发 var wg sync.WaitGroup results := make(chan result, len(resp.QrCodeInfo)) for _, v := range resp.QrCodeInfo { wg.Add(1) //var voteUrl string //if config.Env == "test" || config.Env == "dev" || config.Env == "local" { // voteUrl = "https://kid-art-test.szjixun.cn/vote?uid=" + v.WorkUid //} else if config.Env == "prod" { // voteUrl = "https://appointteam.szjixun.cn/vote?uid=" + v.WorkUid //} else { // service2.ResponseQuickMsg(c, e.Failed, "未知的环境", nil) // return //} // //voteUrl = url.QueryEscape(voteUrl) //contentUrl := "https://appointteam.szjixun.cn/api/appointment/wx?notifyUrl=" + voteUrl var contentUrl string if config.Env == "test" || config.Env == "dev" || config.Env == "local" { contentUrl = "https://kid-art-test.szjixun.cn/vote?uid=" + v.WorkUid } else if config.Env == "prod" { contentUrl = "https://kid-art.szjixun.cn/vote?uid=" + v.WorkUid } else { service2.ResponseQuickMsg(c, e.Failed, "未知的环境", nil) return } prefix := "data:image/png;base64," if req.Type == 1 { // 使用模版 wgComposeImage("./data/儿童画展模版图片.png", "", contentUrl, "《"+strings.Split(v.WorkName, "(")[0]+"》", v.Name, "./data/儿童画展字体.ttf", v.WorkName, results, &wg) } else if req.Type == 2 { // 不使用模版 wgCreateQrBase64(contentUrl, "", prefix, v.WorkName, results, &wg) } else { service2.ResponseQuickMsg(c, e.Failed, "未知的类型", nil) } } // 启动一个goroutine来等待所有worker完成 //go func() { wg.Wait() close(results) // 关闭channel,表示没有更多的结果 //}() // 从channel中收集结果 for v := range results { if v.err != nil { service2.ResponseQuickMsg(c, e.Failed, err.Error(), nil) return } else { var f io.Writer f, err = zipWriter.Create(v.workName + ".jpg") if err != nil { service2.ResponseQuickMsg(c, e.Failed, err.Error(), nil) return } _, err = f.Write(v.jpegBytes) if err != nil { service2.ResponseQuickMsg(c, e.Failed, err.Error(), nil) return } } } var httpType string if config.IsHttps { httpType = model.HttpsType } else { httpType = model.HttpType } var exportUrl = fmt.Sprintf("%s%s/static/儿童美术作品"+currentTimeFormat+".zip", httpType, c.Request.Host) repEF := &equity.RechargeRecordManageExportFormRespond{ ExportUrl: exportUrl, } service.Success(c, repEF) } // GetWorksVotesInfo 获取投票结果 func GetWorksVotesInfo(c *gin.Context) { var req children_competition.GetWorksVotesInfoRequest if err := c.ShouldBind(&req); err != nil { logger.Errorf("UpdateWorkStatusRequest ShouldBind err", err) service2.ResponseQuickMsg(c, e.Failed, err.Error(), nil) return } resp, err := service2.ChildrenCompetitionProvider.GetWorksVotesInfo(context.Background(), &req) if err != nil { service2.ResponseQuickMsg(c, e.Failed, err.Error(), nil) return } service2.ResponseQuickMsg(c, e.Ok, resp.Msg, resp) return } func WorksVotesExportForm(c *gin.Context) { var req children_competition.WorksVotesExportFormRequest if err := c.ShouldBind(&req); err != nil { logger.Errorf("WorksVotesExportFormRequest ShouldBind err", err) service2.ResponseQuickMsg(c, e.Failed, err.Error(), nil) return } resp, err := service2.ChildrenCompetitionProvider.WorksVotesExportForm(context.Background(), &req) if err != nil { service2.ResponseQuickMsg(c, e.Failed, err.Error(), nil) return } columns := []string{"参赛作品uid", "作品排名", "作品名称", "作品图片链接", "作品尺寸(cm)", "参赛人员姓名", "手机号", "总票数", "投票截止时间"} var data []interface{} // 添加数据行 for _, worksVotesInfo := range resp.WorksVotesInfo { var info []string info = append(info, worksVotesInfo.Uid) // 参赛作品uid info = append(info, fmt.Sprintf("%d", worksVotesInfo.Ranking)) // 作品排名 info = append(info, worksVotesInfo.WorkName) // 作品名称 info = append(info, worksVotesInfo.PicUrl) // 作品图片链接 info = append(info, worksVotesInfo.WorkSize) // 作品尺寸(cm) info = append(info, worksVotesInfo.Name) // 参赛人员姓名 info = append(info, worksVotesInfo.Phone) // 手机号 info = append(info, fmt.Sprintf("%d", worksVotesInfo.VoteCount)) // 总票数 info = append(info, worksVotesInfo.RegistrationDeadline) // 投票截止时间 data = append(data, &info) } //获取当前时间的标准时间 currentTimeFormat := time.Now().Format("20060102") filePath := "./runtime/儿童美术作品投票记录" + currentTimeFormat + ".xlsx" _, err = utils.ToExcelByType(columns, data, "slice", filePath) if err != nil { fmt.Println("err", err) service.ResponseQuickMsg(c, e.Failed, errors.New("转换为excel类型出错").Error(), nil) return } var httpType string if config.IsHttps { httpType = model.HttpsType } else { httpType = model.HttpType } var exportUrl = fmt.Sprintf("%s%s/static/儿童美术作品投票记录"+currentTimeFormat+".xlsx", httpType, c.Request.Host) repEF := &equity.RechargeRecordManageExportFormRespond{ ExportUrl: exportUrl, } service.Success(c, repEF) return } func wgComposeImage(templatePath, outputPath, contentURL, workName, authorName, fontPath, relWorkName string, results chan<- result, wg *sync.WaitGroup) { defer wg.Done() jpegBytes, err := composeImage(templatePath, outputPath, contentURL, workName, authorName, fontPath) if err != nil { results <- result{err: err} return } results <- result{jpegBytes: jpegBytes, workName: relWorkName} return } func wgCreateQrBase64(contentUrl, logoUrl, prefix, relWorkName string, results chan<- result, wg *sync.WaitGroup) { defer wg.Done() base64Str, err := utils.CreateQrBase64(contentUrl, logoUrl) if err != nil { results <- result{err: err} return } // 上次生成的格式为 "data:image/png;base64,/9j/2wCEAAgGBgcGBQgHBwcJCQgKDBQNDAs" 去掉前缀才是标准的base64编码 jpegBytes, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(base64Str, prefix)) if err != nil { results <- result{err: err} return } results <- result{jpegBytes: jpegBytes, workName: relWorkName} return } // 合成图像 func composeImage(templatePath, outputPath, contentURL, workName, authorName, fontPath string) (jpegBytes []byte, err error) { file, err := os.Open(templatePath) if err != nil { return } defer file.Close() img, _, err := image.Decode(file) if err != nil { return } bounds := img.Bounds() newImage := image.NewRGBA(bounds) draw.Draw(newImage, bounds, img, bounds.Min, draw.Src) // 生成并添加带颜色的二维码 qrColor := color.RGBA{30, 65, 118, 255} // #1E4176 var qrImage *image.RGBA qrImage, err = generateQRCodeWithColor(contentURL, qrColor) if err != nil { return } qrBounds := qrImage.Bounds() qrPosition := image.Point{X: (bounds.Dx() - qrBounds.Dx()) / 2, Y: 520} // 修改二维码的位置 draw.Draw(newImage, image.Rectangle{Min: qrPosition, Max: qrPosition.Add(qrBounds.Size())}, qrImage, qrBounds.Min, draw.Over) workNameWidth, err := textWidth(workName, fontPath, 50) if err != nil { return } authorNameWidth, err := textWidth(authorName, fontPath, 50) if err != nil { return } // 添加作品名称和作者名字 labelColor := color.RGBA{30, 65, 118, 255} // #1E4176 addLabel(newImage, (bounds.Dx()-workNameWidth)/2, 1250, workName, labelColor, fontPath, 50) addLabel(newImage, (bounds.Dx()-authorNameWidth)/2, 1320, authorName, labelColor, fontPath, 50) var buf bytes.Buffer // 这段代码及其耗时 //err = png.Encode(&buf, newImage) //if err != nil { // return //} err = jpeg.Encode(&buf, newImage, nil) if err != nil { return } if outputPath != "" { // 在指定文件路径新增图片 var outFile *os.File outFile, err = os.Create(outputPath) if err != nil { return } defer outFile.Close() err = jpeg.Encode(outFile, newImage, nil) if err != nil { return } } else { // 将PNG字节流 //// 将PNG字节流编码为Base64 //base64Str = base64.StdEncoding.EncodeToString(buf.Bytes()) jpegBytes = buf.Bytes() } return } // 修改颜色的二维码生成函数 func generateQRCodeWithColor(content string, qrColor color.RGBA) (*image.RGBA, error) { qr, err := qrcode.New(content, qrcode.Medium) if err != nil { return nil, err } originalImg := qr.Image(700) bounds := originalImg.Bounds() coloredImg := image.NewRGBA(bounds) for y := bounds.Min.Y; y < bounds.Max.Y; y++ { for x := bounds.Min.X; x < bounds.Max.X; x++ { if originalImg.At(x, y) == color.Black { coloredImg.Set(x, y, qrColor) // 设置为特定颜色 } else { coloredImg.Set(x, y, color.White) } } } return coloredImg, nil } // 添加文字的函数,使用外部字体 func addLabel(img *image.RGBA, x, y int, label string, clr color.Color, fontPath string, fontSize float64) { fontBytes, err := os.ReadFile(fontPath) if err != nil { return } f, err := opentype.Parse(fontBytes) if err != nil { return } face, err := opentype.NewFace(f, &opentype.FaceOptions{ Size: fontSize, DPI: 72, Hinting: font.HintingFull, }) if err != nil { return } point := fixed.P(x, y) d := &font.Drawer{ Dst: img, Src: image.NewUniform(clr), Face: face, Dot: point, } d.DrawString(label) } // 计算文本的宽度 func textWidth(label string, fontPath string, fontSize float64) (int, error) { fontBytes, err := os.ReadFile(fontPath) if err != nil { return 0, err } f, err := opentype.Parse(fontBytes) if err != nil { return 0, err } face, err := opentype.NewFace(f, &opentype.FaceOptions{ Size: fontSize, DPI: 72, Hinting: font.HintingFull, }) if err != nil { return 0, err } d := &font.Drawer{ Face: face, } return d.MeasureString(label).Ceil(), nil } type result struct { jpegBytes []byte workName string err error }