Files
rootandClaude Opus 5 8679200f41 Initial commit
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 13:57:10 +08:00

263 lines
9.8 KiB
Go

package adser
import (
"91porn-server/app/proto"
"encoding/json"
"errors"
"fmt"
"net/http"
"sort"
"strconv"
"strings"
"time"
"91porn-server/app/appg"
"91porn-server/app/service/activityclient"
"91porn-server/common"
"91porn-server/common/crypt"
"91porn-server/common/httputil"
"91porn-server/common/log"
"91porn-server/models/v/usermod"
"91porn-server/models/v/walletmod"
)
/*
测试:
部门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
}
// JtAdvertiseThreeServer 集团广告中心
func JtAdvertiseThreeServer() ([]AdSlot, error) {
var foreverCacheKey = fmt.Sprintf("jtAdForever-%s-%s", appg.Conf.AdCenter.MerchantCode, appg.Conf.AdCenter.AppCode)
redisKey := fmt.Sprintf("jtAd-%s-%s", appg.Conf.AdCenter.MerchantCode, appg.Conf.AdCenter.AppCode)
str, err := appg.Redis.Get(redisKey)
if err != nil {
log.Error(fmt.Sprintf("JtAdvertiseThreeServer 缓存获取广告列表信息异常:%v", err))
}
var resp []AdSlot
if str != nil {
if err = json.Unmarshal([]byte(*str), &resp); err == nil {
return resp, nil
}
log.Error(fmt.Sprintf("JtAdvertiseThreeServer 解析缓存数据异常:%v", err))
}
req := JtAdvertiseReq{
MerchantCode: appg.Conf.AdCenter.MerchantCode,
AppCode: appg.Conf.AdCenter.AppCode,
AdStatus: 1,
}
var serverResp *JtAdvertiseRes
url := appg.Conf.AdCenter.ApiDomain + "/openapi/getAdvertiseList"
bodyStr, _ := json.Marshal(req)
code, err := httputil.DefaultClientPostJsonWithResp(&serverResp, url, nil, bodyStr)
if err != nil {
log.Error("JtAdvertiseThreeServer POSTWithJResp ", log.Any("url", url), log.E(err))
// 从最后请求成功的一次拿数据
return getFromLastTime(foreverCacheKey, err)
}
if code != http.StatusOK {
log.Error("JtAdvertiseThreeServer response status ", log.Any("code", code))
// 从最后请求成功的一次拿数据
return getFromLastTime(foreverCacheKey, errors.New("response status err"))
}
if serverResp.Code != 0 {
log.Error("JtAdvertiseThreeServer response code ", log.Any("code", serverResp.Code), log.Any("msg", serverResp.Msg))
// 从最后请求成功的一次拿数据
return getFromLastTime(foreverCacheKey, errors.New("response code err"))
}
adsData, err := crypt.AdDecrypt(serverResp.Data, appg.Conf.AdCenter.AesKey)
if err != nil {
log.Error(fmt.Sprintf("JtAdvertiseThreeServer 解密广告数据异常:%v", err))
return nil, errors.New("response data err")
}
err = json.Unmarshal([]byte(adsData), &resp)
if err != nil {
log.Error(fmt.Sprintf("JtAdvertiseThreeServer 解析 JSON 失败:%v", err))
return nil, errors.New("data format error")
}
// 对广告进行升序排序
for i := range resp {
sort.Slice(resp[i].AdDetailInfoList, func(j, k int) bool {
return resp[i].AdDetailInfoList[j].Sort < resp[i].AdDetailInfoList[k].Sort
})
}
// 将最后一次请求成功的数据更新到二级缓存
common.Go(func() {
setAdsCache(foreverCacheKey, resp)
})
common.Go(func() {
data, err := json.Marshal(&resp)
if err != nil {
log.Error(fmt.Sprintf("JtAdvertiseThreeServer json序列化缓存数据异常:%v", err))
return
}
if err = appg.Redis.Set(redisKey, data, time.Minute); err != nil {
log.Error(fmt.Sprintf("JtAdvertiseThreeServer 保存缓存数据异常:%v", err))
}
})
return resp, nil
}
// getFromLastTime 返回最后一次请求的广告数据
func getFromLastTime(foreverCacheKey string, e error) ([]AdSlot, error) {
str, err := appg.Redis.Get(foreverCacheKey)
if err != nil {
log.Error(fmt.Sprintf("getFromLastTime 缓存获取广告列表信息异常:%v", err))
return nil, e
}
var resp []AdSlot
if str != nil {
if err = json.Unmarshal([]byte(*str), &resp); err == nil {
return resp, nil
}
log.Error(fmt.Sprintf("getFromLastTime 解析缓存数据异常:%v", err))
}
return nil, e
}
// setAdsCache 更新最后一次请求的广告数据
func setAdsCache(foreverCacheKey string, resp []AdSlot) {
data, err := json.Marshal(&resp)
if err != nil {
log.Error(fmt.Sprintf("setAdsCache json序列化缓存数据异常:%v", err))
return
}
if err := appg.Redis.Set(foreverCacheKey, data, time.Hour*72); err != nil {
log.Error(fmt.Sprintf("setAdsCache 保存缓存数据异常:%v", err))
}
}
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
}