fonchain-fiee/pkg/service/children_competition/children_competition.go

563 lines
16 KiB
Go
Raw Normal View History

2025-02-19 06:24:15 +00:00
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
}