first commit
This commit is contained in:
@@ -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 "摩羯座"
|
||||
}
|
||||
Reference in New Issue
Block a user