package adser import ( "91porn-server/app/proto" "encoding/json" "errors" "fmt" "net/http" "sort" "strconv" "strings" "sync" "91porn-server/app/appg" "91porn-server/app/service/activityclient" "91porn-server/common" "91porn-server/common/httputil" "91porn-server/common/log" "91porn-server/models/v/usermod" "91porn-server/models/v/walletmod" "github.com/robfig/cron/v3" ) /* 测试: 部门CODE:1991129769626804224 商户CODE:1991129808640122880 应用CODE:jh_appid01 客户CODE:1991129948159451136 */ type JtAdvertiseReq struct { MerchantCode string `json:"merchantCode"` // 商户 code,必填 AppCode string `json:"appCode"` // 应用 code,必填 AdStatus int `json:"adStatus"` // 广告状态 1-上架,0-下架,2-未开始,3-已过期,4-即将到期 默认查询全部 //AdvertiseLocationCode string `json:"advertiseLocationCode,omitempty"` // 广告位唯一标识,选填 //AdvertiseCode string `json:"advertiseCode,omitempty"` // 广告唯一标识,选填 //PageNo int `json:"pageNo,omitempty"` // 页码,全量查询不需要传,选填 //PageSize int `json:"pageSize,omitempty"` // 每页条数,全量查询不需要传,选填 } type JtAdvertiseRes struct { Code int `json:"code"` // 返回状态码 Msg string `json:"msg"` // 返回消息 Data string `json:"data"` // 广告信息 - 加密 } // AdSlot 广告位信息 type AdSlot struct { DeptCode string `json:"deptCode"` // 部门 code MerchantCode string `json:"merchantCode"` // 商户 code AppCode string `json:"appCode"` // 应用 code LocationType int `json:"locationType"` // 广告位类型:0 → 开屏广告 1 → 弹窗广告 2 → 图标广告 3 → 九宫格广告 4 → 信息流广告 5 → 瀑布流广告 6 → 详情广告 7 → 片头广告 AdvertiseLocationCode string `json:"advertiseLocationCode"` // 广告位标识 AdvertiseLocationName string `json:"advertiseLocationName"` // 广告位名称 DisplayMode int `json:"displayMode"` // 展示模式:0 → 多个轮播, 1 → 单体, 2 → 三分屏, 3 → 四分屏, 4 → 小图标, 5 → 大图标 AdvertiseHeight int `json:"advertiseHeight"` // 高 AdvertiseWidth int `json:"advertiseWidth"` // 宽 MaterialType int `json:"materialType"` // 素材类型(视频/图片):1 → 视频, 2 → 图片, 3 → 视频、图片 AdLimit int `json:"adLimit"` // 广告限制数量 AdDetailInfoList []AdDetailInfo `json:"adDetailInfoList"` // 广告详情信息列表 } // AdDetailInfo 广告详情信息 type AdDetailInfo struct { AdvertiseCode string `json:"advertiseCode"` // 广告业务code CustomerCode string `json:"customerCode"` // 客户code AdvertiseName string `json:"advertiseName"` // 广告名称 AdvertiseUrl string `json:"advertiseUrl"` // 广告链接 AdvertiseIcon string `json:"advertiseIcon"` // 广告图片地址 AdvertiseType int `json:"advertiseType"` // 广告类型:1 → 播放器, 2 → 药台, 3 → 炮台, 4 → 黄游, 5 → 直播, 6 → BC AdMode string `json:"adMode"` // 广告模式 AppCode string `json:"appCode"` // 应用 code LocationType int `json:"locationType"` // 广告位类型:0 → 开屏广告 1 → 弹窗广告 2 → 图标广告 3 → 九宫格广告 4 → 信息流广告 5 → 瀑布流广告 6 → 详情广告 7 → 片头广告 AdvertiseDesc string `json:"advertiseDesc"` // 广告描述 PageType string `json:"pageType"` // 内部路由页面类型:如 rechange_page → 会员充值页, gold_rechange_page → 金币充值页, invite_page → 邀请分享页 StartTimeStamp int64 `json:"startTimeStamp"` // 广告开始时间 时间戳 EndTimeStamp int64 `json:"endTimeStamp"` // 广告结束时间 时间戳 Sort int `json:"sort"` // 排序 AdExtData string `json:"adExtData"` // 扩展字段1 = CoverImgSize } type Extra struct { CoverImgSize string `json:"coverImgSize"` WatchTime int `json:"watchTime"` DownloadCount int64 `json:"downloadCount"` Type string `json:"type"` // 应用类型 app:APP、web:网页 Location string `json:"location"` // 位置 1:顶部 2:底部 DisplayType string `json:"displayType"` // 展示类型 heng:横版 shu:竖版 } func (ad AdDetailInfo) GetExtraData() (res Extra) { _ = json.Unmarshal([]byte(ad.AdExtData), &res) return } func (ad AdDetailInfo) GetRealLink(user *usermod.User, w *walletmod.Wallet) string { ad.AdvertiseUrl = activityclient.ReplaceActivityDomain(ad.AdvertiseUrl, user, w) return strings.ReplaceAll(ad.AdvertiseUrl, "inner://yinseinner/", "yinseinner://") } func (ad AdDetailInfo) GetCoverLsj() string { if ad.AdvertiseIcon == "" { return "" } return strings.TrimLeft(ad.AdvertiseIcon, "/") } func (ad AdDetailInfo) GetAdvertiseType() int { // 内部顺序:1 应用 2 游戏 3 炮台 4 棋牌 5 直播 // 外部顺序:1:应用 2:药台、3:炮台、4:游戏、5:直播、6:棋牌 if ad.AdvertiseType == 4 { return 2 } if ad.AdvertiseType == 6 { return 4 } return ad.AdvertiseType } // advertiseAppId 广告中心应用ID const advertiseAppId int32 = 2 type advertiseClickReq struct { Id int64 `json:"id"` // 广告id AppId int32 `json:"appId"` // 应用id } type advertiseClickResp struct { Code int64 `json:"code"` // 错误码 } // AdvertiseClickCenter 上报广告点击到广告中心 func AdvertiseClickCenter(advertiseId int64) error { req := advertiseClickReq{ Id: advertiseId, AppId: advertiseAppId, } resp := advertiseClickResp{} url := appg.Conf.URL.VersionUrl + "/api/stat/advitise/v2/click" bodyStr, _ := json.Marshal(req) code, err := httputil.DefaultClientPostJsonWithResp(&resp, url, nil, bodyStr) if err != nil { log.Error("AdvertiseClickCenter POSTWithJResp", log.Any("url", url), log.Any("req", req), log.E(err)) return err } if code != http.StatusOK { log.Error("AdvertiseClickCenter response status", log.Any("code", code), log.Any("req", req)) return fmt.Errorf("response status err") } return nil } // AsyncAdvertiseClick 异步上报广告点击,广告id非数字时忽略 func AsyncAdvertiseClick(id string) { advertiseId, err := strconv.ParseInt(strings.TrimSpace(id), 10, 64) if err != nil { log.Warn("AsyncAdvertiseClick invalid advertise id", log.Any("id", id)) return } common.Go(func() { _ = AdvertiseClickCenter(advertiseId) }) } var ( advertiseCacheOnce sync.Once advertiseCacheMu sync.RWMutex advertiseCache []AdSlot advertiseCacheOK bool ) // JtAdvertiseThreeServer 获取广告位列表(内存缓存,每两分钟从广告中心刷新一次) func JtAdvertiseThreeServer() ([]AdSlot, error) { advertiseCacheOnce.Do(startAdvertiseCache) advertiseCacheMu.RLock() defer advertiseCacheMu.RUnlock() if !advertiseCacheOK { return nil, errors.New("advertise cache not ready") } res := make([]AdSlot, len(advertiseCache)) copy(res, advertiseCache) return res, nil } // startAdvertiseCache 首次加载广告并启动定时刷新 func startAdvertiseCache() { updataAdvertiseInfo() c := cron.New(cron.WithSeconds()) _, _ = c.AddFunc("0 */2 * * * ?", updataAdvertiseInfo) // 两分钟更新一次 c.Start() } // updataAdvertiseInfo 从广告中心拉取广告并更新缓存,列表为空时重试一次 func updataAdvertiseInfo() { advertiseResp, err := advertiseCenterServer() if err != nil { log.Error("advertiseCenterServer", log.E(err)) return } log.Error("advertiseCenterServer", log.Any("advertiseResp", advertiseResp)) if len(advertiseResp.AdvertiseList) == 0 { advertiseResp, err = advertiseCenterServer() if err != nil { log.Error("advertiseCenterServer", log.E(err)) return } } list := advertiseResp.AdvertiseList sort.SliceStable(list, func(i, j int) bool { if list[i].LocId != list[j].LocId { return list[i].LocId < list[j].LocId } return list[i].Sort < list[j].Sort }) // 每条广告单独作为一个广告位返回,广告位名称取广告标题 slots := make([]AdSlot, 0, len(list)) for _, value := range list { slots = append(slots, AdSlot{ AdvertiseLocationCode: strconv.FormatInt(int64(value.LocId), 10), AdvertiseLocationName: value.Title, AdDetailInfoList: []AdDetailInfo{{ AdvertiseCode: strconv.FormatInt(value.Id, 10), AdvertiseName: value.Title, AdvertiseUrl: value.Link, AdvertiseIcon: value.CoverImg, AdvertiseDesc: value.Remark, Sort: int(value.Sort), }}, }) } advertiseCacheMu.Lock() advertiseCache = slots advertiseCacheOK = true advertiseCacheMu.Unlock() } // advertiseCenterServer 请求广告中心广告列表 func advertiseCenterServer() (resp AdvertiseRes, err error) { req := AdvertiseReq{ AppId: advertiseAppId, } url := appg.Conf.URL.VersionUrl + "/api/stat/advitise/v2/get" bodyStr, _ := json.Marshal(req) code, err := httputil.DefaultClientPostJsonWithResp(&resp, url, nil, bodyStr) if err != nil { log.Error("advertiseCenterServer POSTWithJResp ", log.Any("url", url), log.E(err)) return } log.Info("*************\n\n\n", log.Any("", resp)) if code != http.StatusOK { log.Error("advertiseCenterServer response status ", log.Any("code", code)) err = fmt.Errorf("response status err") return } return } func GetAdvertiseLocation(adverId string) (res proto.AdsInfo) { list, err := JtAdvertiseThreeServer() if err != nil { return } for _, adverLocation := range list { for _, ad := range adverLocation.AdDetailInfoList { if ad.AdvertiseCode == adverId { pos, _ := strconv.Atoi(adverLocation.AdvertiseLocationCode) extra := ad.GetExtraData() res = proto.AdsInfo{ ID: ad.AdvertiseCode, Title: ad.AdvertiseName, Description: ad.AdvertiseDesc, Cover: ad.GetCoverLsj(), Href: ad.GetRealLink(nil, nil), Position: pos, PositionName: adverLocation.AdvertiseLocationName, SortCode: ad.Sort, CoverImgSize: extra.CoverImgSize, WatchTime: extra.WatchTime, } return res } } } return }