first commit

This commit is contained in:
2026-09-11 01:41:52 +08:00
commit 9f22e6ccae
263 changed files with 28935 additions and 0 deletions
+172
View File
@@ -0,0 +1,172 @@
package service
import (
"context"
"encoding/json"
"time"
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/global"
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/dto"
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/mysqldao"
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/mysqlmodel"
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/redisdao"
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/pkg/errcode"
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/pkg/tokenhub"
)
// HoroscopeService 今日运势业务服务。
type HoroscopeService struct {
horoscopeDao *mysqldao.HoroscopeDao
userDao *mysqldao.UserDao
}
// NewHoroscopeService 创建运势服务。
func NewHoroscopeService() *HoroscopeService {
return &HoroscopeService{
horoscopeDao: mysqldao.NewHoroscopeDao(),
userDao: mysqldao.NewUserDao(),
}
}
// GetToday 获取当前用户今日运势:由生日推算星座,Redis 优先、未命中回落 MySQL 并回填缓存。
func (s *HoroscopeService) GetToday(ctx context.Context, userID int64) (*dto.HoroscopeResponse, *errcode.Error) {
ctx, span := global.Tracer.Start(ctx, "svc.HoroscopeGetToday")
defer span.End()
user, err := s.userDao.UserGetByID(ctx, userID)
if err != nil {
if isNotFound(err) {
return nil, errcode.UserNotFound
}
return nil, errcode.DatabaseError
}
if user.BirthDay == 0 {
return nil, errcode.InvalidParams.WithDetails("请先完善生日信息")
}
zodiac := zodiacByUnix(user.BirthDay)
statDate := todayStatDate()
// 1. Redis 优先。
if data, cerr := redisdao.HoroscopeGet(ctx, zodiac, statDate); cerr == nil && len(data) > 0 {
var response dto.HoroscopeResponse
if json.Unmarshal(data, &response) == nil {
return &response, nil
}
}
// 2. 回落 MySQL。
h, derr := s.horoscopeDao.HoroscopeGetByZodiacAndDate(ctx, zodiac, statDate)
if derr != nil {
if isNotFound(derr) {
// 今日运势尚未生成,异步触发兜底生成,本次返回未生成。
tokenhub.EnsureTodayFortunes()
return nil, errcode.HoroscopeNotFound
}
return nil, errcode.DatabaseError
}
response := buildHoroscopeResponse(h)
// 回填缓存,TTL 至次日凌晨。
if data, merr := json.Marshal(response); merr == nil {
_ = redisdao.HoroscopeSet(ctx, zodiac, statDate, data, ttlUntilNextMidnight())
}
return &response, nil
}
// buildHoroscopeResponse 将运势模型映射为响应。
func buildHoroscopeResponse(h *mysqlmodel.Horoscope) dto.HoroscopeResponse {
return dto.HoroscopeResponse{
StatDate: h.StatDate,
Zodiac: h.Zodiac,
OverallScore: h.OverallScore,
LoveScore: h.LoveScore,
CareerScore: h.CareerScore,
WealthScore: h.WealthScore,
DailyText: h.DailyText,
LuckyColor: h.LuckyColor,
LuckyNumber: h.LuckyNumber,
}
}
// ttlUntilNextMidnight 计算到次日 0 点的时长,用于运势缓存过期。
func ttlUntilNextMidnight() time.Duration {
now := time.Now()
next := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location())
return next.Sub(now)
}
// zodiacByUnix 根据生日(秒级 unix 时间戳)计算星座(中文名,与运势数据对齐)。
func zodiacByUnix(birthUnix int64) string {
t := time.Unix(birthUnix, 0)
return zodiacByMonthDay(int(t.Month()), t.Day())
}
// zodiacByMonthDay 根据月日返回星座中文名。
func zodiacByMonthDay(month, day int) string {
switch month {
case 1:
if day <= 19 {
return "摩羯座"
}
return "水瓶座"
case 2:
if day <= 18 {
return "水瓶座"
}
return "双鱼座"
case 3:
if day <= 20 {
return "双鱼座"
}
return "白羊座"
case 4:
if day <= 19 {
return "白羊座"
}
return "金牛座"
case 5:
if day <= 20 {
return "金牛座"
}
return "双子座"
case 6:
if day <= 21 {
return "双子座"
}
return "巨蟹座"
case 7:
if day <= 22 {
return "巨蟹座"
}
return "狮子座"
case 8:
if day <= 22 {
return "狮子座"
}
return "处女座"
case 9:
if day <= 22 {
return "处女座"
}
return "天秤座"
case 10:
if day <= 23 {
return "天秤座"
}
return "天蝎座"
case 11:
if day <= 22 {
return "天蝎座"
}
return "射手座"
case 12:
if day <= 21 {
return "射手座"
}
return "摩羯座"
}
return "摩羯座"
}