Files
cid/service/cid_service.go

378 lines
11 KiB
Go
Raw Permalink Normal View History

2025-12-06 10:38:48 +08:00
package service
import (
2025-12-09 16:10:45 +08:00
"cid/dao"
"cid/model/dto"
"cid/model/entity"
2025-12-06 10:38:48 +08:00
"context"
"encoding/json"
"fmt"
"math/rand"
"strconv"
"time"
2026-02-24 16:24:47 +08:00
"gitea.com/red-future/common/utils"
2025-12-06 10:38:48 +08:00
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
)
var (
2025-12-09 16:10:45 +08:00
CID = cid{}
2025-12-06 10:38:48 +08:00
)
2025-12-09 16:10:45 +08:00
type cid struct{}
2025-12-06 10:38:48 +08:00
// AdMatchingStrategy 广告匹配策略
type AdMatchingStrategy struct {
TenantLevel string // 租户级别
MinConversion float64 // 最低转化率
MaxConversion float64 // 最高转化率
SourceWeight map[string]int // 广告源权重
MaxAdsPerRequest int // 每次请求最大广告数
}
// getMatchingStrategy 获取匹配策略
2025-12-09 16:10:45 +08:00
func (s *cid) getMatchingStrategy(ctx context.Context, tenantLevel string) (*AdMatchingStrategy, error) {
2025-12-06 10:38:48 +08:00
// 从数据库获取策略
strategyEntity, err := Strategy.GetStrategyByTenantLevel(ctx, tenantLevel)
if err != nil {
return nil, err
}
if strategyEntity == nil {
// 返回默认策略
return &AdMatchingStrategy{
TenantLevel: tenantLevel,
MinConversion: 0.01,
MaxConversion: 0.05,
SourceWeight: map[string]int{"self": 100},
MaxAdsPerRequest: 3,
}, nil
}
// 反序列化权重配置
var sourceWeights map[string]int
if strategyEntity.SourceWeights != "" {
err = json.Unmarshal([]byte(strategyEntity.SourceWeights), &sourceWeights)
if err != nil {
g.Log().Warningf(ctx, "策略权重反序列化失败: %v", err)
sourceWeights = map[string]int{"self": 100}
}
}
return &AdMatchingStrategy{
2025-12-10 15:41:52 +08:00
TenantLevel: tenantLevel, // 使用传入的tenantLevel参数
2025-12-06 10:38:48 +08:00
MinConversion: strategyEntity.MinConversion,
MaxConversion: strategyEntity.MaxConversion,
SourceWeight: sourceWeights,
MaxAdsPerRequest: strategyEntity.MaxAdsPerReq,
}, nil
}
// GenerateCID 生成CID广告
2025-12-09 16:10:45 +08:00
func (s *cid) GenerateCID(ctx context.Context, req *dto.GenerateCIDReq) (res *dto.GenerateCIDRes, err error) {
2025-12-06 10:38:48 +08:00
// 获取当前用户信息
userInfo, err := utils.GetUserInfo(ctx)
if err != nil {
return nil, gerror.Wrap(err, "获取用户信息失败")
}
// 获取租户信息
tenant, err := s.getTenantByUser(ctx, gconv.Int64(userInfo.UserName))
if err != nil {
return nil, gerror.Wrap(err, "获取租户信息失败")
}
2025-12-06 15:24:30 +08:00
// 检查租户请求次数限制
2025-12-10 15:41:52 +08:00
// 将租户ID转换为int64用于限流检查
tenantIdInt := int64(0)
if tenant.Id != "" && tenant.Id != "default" {
tryInt, _ := strconv.ParseInt(tenant.Id, 10, 64)
tenantIdInt = tryInt
}
allowed, err := RateLimit.CheckTenantRequestLimit(ctx, tenantIdInt, nil)
2025-12-06 15:24:30 +08:00
if err != nil {
return nil, gerror.Wrap(err, "检查租户请求限制失败")
}
if !allowed {
return nil, gerror.New("租户请求次数已超过限制,请稍后再试")
}
2025-12-06 10:38:48 +08:00
// 获取匹配策略
strategy, err := s.getMatchingStrategy(ctx, tenant.Level)
if err != nil {
return nil, gerror.Wrap(err, "获取匹配策略失败")
}
// 根据策略获取广告
ads, err := s.matchAds(ctx, req, strategy)
if err != nil {
return nil, gerror.Wrap(err, "广告匹配失败")
}
// 记录CID请求
go s.recordCIDRequest(context.Background(), req, tenant, ads)
// 生成唯一CID
cid := s.generateUniqueCID()
2025-12-10 15:41:52 +08:00
// 转换租户ID为int64兼容性处理
// 这里直接使用之前已经声明的tenantIdInt变量不需要重新声明
if tenant.Id != "" && tenant.Id != "default" {
// 这里简化处理,实际可能需要更复杂的转换逻辑
tryInt, parseErr := strconv.ParseInt(tenant.Id, 10, 64)
if parseErr == nil {
tenantIdInt = tryInt
}
}
2025-12-06 10:38:48 +08:00
return &dto.GenerateCIDRes{
CID: cid,
Ads: ads,
TotalAds: len(ads),
2025-12-10 15:41:52 +08:00
TenantId: tenantIdInt,
2025-12-06 10:38:48 +08:00
TenantName: tenant.Name,
GeneratedAt: time.Now().Format("2006-01-02 15:04:05"),
}, nil
}
// getTenantByUser 根据用户获取租户信息
2025-12-10 15:41:52 +08:00
func (s *cid) getTenantByUser(ctx context.Context, userId int64) (*dto.TenantInfo, error) {
2025-12-06 10:38:48 +08:00
// 通过common模块获取用户信息包含租户ID
userInfo, err := utils.GetUserInfo(ctx)
if err != nil {
return nil, gerror.Wrap(err, "获取用户信息失败")
}
2025-12-10 15:41:52 +08:00
// 租户ID直接从用户信息中获取
tenantId := ""
if userInfo.TenantId != nil {
if tenantIdStr, ok := userInfo.TenantId.(string); ok && tenantIdStr != "" {
tenantId = tenantIdStr
}
} else {
tenantId = "default" // 默认租户ID
tenantId = "default" // 默认租户ID
2025-12-06 10:38:48 +08:00
}
// 租户级别和名称可以根据租户ID通过其他方式获取或配置
// 这里使用映射配置,实际项目中可能需要调用其他服务
tenantName := "默认租户"
tenantLevel := "basic"
// 根据租户ID设置不同的级别示例逻辑
switch tenantId {
2025-12-10 15:41:52 +08:00
case "default":
2025-12-06 10:38:48 +08:00
tenantName = "基础租户"
tenantLevel = "basic"
2025-12-10 15:41:52 +08:00
case "standard":
2025-12-06 10:38:48 +08:00
tenantName = "标准租户"
tenantLevel = "standard"
2025-12-10 15:41:52 +08:00
case "premium":
2025-12-06 10:38:48 +08:00
tenantName = "高级租户"
tenantLevel = "premium"
2025-12-10 15:41:52 +08:00
default:
// 如果租户ID不是预设值使用租户ID作为名称
tenantName = tenantId + "租户"
tenantLevel = "basic"
2025-12-06 10:38:48 +08:00
}
2025-12-10 15:41:52 +08:00
return &dto.TenantInfo{
Id: tenantId,
Name: tenantName,
Level: tenantLevel,
2025-12-06 10:38:48 +08:00
}, nil
}
// matchAds 根据策略匹配广告
2025-12-09 16:10:45 +08:00
func (s *cid) matchAds(ctx context.Context, req *dto.GenerateCIDReq, strategy *AdMatchingStrategy) ([]*dto.AdInfo, error) {
2025-12-06 10:38:48 +08:00
var matchedAds []*dto.AdInfo
// 根据策略权重从不同源获取广告
for source, weight := range strategy.SourceWeight {
if weight <= 0 {
continue
}
sourceAds, err := s.getAdsFromSource(ctx, source, req, strategy, weight)
if err != nil {
g.Log().Warningf(ctx, "从广告源 %s 获取广告失败: %v", source, err)
continue
}
matchedAds = append(matchedAds, sourceAds...)
}
// 过滤符合转化率要求的广告
var filteredAds []*dto.AdInfo
for _, ad := range matchedAds {
if ad.ConversionRate >= strategy.MinConversion && ad.ConversionRate <= strategy.MaxConversion {
filteredAds = append(filteredAds, ad)
}
}
// 限制广告数量
if len(filteredAds) > strategy.MaxAdsPerRequest {
rand.Shuffle(len(filteredAds), func(i, j int) {
filteredAds[i], filteredAds[j] = filteredAds[j], filteredAds[i]
})
filteredAds = filteredAds[:strategy.MaxAdsPerRequest]
}
return filteredAds, nil
}
// getAdsFromSource 从指定广告源获取广告
2025-12-09 16:10:45 +08:00
func (s *cid) getAdsFromSource(ctx context.Context, source string, req *dto.GenerateCIDReq, strategy *AdMatchingStrategy, weight int) ([]*dto.AdInfo, error) {
2025-12-06 10:38:48 +08:00
switch source {
case "self":
return s.getSelfServiceAds(ctx, req, weight)
case "google":
return s.getGoogleAds(ctx, req, weight)
case "facebook":
return s.getFacebookAds(ctx, req, weight)
default:
return nil, gerror.Newf("不支持的广告源: %s", source)
}
}
// getSelfServiceAds 获取自营广告
2025-12-09 16:10:45 +08:00
func (s *cid) getSelfServiceAds(ctx context.Context, req *dto.GenerateCIDReq, count int) ([]*dto.AdInfo, error) {
2025-12-06 10:38:48 +08:00
// 这里应该从数据库查询自营广告
// 暂时返回模拟数据
ads := make([]*dto.AdInfo, 0)
for i := 0; i < count; i++ {
ads = append(ads, &dto.AdInfo{
Id: int64(rand.Intn(89999) + 10000),
Title: fmt.Sprintf("自营广告 %d", i+1),
Description: "这是一个高质量的自营广告",
ImageUrl: "https://example.com/ad.jpg",
TargetUrl: "https://example.com/landing",
ConversionRate: rand.Float64(),
Source: "self",
Bid: rand.Intn(901) + 100,
})
}
return ads, nil
}
// getGoogleAds 获取Google广告
2025-12-09 16:10:45 +08:00
func (s *cid) getGoogleAds(ctx context.Context, req *dto.GenerateCIDReq, count int) ([]*dto.AdInfo, error) {
2025-12-06 10:38:48 +08:00
// 这里应该调用Google Ads API
// 暂时返回模拟数据
ads := make([]*dto.AdInfo, 0)
for i := 0; i < count; i++ {
ads = append(ads, &dto.AdInfo{
Id: int64(rand.Intn(9999) + 20000),
Title: fmt.Sprintf("Google广告 %d", i+1),
Description: "来自Google的高质量广告",
ImageUrl: "https://google.com/ad.jpg",
TargetUrl: "https://google.com/landing",
ConversionRate: rand.Float64()*0.3 + 0.1,
Source: "google",
Bid: rand.Intn(1301) + 200,
})
}
return ads, nil
}
// getFacebookAds 获取Facebook广告
2025-12-09 16:10:45 +08:00
func (s *cid) getFacebookAds(ctx context.Context, req *dto.GenerateCIDReq, count int) ([]*dto.AdInfo, error) {
2025-12-06 10:38:48 +08:00
// 这里应该调用Facebook Ads API
// 暂时返回模拟数据
ads := make([]*dto.AdInfo, 0)
for i := 0; i < count; i++ {
ads = append(ads, &dto.AdInfo{
Id: int64(rand.Intn(9999) + 30000),
Title: fmt.Sprintf("Facebook广告 %d", i+1),
Description: "来自Facebook的高质量广告",
ImageUrl: "https://facebook.com/ad.jpg",
TargetUrl: "https://facebook.com/landing",
ConversionRate: rand.Float64()*0.25 + 0.08,
Source: "facebook",
Bid: rand.Intn(1051) + 150,
})
}
return ads, nil
}
// generateUniqueCID 生成唯一CID
2025-12-09 16:10:45 +08:00
func (s *cid) generateUniqueCID() string {
2025-12-06 10:38:48 +08:00
timestamp := time.Now().Unix()
random := rand.Intn(8999) + 1000
return fmt.Sprintf("CID_%d_%d", timestamp, random)
}
// recordCIDRequest 记录CID请求
2025-12-10 15:41:52 +08:00
func (s *cid) recordCIDRequest(ctx context.Context, req *dto.GenerateCIDReq, tenant *dto.TenantInfo, ads []*dto.AdInfo) {
2025-12-06 10:38:48 +08:00
// 转换dto.AdInfo到entity.Ad
var entityAds []entity.Ad
for _, ad := range ads {
entityAds = append(entityAds, entity.Ad{
ID: fmt.Sprintf("%d", ad.Id),
AdSource: ad.Source,
Title: ad.Title,
Description: ad.Description,
CreativeURL: ad.ImageUrl,
LandingURL: ad.TargetUrl,
BidAmount: int64(ad.Bid),
})
}
request := &entity.CidRequest{
RequestID: fmt.Sprintf("REQ_%d_%d", time.Now().Unix(), rand.Intn(10000)),
UserID: fmt.Sprintf("%d", req.UserId),
Response: &entity.CidResponse{
Ads: entityAds,
},
ProcessingTime: int64(rand.Intn(401) + 100), // 模拟处理时间
}
2025-12-10 15:41:52 +08:00
_, err := dao.CIDRequest.Create(ctx, request)
if err != nil {
g.Log().Errorf(ctx, "记录CID请求失败: %v", err)
}
2025-12-06 10:38:48 +08:00
}
// GetCIDHistory 获取CID请求历史
2025-12-09 16:10:45 +08:00
func (s *cid) GetCIDHistory(ctx context.Context, userId int64, page, size int) (res *dto.GetCIDHistoryRes, err error) {
2025-12-10 15:41:52 +08:00
history, total, err := dao.CIDRequest.GetHistory(ctx, strconv.FormatInt(userId, 10), page, size)
2025-12-06 10:38:48 +08:00
if err != nil {
return nil, err
}
var historyList []*dto.CIDRequestHistory
for _, record := range history {
// 解析TenantID
tenantId := int64(0)
2025-12-10 15:41:52 +08:00
if tenantIdStr, ok := record.TenantId.(string); ok && tenantIdStr != "" {
tenantId, _ = strconv.ParseInt(tenantIdStr, 10, 64)
2025-12-06 10:38:48 +08:00
}
// 解析UserID
uid := int64(0)
if record.UserID != "" {
uid, _ = strconv.ParseInt(record.UserID, 10, 64)
}
historyList = append(historyList, &dto.CIDRequestHistory{
Id: 0, // 使用默认值因为entity使用的是ObjectID
TenantId: tenantId,
UserId: uid,
RequestType: "CID", // 默认值
Status: "completed", // 从response状态获取
ProcessTime: int(record.ProcessingTime),
CreatedAt: record.CreatedAt.String(),
})
}
return &dto.GetCIDHistoryRes{
List: historyList,
Total: total,
Page: page,
Size: size,
}, nil
}