500 lines
12 KiB
Go
500 lines
12 KiB
Go
|
package splash
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"math"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"time"
|
||
|
|
||
|
"go-common/app/interface/main/app-resource/conf"
|
||
|
addao "go-common/app/interface/main/app-resource/dao/ad"
|
||
|
locdao "go-common/app/interface/main/app-resource/dao/location"
|
||
|
spdao "go-common/app/interface/main/app-resource/dao/splash"
|
||
|
"go-common/app/interface/main/app-resource/model"
|
||
|
"go-common/app/interface/main/app-resource/model/splash"
|
||
|
locmdl "go-common/app/service/main/location/model"
|
||
|
"go-common/library/ecode"
|
||
|
"go-common/library/log"
|
||
|
"go-common/library/net/metadata"
|
||
|
"go-common/library/sync/errgroup"
|
||
|
|
||
|
farm "github.com/dgryski/go-farm"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
_birthType = 2
|
||
|
_vipType = 4
|
||
|
_defaultType = 0
|
||
|
_initVersion = "splash_version"
|
||
|
_initSplashKey = "splash_key_%d_%d_%d"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
_emptySplashs = []*splash.Splash{}
|
||
|
)
|
||
|
|
||
|
// Service is splash service.
|
||
|
type Service struct {
|
||
|
dao *spdao.Dao
|
||
|
ad *addao.Dao
|
||
|
loc *locdao.Dao
|
||
|
// tick
|
||
|
tick time.Duration
|
||
|
// splash duration
|
||
|
splashTick string
|
||
|
// screen
|
||
|
andScreen map[int8]map[float64][2]int
|
||
|
iosScreen map[int8]map[float64][2]int
|
||
|
// cache
|
||
|
cache map[string][]*splash.Splash
|
||
|
defaultCache map[string][]*splash.Splash
|
||
|
birthCache map[string][]*splash.Splash
|
||
|
vipCache map[string][]*splash.Splash
|
||
|
// splash random
|
||
|
splashRandomIds map[int8]map[int64]struct{}
|
||
|
}
|
||
|
|
||
|
// New new a splash service.
|
||
|
func New(c *conf.Config) *Service {
|
||
|
s := &Service{
|
||
|
dao: spdao.New(c),
|
||
|
ad: addao.New(c),
|
||
|
loc: locdao.New(c),
|
||
|
// tick
|
||
|
tick: time.Duration(c.Tick),
|
||
|
// splash duration
|
||
|
splashTick: c.Duration.Splash,
|
||
|
// screen
|
||
|
andScreen: map[int8]map[float64][2]int{},
|
||
|
iosScreen: map[int8]map[float64][2]int{},
|
||
|
// splash cache
|
||
|
cache: map[string][]*splash.Splash{},
|
||
|
defaultCache: map[string][]*splash.Splash{},
|
||
|
birthCache: map[string][]*splash.Splash{},
|
||
|
vipCache: map[string][]*splash.Splash{},
|
||
|
// splash random
|
||
|
splashRandomIds: map[int8]map[int64]struct{}{},
|
||
|
}
|
||
|
s.load()
|
||
|
s.loadBirth()
|
||
|
s.loadSplashRandomIds(c)
|
||
|
go s.loadproc()
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
// Display dispaly data.
|
||
|
func (s *Service) Display(c context.Context, plat int8, w, h, build int, channel, ver string, now time.Time) (res []*splash.Splash, version string, err error) {
|
||
|
// get from cache
|
||
|
res, version, err = s.getCache(c, plat, w, h, build, channel, ver, now)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (s *Service) Birthday(c context.Context, plat int8, w, h int, birth string) (res *splash.Splash, err error) {
|
||
|
// get from cache
|
||
|
res, err = s.getBirthCache(c, plat, w, h, birth)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// AdList ad splash list
|
||
|
func (s *Service) AdList(c context.Context, plat int8, mobiApp, device, buvid, birth, adExtra string, height, width, build int, mid int64) (res *splash.CmSplash, err error) {
|
||
|
var (
|
||
|
list []*splash.List
|
||
|
show []*splash.Show
|
||
|
config *splash.CmConfig
|
||
|
)
|
||
|
if ok := model.IsOverseas(plat); ok {
|
||
|
err = ecode.NotModified
|
||
|
return
|
||
|
}
|
||
|
g, ctx := errgroup.WithContext(c)
|
||
|
g.Go(func() error {
|
||
|
var e error
|
||
|
if list, config, e = s.ad.SplashList(ctx, mobiApp, device, buvid, birth, adExtra, height, width, build, mid); e != nil {
|
||
|
log.Error("cm s.ad.SplashList error(%v)", e)
|
||
|
return e
|
||
|
}
|
||
|
return nil
|
||
|
})
|
||
|
g.Go(func() error {
|
||
|
var e error
|
||
|
if show, e = s.ad.SplashShow(ctx, mobiApp, device, buvid, birth, adExtra, height, width, build, mid); e != nil {
|
||
|
log.Error("cm s.ad.SplashShow error(%v)", e)
|
||
|
return e
|
||
|
}
|
||
|
return nil
|
||
|
})
|
||
|
if err = g.Wait(); err != nil {
|
||
|
log.Error("cm splash errgroup.WithContext error(%v)", err)
|
||
|
return
|
||
|
}
|
||
|
res = &splash.CmSplash{
|
||
|
CmConfig: config,
|
||
|
List: list,
|
||
|
Show: show,
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// getCache cache display data.
|
||
|
func (s *Service) getCache(c context.Context, plat int8, w, h, build int, channel, ver string, now time.Time) (res []*splash.Splash, version string, err error) {
|
||
|
var (
|
||
|
ip = metadata.String(c, metadata.RemoteIP)
|
||
|
screen map[int8]map[float64][2]int
|
||
|
)
|
||
|
if model.IsIOS(plat) {
|
||
|
screen = s.iosScreen
|
||
|
} else if model.IsAndroid(plat) {
|
||
|
screen = s.andScreen
|
||
|
}
|
||
|
// TODO fate go start
|
||
|
var (
|
||
|
fgId int64
|
||
|
oldIdStr string
|
||
|
fgids = s.splashRandomIds[plat]
|
||
|
pids []string
|
||
|
auths map[string]*locmdl.Auth
|
||
|
)
|
||
|
vers := strings.Split(ver, strconv.Itoa(now.Year()))
|
||
|
if len(vers) > 1 {
|
||
|
ver = vers[0]
|
||
|
oldIdStr = vers[1]
|
||
|
}
|
||
|
for id, _ := range fgids {
|
||
|
fgId = id
|
||
|
idStr := strconv.FormatInt(fgId, 10)
|
||
|
if oldIdStr != idStr {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
for tSplash, tScreen := range screen {
|
||
|
var ss []*splash.Splash
|
||
|
if ss = s.cache[fmt.Sprintf(_initSplashKey, plat, w, h)]; len(ss) == 0 {
|
||
|
wh := s.similarScreen(plat, w, h, tScreen)
|
||
|
width := wh[0]
|
||
|
height := wh[1]
|
||
|
ss = s.cache[fmt.Sprintf(_initSplashKey, plat, width, height)]
|
||
|
}
|
||
|
for _, splash := range ss {
|
||
|
if splash.Type != tSplash {
|
||
|
continue
|
||
|
}
|
||
|
if splash.Area != "" {
|
||
|
pids = append(pids, splash.Area)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if len(pids) > 0 {
|
||
|
auths, _ = s.loc.AuthPIDs(c, strings.Join(pids, ","), ip)
|
||
|
}
|
||
|
// TODO fate go end
|
||
|
for tSplash, tScreen := range screen {
|
||
|
var (
|
||
|
ss []*splash.Splash
|
||
|
// advance time
|
||
|
advance, _ = time.ParseDuration(s.splashTick)
|
||
|
)
|
||
|
if ss = s.cache[fmt.Sprintf(_initSplashKey, plat, w, h)]; len(ss) == 0 {
|
||
|
wh := s.similarScreen(plat, w, h, tScreen)
|
||
|
width := wh[0]
|
||
|
height := wh[1]
|
||
|
ss = s.cache[fmt.Sprintf(_initSplashKey, plat, width, height)]
|
||
|
}
|
||
|
for _, splash := range ss {
|
||
|
if splash.Type != tSplash {
|
||
|
continue
|
||
|
}
|
||
|
// gt splash start time
|
||
|
if splash.NoPreview == 1 {
|
||
|
if h1 := now.Add(advance); int64(splash.Start) > h1.Unix() {
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
// TODO fate go start
|
||
|
if fgids != nil && splash.ID != fgId {
|
||
|
if _, ok := fgids[splash.ID]; ok {
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
// TODO fate go end
|
||
|
if model.InvalidBuild(build, splash.Build, splash.Condition) {
|
||
|
continue
|
||
|
}
|
||
|
if splash.Area != "" {
|
||
|
if auth, ok := auths[splash.Area]; ok && auth.Play == locmdl.Forbidden {
|
||
|
log.Warn("s.invalid area(%v) ip(%v) error(%v)", splash.Area, ip, err)
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
res = append(res, splash)
|
||
|
}
|
||
|
}
|
||
|
if vSplash := s.getVipCache(plat, w, h, screen, now); vSplash != nil {
|
||
|
res = append(res, vSplash)
|
||
|
}
|
||
|
if dSplash := s.getDefaultCache(plat, w, h, screen, now); dSplash != nil {
|
||
|
res = append(res, dSplash)
|
||
|
}
|
||
|
if len(res) == 0 {
|
||
|
res = _emptySplashs
|
||
|
}
|
||
|
if version = s.hash(res); version == ver {
|
||
|
err = ecode.NotModified
|
||
|
res = nil
|
||
|
}
|
||
|
version = version + strconv.Itoa(now.Year()) + strconv.FormatInt(fgId, 10)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// getBirthCache get birthday splash.
|
||
|
func (s *Service) getBirthCache(c context.Context, plat int8, w, h int, birth string) (res *splash.Splash, err error) {
|
||
|
var (
|
||
|
screen map[int8]map[float64][2]int
|
||
|
wh [2]int
|
||
|
)
|
||
|
if model.IsIOS(plat) {
|
||
|
screen = s.iosScreen
|
||
|
} else if model.IsAndroid(plat) {
|
||
|
screen = s.andScreen
|
||
|
}
|
||
|
if v, ok := screen[_birthType]; !ok {
|
||
|
return
|
||
|
} else {
|
||
|
wh = s.similarScreen(plat, w, h, v)
|
||
|
w = wh[0]
|
||
|
h = wh[1]
|
||
|
}
|
||
|
sps := s.birthCache[fmt.Sprintf(_initSplashKey, plat, w, h)]
|
||
|
for _, sp := range sps {
|
||
|
if sp.BirthStartMonth == "12" && sp.BirthEndMonth == "01" {
|
||
|
if (sp.BirthStart <= birth && "1231" >= birth) || ("0101" <= birth && sp.BirthEnd >= birth) {
|
||
|
res = sp
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
if sp.BirthStart <= birth && sp.BirthEnd >= birth {
|
||
|
res = sp
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
err = ecode.NothingFound
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// getVipCache
|
||
|
func (s *Service) getVipCache(plat int8, w, h int, screen map[int8]map[float64][2]int, now time.Time) (res *splash.Splash) {
|
||
|
var (
|
||
|
ss []*splash.Splash
|
||
|
)
|
||
|
if v, ok := screen[_vipType]; !ok {
|
||
|
return
|
||
|
} else if ss = s.vipCache[fmt.Sprintf(_initSplashKey, plat, w, h)]; len(ss) == 0 {
|
||
|
wh := s.similarScreen(plat, w, h, v)
|
||
|
width := wh[0]
|
||
|
height := wh[1]
|
||
|
ss = s.vipCache[fmt.Sprintf(_initSplashKey, plat, width, height)]
|
||
|
}
|
||
|
if len(ss) == 0 {
|
||
|
return
|
||
|
}
|
||
|
res = ss[(now.Day() % len(ss))]
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// getDefaultCache
|
||
|
func (s *Service) getDefaultCache(plat int8, w, h int, screen map[int8]map[float64][2]int, now time.Time) (res *splash.Splash) {
|
||
|
var (
|
||
|
ss []*splash.Splash
|
||
|
)
|
||
|
if v, ok := screen[_defaultType]; !ok {
|
||
|
return
|
||
|
} else if ss = s.defaultCache[fmt.Sprintf(_initSplashKey, plat, w, h)]; len(ss) == 0 {
|
||
|
wh := s.similarScreen(plat, w, h, v)
|
||
|
width := wh[0]
|
||
|
height := wh[1]
|
||
|
ss = s.defaultCache[fmt.Sprintf(_initSplashKey, plat, width, height)]
|
||
|
}
|
||
|
if len(ss) == 0 {
|
||
|
return
|
||
|
}
|
||
|
res = ss[(now.Day() % len(ss))]
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (s *Service) hash(v []*splash.Splash) string {
|
||
|
bs, err := json.Marshal(v)
|
||
|
if err != nil {
|
||
|
log.Error("json.Marshal error(%v)", err)
|
||
|
return _initVersion
|
||
|
}
|
||
|
return strconv.FormatUint(farm.Hash64(bs), 10)
|
||
|
}
|
||
|
|
||
|
// cacheproc load splash into cache.
|
||
|
func (s *Service) load() {
|
||
|
res, err := s.dao.ActiveAll(context.TODO())
|
||
|
if err != nil {
|
||
|
log.Error("s.dao.GetActiveAll() error(%v)", err)
|
||
|
return
|
||
|
}
|
||
|
var (
|
||
|
tmp []*splash.Splash
|
||
|
tmpdefault []*splash.Splash
|
||
|
)
|
||
|
for _, r := range res {
|
||
|
if r.Type == _defaultType {
|
||
|
tmpdefault = append(tmpdefault, r)
|
||
|
} else {
|
||
|
tmp = append(tmp, r)
|
||
|
}
|
||
|
}
|
||
|
s.cache = s.dealCache(tmp)
|
||
|
log.Info("splash cacheproc success")
|
||
|
s.defaultCache = s.dealCache(tmpdefault)
|
||
|
log.Info("splash default cacheproc tmpdefault")
|
||
|
resVip, err := s.dao.ActiveVip(context.TODO())
|
||
|
if err != nil {
|
||
|
log.Error("s.dao.ActiveVip() error(%v)", err)
|
||
|
return
|
||
|
}
|
||
|
s.vipCache = s.dealCache(resVip)
|
||
|
log.Info("splash Vip cacheproc success")
|
||
|
}
|
||
|
|
||
|
// loadBirth load birthday splash.
|
||
|
func (s *Service) loadBirth() {
|
||
|
res, err := s.dao.ActiveBirth(context.TODO())
|
||
|
if err != nil {
|
||
|
log.Error("s.dao.ActiveBirthday() error(%v)", err)
|
||
|
return
|
||
|
}
|
||
|
s.birthCache = s.dealCache(res)
|
||
|
log.Info("splash Birthday cacheproc success")
|
||
|
}
|
||
|
|
||
|
// dealCache
|
||
|
func (s *Service) dealCache(sps []*splash.Splash) (res map[string][]*splash.Splash) {
|
||
|
res = map[string][]*splash.Splash{}
|
||
|
tmpand := map[int8]map[float64][2]int{}
|
||
|
tmpios := map[int8]map[float64][2]int{}
|
||
|
for plat, v := range s.andScreen {
|
||
|
for r, value := range v {
|
||
|
if _, ok := tmpand[plat]; ok {
|
||
|
tmpand[plat][r] = value
|
||
|
} else {
|
||
|
tmpand[plat] = map[float64][2]int{
|
||
|
r: value,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
for plat, v := range s.iosScreen {
|
||
|
for r, value := range v {
|
||
|
if _, ok := tmpios[plat]; ok {
|
||
|
tmpios[plat][r] = value
|
||
|
} else {
|
||
|
tmpios[plat] = map[float64][2]int{
|
||
|
r: value,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
for _, v := range sps {
|
||
|
v.URI = model.FillURI(v.Goto, v.Param, nil)
|
||
|
key := fmt.Sprintf(_initSplashKey, v.Plat, v.Width, v.Height)
|
||
|
res[key] = append(res[key], v)
|
||
|
// generate screen
|
||
|
if model.IsAndroid(v.Plat) {
|
||
|
if _, ok := tmpand[v.Type]; ok {
|
||
|
tmpand[v.Type][splash.Ratio(v.Width, v.Height)] = [2]int{v.Width, v.Height}
|
||
|
} else {
|
||
|
tmpand[v.Type] = map[float64][2]int{
|
||
|
splash.Ratio(v.Width, v.Height): [2]int{v.Width, v.Height},
|
||
|
}
|
||
|
}
|
||
|
} else if model.IsIOS(v.Plat) {
|
||
|
if _, ok := tmpios[v.Type]; ok {
|
||
|
tmpios[v.Type][splash.Ratio(v.Width, v.Height)] = [2]int{v.Width, v.Height}
|
||
|
} else {
|
||
|
tmpios[v.Type] = map[float64][2]int{
|
||
|
splash.Ratio(v.Width, v.Height): [2]int{v.Width, v.Height},
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
s.andScreen = tmpand
|
||
|
s.iosScreen = tmpios
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (s *Service) loadSplashRandomIds(c *conf.Config) {
|
||
|
splashIds := map[int8]map[int64]struct{}{}
|
||
|
for k, v := range c.Splash.Random {
|
||
|
key := model.Plat(k, "")
|
||
|
splashIds[key] = map[int64]struct{}{}
|
||
|
for _, idStr := range v {
|
||
|
idInt, _ := strconv.ParseInt(idStr, 10, 64)
|
||
|
splashIds[key][idInt] = struct{}{}
|
||
|
}
|
||
|
}
|
||
|
s.splashRandomIds = splashIds
|
||
|
log.Info("splash Random cache success")
|
||
|
}
|
||
|
|
||
|
// loadproc load process.
|
||
|
func (s *Service) loadproc() {
|
||
|
for {
|
||
|
time.Sleep(s.tick)
|
||
|
s.load()
|
||
|
s.loadBirth()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// similarScreen android screnn size
|
||
|
func (s *Service) similarScreen(plat int8, w, h int, screen map[float64][2]int) (wh [2]int) {
|
||
|
if model.IsIOS(plat) {
|
||
|
switch {
|
||
|
case w == 750:
|
||
|
h = 1334
|
||
|
case w == 640 && h > 960:
|
||
|
h = 1136
|
||
|
case w == 640:
|
||
|
h = 960
|
||
|
case w == 2732:
|
||
|
h = 2048
|
||
|
case w == 2048:
|
||
|
h = 1536
|
||
|
case w == 1024:
|
||
|
h = 768
|
||
|
case w == 1242:
|
||
|
h = 2208
|
||
|
case w == 1496 || w == 1536:
|
||
|
w = 2048
|
||
|
h = 1536
|
||
|
case w == 748 || w == 768:
|
||
|
w = 1024
|
||
|
h = 768
|
||
|
}
|
||
|
}
|
||
|
min := float64(1<<64 - 1)
|
||
|
for r, s := range screen {
|
||
|
if s[0] == w && s[1] == h {
|
||
|
wh = s
|
||
|
return
|
||
|
}
|
||
|
abs := math.Abs(splash.Ratio(w, h) - r)
|
||
|
if abs < min {
|
||
|
min = abs
|
||
|
wh = s
|
||
|
}
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Close dao
|
||
|
func (s *Service) Close() {
|
||
|
s.dao.Close()
|
||
|
}
|