Go-合成周年祝福图片
需求
(1)生日周年系统,需要根据员工的信息,合成周年祝福图片
(2)生成邮件内容进行发送
(3)不同周年使用图片,元素不同
实现效果:如2周年
任务拆解
(1)周年祝福图片上面有动态合成的元素有:
第x周年的底图、员工姓名、周年数字、入职日期、员工工号以及信息所用到的一些字体
(2)步骤:
A.读取底图进行解码
B.新建一张有边界的画布
C.将模板底图画到画布上
D.姓名等其他信息涉及字体和坐标,所以得提前加载字体
E.接着设置姓名坐标,字体,大小,内容并画到画布上
F.周年数字,入职日期,员工工号同上
H.生成一张新的图片
G.将图片设置成邮件的内容发送到相应的人
(3) 定时任务每天都检测周年的员工,定时操作(2)步骤
新图片生成,看代码实现
bir_notice.go
...//创建一个图片工具对象imageItem := new(library.ImageItem)//掉图片工具类方法,传入姓名,工号,周年,入职日期,生成周年图片并返回路径filePath, fileName, err := imageItem.MakeYearsImage(staffName, workId, empYears, date)...
package libraryimport ("bytes""encoding/base64""errors""fmt""github.com/gogf/gf/errors/gerror""github.com/gogf/gf/frame/g""github.com/gogf/gf/util/gconv""github.com/golang/freetype""github.com/golang/freetype/truetype""image""image/draw""image/jpeg""image/png""io""io/ioutil""net/http""os""time")var (fontWryh *truetype.Font // 名字字体fontSyhx *truetype.Font // 日期字体fontMsyhbd *truetype.Font // 工号字体)type ImageItem struct {FontSize int //字体大小NameX int //姓名X坐标NameY int //姓名Y坐标DateX int //日期Y坐标DateY int //日期Y坐标UserIdX int //工号X坐标UserIdY int //工号Y坐标ImgPath string //模板文件文件路径ImgPathNew string //生成新文件路径文件路径NewImg string //新图片Title string //标题}//MakeYearsImage 生成周年图片func (i *ImageItem) MakeYearsImage(staffName string, workId int, empYears int, date string) (filePath string, fileName string, err error) {filePath = ""filePath = g.Cfg().GetString("birImages.dir")imageItem, err := i.setImageItem(empYears, filePath)g.Dump("imageItem:", imageItem)if err != nil {return "", "", err}// 根据路径打开模板文件templateFile, err := os.Open(imageItem.ImgPath)if err != nil {return "", "", err}defer templateFile.Close()// 解码templateFileImage, err := jpeg.Decode(templateFile)if err != nil {return "", "", err}// 新建一张和模板文件一样大小的画布newTemplateImage := image.NewRGBA(templateFileImage.Bounds())// 将模板图片画到新建的画布上draw.Draw(newTemplateImage, templateFileImage.Bounds(), templateFileImage, templateFileImage.Bounds().Min, draw.Over)// 加载字体文件 这里我们加载两种字体文件fontWryh, err = i.loadFont(filePath + "wryh.ttf")if err != nil {return "", "", err}fontSyhx, err = i.loadFont(filePath + "syhx.ttf")if err != nil {return "", "", err}fontMsyhbd, err = i.loadFont(filePath + "msyhbd.ttf")if err != nil {return "", "", err}// 向图片中写入文字// 在写入之前有一些准备工作content := freetype.NewContext()content.SetClip(newTemplateImage.Bounds())content.SetDst(newTemplateImage)content.SetSrc(image.Black) // 设置字体颜色content.SetDPI(72) // 设置字体分辨率// 正式写入姓名// 参数1:要写入的文字// 参数2:文字坐标content.SetSrc(image.Black)content.SetFont(fontMsyhbd)content.SetFontSize(gconv.Float64(20)) // 设置字体大小_, err = content.DrawString("NO."+gconv.String(workId), freetype.Pt(imageItem.UserIdX, imageItem.UserIdY))if err != nil {return "", "", err}content.SetSrc(image.Black)content.SetFont(fontWryh) // 设置字体样式,就是我们上面加载的字体content.SetFontSize(gconv.Float64(imageItem.FontSize)) // 设置字体大小_, err = content.DrawString(staffName, freetype.Pt(imageItem.NameX, imageItem.NameY))if err != nil {fmt.Println("err:", err)return "", "", err}content.SetFont(fontSyhx)content.SetFontSize(gconv.Float64(imageItem.FontSize)) // 设置字体大小_, err = content.DrawString(date, freetype.Pt(imageItem.DateX, imageItem.DateY))if err != nil {return "", "", err}// 保存图片 ---> 在此我们统一将文件保存到:i.ImgPathNewerr = i.saveFile(newTemplateImage, imageItem.ImgPathNew)if err != nil {return "", "", err}return imageItem.ImgPathNew, imageItem.NewImg, nil}func (i *ImageItem) setImageItem(empYears int, filePath string) (imageItem ImageItem, err error) {TitleFormat := "相识在37,%v周年纪念日快乐!"switch empYears {case 1:imageItem.FontSize = 28imageItem.NameX = 172imageItem.NameY = 183imageItem.DateX = 315imageItem.DateY = 790imageItem.UserIdX = 59imageItem.UserIdY = 850imageItem.ImgPath = filePath + "oneyear2020.jpg"imageItem.ImgPathNew = filePath + "oneyear2020_new.jpg"imageItem.NewImg = "oneyear2020_new.jpg"imageItem.Title = fmt.Sprintf(TitleFormat, "一")case 2:imageItem.FontSize = 28imageItem.NameX = 158imageItem.NameY = 173imageItem.DateX = 310imageItem.DateY = 684imageItem.UserIdX = 59imageItem.UserIdY = 850imageItem.ImgPath = filePath + "twoyear2020.jpg"imageItem.ImgPathNew = filePath + "twoyear2020_new.jpg"imageItem.NewImg = "twoyear2020_new.jpg"imageItem.Title = fmt.Sprintf(TitleFormat, "二")case 3:imageItem.FontSize = 28imageItem.NameX = 165imageItem.NameY = 189imageItem.DateX = 318imageItem.DateY = 785imageItem.UserIdX = 59imageItem.UserIdY = 850imageItem.ImgPath = filePath + "threeyear2020.jpg"imageItem.ImgPathNew = filePath + "threeyear2020_new.jpg"imageItem.NewImg = "threeyear2020_new.jpg"imageItem.Title = fmt.Sprintf(TitleFormat, "三")case 4:imageItem.FontSize = 28imageItem.NameX = 160imageItem.NameY = 173imageItem.DateX = 315imageItem.DateY = 638imageItem.UserIdX = 59imageItem.UserIdY = 850imageItem.ImgPath = filePath + "fouryear2020.jpg"imageItem.ImgPathNew = filePath + "fouryear2020_new.jpg"imageItem.NewImg = "fouryear2020_new.jpg"imageItem.Title = fmt.Sprintf(TitleFormat, "四")case 5:imageItem.FontSize = 28imageItem.NameX = 165imageItem.NameY = 182imageItem.DateX = 315imageItem.DateY = 775imageItem.UserIdX = 62imageItem.UserIdY = 850imageItem.ImgPath = filePath + "fiveyear2020.jpg"imageItem.ImgPathNew = filePath + "fiveyear2020_new.jpg"imageItem.NewImg = "fiveyear2020_new.jpg"imageItem.Title = fmt.Sprintf(TitleFormat, "五")case 6:imageItem.FontSize = 28imageItem.NameX = 157imageItem.NameY = 174imageItem.DateX = 315imageItem.DateY = 682imageItem.UserIdX = 55imageItem.UserIdY = 850imageItem.ImgPath = filePath + "sixyear2020.jpg"imageItem.ImgPathNew = filePath + "sixyear2020_new.jpg"imageItem.NewImg = "sixyear2020_new.jpg"imageItem.Title = fmt.Sprintf(TitleFormat, "六")case 7:imageItem.FontSize = 28imageItem.NameX = 165imageItem.NameY = 160imageItem.DateX = 315imageItem.DateY = 812imageItem.UserIdX = 55imageItem.UserIdY = 850imageItem.ImgPath = filePath + "sevenyear2020.jpg"imageItem.ImgPathNew = filePath + "sevenyear2020_new.jpg"imageItem.NewImg = "sevenyear2020_new.jpg"imageItem.Title = fmt.Sprintf(TitleFormat, "七")case 8:imageItem.FontSize = 28imageItem.NameX = 157imageItem.NameY = 173imageItem.DateX = 315imageItem.DateY = 596imageItem.UserIdX = 55imageItem.UserIdY = 850imageItem.ImgPath = filePath + "eightyear2020.jpg"imageItem.ImgPathNew = filePath + "eightyear2020_new.jpg"imageItem.NewImg = "eightyear2020_new.jpg"imageItem.Title = fmt.Sprintf(TitleFormat, "八")case 9:imageItem.FontSize = 28imageItem.NameX = 157imageItem.NameY = 173imageItem.DateX = 315imageItem.DateY = 723imageItem.UserIdX = 57imageItem.UserIdY = 850imageItem.ImgPath = filePath + "nineyear2020.jpg"imageItem.ImgPathNew = filePath + "nineyear2020_new.jpg"imageItem.NewImg = "nineyear2020_new.jpg"imageItem.Title = fmt.Sprintf(TitleFormat, "九")case 10:imageItem.FontSize = 28imageItem.NameX = 168imageItem.NameY = 167imageItem.DateX = 315imageItem.DateY = 818imageItem.UserIdX = 62imageItem.UserIdY = 850imageItem.ImgPath = filePath + "tenyear2020.jpg"imageItem.ImgPathNew = filePath + "tenyear2020_new.jpg"imageItem.NewImg = "tenyear2020_new.jpg"imageItem.Title = fmt.Sprintf(TitleFormat, "十")case 11:imageItem.FontSize = 28imageItem.NameX = 157imageItem.NameY = 173imageItem.DateX = 315imageItem.DateY = 681imageItem.UserIdX = 57imageItem.UserIdY = 850imageItem.ImgPath = filePath + "elevenyear2020.jpg"imageItem.ImgPathNew = filePath + "elevenyear2020_new.jpg"imageItem.NewImg = "elevenyear2020_new.jpg"imageItem.Title = fmt.Sprintf(TitleFormat, "十一")case 12:imageItem.FontSize = 28imageItem.NameX = 157imageItem.NameY = 173imageItem.DateX = 315imageItem.DateY = 765imageItem.UserIdX = 57imageItem.UserIdY = 850imageItem.ImgPath = filePath + "twelveyear2020.jpg"imageItem.ImgPathNew = filePath + "twelveyear2020_new.jpg"imageItem.NewImg = "twelveyear2020_new.jpg"imageItem.Title = fmt.Sprintf(TitleFormat, "十二")case 13:imageItem.FontSize = 40imageItem.NameX = 223imageItem.NameY = 244imageItem.DateX = 450imageItem.DateY = 990imageItem.UserIdX = 100imageItem.UserIdY = 1200imageItem.ImgPath = filePath + "year13.jpg"imageItem.ImgPathNew = filePath + "year13_new.jpg"imageItem.NewImg = "year13_new.jpg"imageItem.Title = fmt.Sprintf(TitleFormat, "十三")case 14:imageItem.FontSize = 40imageItem.NameX = 223imageItem.NameY = 244imageItem.DateX = 450imageItem.DateY = 1080imageItem.UserIdX = 100imageItem.UserIdY = 1200imageItem.ImgPath = filePath + "year14.jpg"imageItem.ImgPathNew = filePath + "year14_new.jpg"imageItem.NewImg = "year14_new.jpg"imageItem.Title = fmt.Sprintf(TitleFormat, "十四")case 15:imageItem.FontSize = 35imageItem.NameX = 223imageItem.NameY = 244imageItem.DateX = 485imageItem.DateY = 1079imageItem.UserIdX = 100imageItem.UserIdY = 1200imageItem.ImgPath = filePath + "year15.jpg"imageItem.ImgPathNew = filePath + "year15_new.jpg"imageItem.NewImg = "year15_new.jpg"imageItem.Title = fmt.Sprintf(TitleFormat, "十五")}return imageItem, nil}// 根据路径加载字体文件// path 字体的路径func (i *ImageItem) loadFont(path string) (font *truetype.Font, err error) {var fontBytes []bytefontBytes, err = ioutil.ReadFile(path) // 读取字体文件if err != nil {err = fmt.Errorf("加载字体文件出错:%s", err.Error())return}font, err = freetype.ParseFont(fontBytes) // 解析字体文件if err != nil {err = fmt.Errorf("解析字体文件出错,%s", err.Error())return}return}func (i *ImageItem) saveFile(pic *image.RGBA, filePath string) error {dstFile, err := os.Create(filePath)if err != nil {return err}defer dstFile.Close()err = png.Encode(dstFile, pic)if err != nil {return err}return nil}//GetLocImgBase64 转换本地图片,path图片路径 ,isCompress:是否压缩func GetLocImgBase64(path string, isCompress bool) (baseImg string, err error) {//获取本地文件file, err := os.Open(path)if err != nil {err = errors.New("获取本地图片失败")return}defer file.Close()imgByte, _ := ioutil.ReadAll(file)//压缩图片if isCompress {imgByte = compressImageResource(imgByte)}// 判断文件类型,生成一个前缀,拼接base64后可以直接粘贴到浏览器打开,不需要可以不用下面代码//取图片类型mimeType := http.DetectContentType(imgByte)switch mimeType {case "image/jpeg":baseImg = "data:image/jpeg;base64," + base64.StdEncoding.EncodeToString(imgByte)case "image/png":baseImg = "data:image/png;base64," + base64.StdEncoding.EncodeToString(imgByte)}return}//GetUrlImgBase64 获取网络图片func GetUrlImgBase64(path string) (baseImg string, err error) {//获取网络图片client := &http.Client{Timeout: time.Second * 5, //超时时间}var bodyImg io.Readerrequest, err := http.NewRequest("GET", path, bodyImg)if err != nil {err = errors.New("获取网络图片失败")return}respImg, _ := client.Do(request)defer respImg.Body.Close()imgByte, _ := ioutil.ReadAll(respImg.Body)// 判断文件类型,生成一个前缀,拼接base64后可以直接粘贴到浏览器打开,不需要可以不用下面代码//取图片类型mimeType := http.DetectContentType(imgByte)switch mimeType {case "image/jpeg":baseImg = "data:image/jpeg;base64," + base64.StdEncoding.EncodeToString(imgByte)case "image/png":baseImg = "data:image/png;base64," + base64.StdEncoding.EncodeToString(imgByte)}return}func compressImageResource(data []byte) []byte {img, _, err := image.Decode(bytes.NewReader(data))if err != nil {return data}buf := bytes.Buffer{}err = jpeg.Encode(&buf, img, &jpeg.Options{Quality: 40})if err != nil {return data}if buf.Len() > len(data) {return data}return buf.Bytes()}// ClipImageResource 图片调整func ClipImageResource(data []byte, quality int) (out []byte, err error) {img, fm, err := image.Decode(bytes.NewReader(data))if err != nil {err = gerror.Wrap(err, "image decode失败")return}buf := bytes.Buffer{}switch fm {case "jpeg":if quality != 0 {//压缩err = jpeg.Encode(&buf, img, &jpeg.Options{Quality: quality})if err != nil {err = gerror.Wrap(err, "jpeg encode失败")return}if buf.Len() > len(data) {out = data} else {out = buf.Bytes()}}}if len(out) == 0 {out = data}return}中间需要做一些业务告警处理,比如生成周年图片失败,给开发发小七告警filePath, fileName, err := imageItem.MakeYearsImage(staffName, workId, empYears, date)if err != nil {fmt.Println("err:", err)//合成集团周年图片失败,给开发发告警信息warringNotice := new(birNotice)warringNotice.Type = 5warringNotice.TaskType = 1warringNotice.SendTitle = "合成集团周年图片失败告警"warringNotice.SendContent = "合成集团周年图片失败信息:filePath:" + filePath + ",fileName:" + fileName + ",err:" + gconv.String(err)warringNotice.ReceiveList = g.Config().GetString("birthday.workIds.dev")warringNotice.SendTime = gtime.Datetime()_ = warringNotice.sendMsg("")}
将图片设置成邮件内容发送
//需做部分名单拦截...//发送邮件err = library.SendMail(mailTo, "37点赞", c.SendTitle, c.SendContent, c.PicUrl, c.PicUrl)//当然,邮件发送失败也需要实时给开发告警if err != nil {fmt.Println("sendYearEmailMsg err:", err)//发送邮件失败,给开发发告警信息warringNotice := new(birNotice)warringNotice.Type = 5warringNotice.TaskType = 1warringNotice.SendTitle = "发送集团周年邮件消息失败告警"warringNotice.SendContent = "发送集团周年邮件数据:接收工号:(" + c.ReceiveList + ")原始数据:SendMail(" + gconv.String(mailTo) + ",37点赞," + c.SendTitle + "," + c.SendContent + "," + c.PicUrl + "," + c.PicUrl + ")\n" + "err:" + gconv.String(err)warringNotice.ReceiveList = g.Config().GetString("birthday.workIds.dev")warringNotice.SendTime = gtime.Datetime()_ = warringNotice.sendMsg("")}
邮件工具类
///SendMail 发送邮件func SendMail(mailTo []string, serverName, subject, body, filePath, fileName string) error {address := g.Cfg().GetString("birthday.email.account")password := g.Cfg().GetString("birthday.email.password")host := g.Cfg().GetString("birthday.email.host")port := g.Cfg().GetString("birthday.email.port")mail := gomail.NewMessage()mail.SetHeader("From", mail.FormatAddress(address, serverName))mail.SetHeader("To", mailTo...)mail.SetHeader("Subject", subject)mail.SetBody("text/html", body)if len(filePath) > 0 {mail.Embed(fileName) //设置图片为内容}dialer := gomail.NewDialer(host, gconv.Int(port), address, password)return dialer.DialAndSend(mail)}
后台预览发送记录
邮件发送的内容及图片,后台同步记录,点击查看可以实时预览
以上就是用go实现图片合成的小demo,供大家参考