Files
sundynix-pets/pets-be/internal/service/idcard.go
T
Blizzard bb7db78763 feat(fe+be): 身份卡头像不再压扁 + 内容丰富
- 头像用九参数 drawImage 居中裁剪(cover),竖图/横图都不变形,加白边圈
- 后端 IDCard 增 zodiac(按生日算星座) + weight,卡面加「星座/体重」行
- 加淡淡的肉垫水印 + 旋转的「肉垫计划·官方认证」印章
- 画布加高容纳新增内容

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 11:50:04 +08:00

117 lines
3.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"log"
"strings"
"github.com/sundynix/pets-be/internal/model"
)
// IDCard 宠物身份卡的全部内容,前端拿去 canvas 上画
type IDCard struct {
Name string `json:"name"`
Species string `json:"species"` // 猫猫 / 狗狗
Gender string `json:"gender"`
Breed string `json:"breed"`
Birthday string `json:"birthday"` // "2023 年 12 月 15 日"
Zodiac string `json:"zodiac"` // 星座,按生日算
Weight string `json:"weight"` // 体重,如 "13.5kg"
Intro string `json:"intro"`
AvatarURL string `json:"avatar_url"`
IDNo string `json:"id_no"` // 玩具身份号,不入库
Validity string `json:"validity"` // "2023.12.15 - 永远"
QRURL string `json:"qr_url"` // 小程序码,未发布时为空
QRReady bool `json:"qr_ready"` // 码有没有生成出来
}
// petIDNo 造一个稳定的「身份号」。纯装饰,不入库、不做校验。
// 规律照竞品那种:一串数字,扫不出真实含义但看着像证件号。
// 用 宠物雪花 id 的数字 + 生日 拼,同一只宠物每次都一样
func petIDNo(pet *model.Pet) string {
digits := make([]byte, 0, 18)
for i := 0; i < len(pet.ID) && len(digits) < 10; i++ {
if pet.ID[i] >= '0' && pet.ID[i] <= '9' {
digits = append(digits, pet.ID[i])
}
}
for len(digits) < 10 {
digits = append(digits, '0')
}
bd := "00000000"
if pet.Birthday != nil && !pet.Birthday.IsZero() {
bd = pet.Birthday.Format("20060102")
}
raw := string(digits[:10]) + bd // 18 位
// 每 4 位空一下,像证件号
var b strings.Builder
for i, c := range raw {
if i > 0 && i%4 == 0 {
b.WriteByte(' ')
}
b.WriteRune(c)
}
return b.String()
}
// zodiacOf 按月日算西方星座。按各星座起始日(含)顺序判定,
// 落在 12/221/19 的默认摩羯座
func zodiacOf(month, day int) string {
starts := []struct {
m, d int
name string
}{
{1, 20, "水瓶座"}, {2, 19, "双鱼座"}, {3, 21, "白羊座"}, {4, 20, "金牛座"},
{5, 21, "双子座"}, {6, 22, "巨蟹座"}, {7, 23, "狮子座"}, {8, 23, "处女座"},
{9, 23, "天秤座"}, {10, 24, "天蝎座"}, {11, 22, "射手座"}, {12, 22, "摩羯座"},
}
name := "摩羯座"
for _, s := range starts {
if month > s.m || (month == s.m && day >= s.d) {
name = s.name
}
}
return name
}
// GetIDCard 组装身份卡。小程序码是 best-effort:拿不到(未发布/配额)也照出卡
func (s *Service) GetIDCard(userID, petID string) (*IDCard, error) {
pet, err := s.GetPet(userID, petID)
if err != nil {
return nil, err
}
intro := "地球来了个新居民~"
switch pet.Type {
case "狗狗":
intro = "汪星人来地球啦~"
case "猫猫":
intro = "喵星人来地球啦~"
}
bd, validity := "未知", "永久有效"
if pet.Birthday != nil && !pet.Birthday.IsZero() {
bd = pet.Birthday.Format("2006 年 1 月 2 日")
validity = pet.Birthday.Format("2006.01.02") + " - 永远"
}
zodiac := ""
if pet.Birthday != nil && !pet.Birthday.IsZero() {
zodiac = zodiacOf(int(pet.Birthday.Month()), pet.Birthday.Day())
}
card := &IDCard{
Name: pet.Name, Species: pet.Type, Gender: pet.Gender, Breed: pet.Breed,
Birthday: bd, Zodiac: zodiac, Weight: pet.Weight, Intro: intro,
AvatarURL: pet.AvatarURL, IDNo: petIDNo(pet), Validity: validity,
}
// 小程序码取不到不算错——未发布/配额用尽时卡照样出,只是没码
if url, e := s.PetQRCode(petID); e == nil {
card.QRURL = url
card.QRReady = true
} else {
log.Printf("[info] 宠物 %s 的小程序码暂不可用(多为小程序未发布):%v", petID, e)
}
return card, nil
}