初始化项目

This commit is contained in:
2025-12-06 10:38:48 +08:00
parent c9fcfc761e
commit 88a2753211
20 changed files with 2552 additions and 74 deletions

View File

@@ -0,0 +1,104 @@
package service
import (
"cidService/dao"
"cidService/model/dto"
"cidService/model/entity"
"context"
"github.com/gogf/gf/v2/errors/gerror"
)
var (
AdSource = adSourceService{}
)
type adSourceService struct{}
// GetAvailableSources 获取可用的广告源列表
func (s *adSourceService) GetAvailableSources(ctx context.Context) (list []*entity.AdSource, err error) {
return dao.AdSource.GetAvailableSources(ctx)
}
// GetSourcesByProvider 根据提供商获取广告源
func (s *adSourceService) GetSourcesByProvider(ctx context.Context, provider string) (list []*entity.AdSource, err error) {
return dao.AdSource.GetSourcesByProvider(ctx, provider)
}
// CreateAdSource 创建广告源
func (s *adSourceService) CreateAdSource(ctx context.Context, req *dto.CreateAdSourceReq) (id int64, err error) {
// 检查广告源名称是否已存在
existingSource, err := dao.AdSource.GetByName(ctx, req.Name)
if err != nil {
return 0, err
}
if existingSource != nil {
return 0, gerror.New("广告源名称已存在")
}
adSource := &entity.AdSource{
Name: req.Name,
Code: req.Code,
Provider: req.Provider,
Type: req.Type,
APIEndpoint: req.APIEndpoint,
Status: "active", // 默认状态
Priority: 1, // 默认优先级
}
return dao.AdSource.Create(ctx, adSource)
}
// UpdateAdSource 更新广告源
func (s *adSourceService) UpdateAdSource(ctx context.Context, id int64, req *dto.UpdateAdSourceReq) (affected int64, err error) {
// 检查广告源是否存在
existingSource, err := dao.AdSource.GetByID(ctx, id)
if err != nil {
return 0, err
}
if existingSource == nil {
return 0, gerror.New("广告源不存在")
}
// 如果更新名称,检查是否与其他广告源冲突
if req.Name != "" && req.Name != existingSource.Name {
conflictSource, err := dao.AdSource.GetByName(ctx, req.Name)
if err != nil {
return 0, err
}
if conflictSource != nil {
return 0, gerror.New("广告源名称已存在")
}
}
// 构建更新数据
updateData := &entity.AdSource{}
if req.Name != "" {
updateData.Name = req.Name
}
if req.APIEndpoint != "" {
updateData.APIEndpoint = req.APIEndpoint
}
return dao.AdSource.UpdateFields(ctx, id, updateData)
}
// DeleteAdSource 删除广告源
func (s *adSourceService) DeleteAdSource(ctx context.Context, id int64) (affected int64, err error) {
// 检查广告源是否存在
existingSource, err := dao.AdSource.GetByID(ctx, id)
if err != nil {
return 0, err
}
if existingSource == nil {
return 0, gerror.New("广告源不存在")
}
return dao.AdSource.Delete(ctx, id)
}
// GetAdSourceByID 根据ID获取广告源
func (s *adSourceService) GetAdSourceByID(ctx context.Context, id int64) (adSource *entity.AdSource, err error) {
return dao.AdSource.GetByID(ctx, id)
}

360
service/cid_service.go Normal file
View File

@@ -0,0 +1,360 @@
package service
import (
"cidService/dao"
"cidService/model/dto"
"cidService/model/entity"
"cidService/model/types"
"context"
"encoding/json"
"fmt"
"math/rand"
"strconv"
"time"
"gitee.com/red-future---jilin-g/common/utils"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
)
var (
CID = cidService{}
)
type cidService struct{}
// AdMatchingStrategy 广告匹配策略
type AdMatchingStrategy struct {
TenantLevel string // 租户级别
MinConversion float64 // 最低转化率
MaxConversion float64 // 最高转化率
SourceWeight map[string]int // 广告源权重
MaxAdsPerRequest int // 每次请求最大广告数
}
// getMatchingStrategy 获取匹配策略
func (s *cidService) getMatchingStrategy(ctx context.Context, tenantLevel string) (*AdMatchingStrategy, error) {
// 从数据库获取策略
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{
TenantLevel: strategyEntity.TenantLevel,
MinConversion: strategyEntity.MinConversion,
MaxConversion: strategyEntity.MaxConversion,
SourceWeight: sourceWeights,
MaxAdsPerRequest: strategyEntity.MaxAdsPerReq,
}, nil
}
// GenerateCID 生成CID广告
func (s *cidService) GenerateCID(ctx context.Context, req *dto.GenerateCIDReq) (res *dto.GenerateCIDRes, err error) {
// 获取当前用户信息
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, "获取租户信息失败")
}
// 获取匹配策略
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()
return &dto.GenerateCIDRes{
CID: cid,
Ads: ads,
TotalAds: len(ads),
TenantId: tenant.Id,
TenantName: tenant.Name,
GeneratedAt: time.Now().Format("2006-01-02 15:04:05"),
}, nil
}
// getTenantByUser 根据用户获取租户信息
func (s *cidService) getTenantByUser(ctx context.Context, userId int64) (*types.Tenant, error) {
// 通过common模块获取用户信息包含租户ID
userInfo, err := utils.GetUserInfo(ctx)
if err != nil {
return nil, gerror.Wrap(err, "获取用户信息失败")
}
// 租户ID从用户信息中获取
tenantId := gconv.Int64(userInfo.TenantId)
if tenantId == 0 {
tenantId = 1 // 默认租户ID
}
// 租户级别和名称可以根据租户ID通过其他方式获取或配置
// 这里使用映射配置,实际项目中可能需要调用其他服务
tenantName := "默认租户"
tenantLevel := "basic"
tenantStatus := "active"
// 根据租户ID设置不同的级别示例逻辑
switch tenantId {
case 1:
tenantName = "基础租户"
tenantLevel = "basic"
case 2:
tenantName = "标准租户"
tenantLevel = "standard"
case 3:
tenantName = "高级租户"
tenantLevel = "premium"
}
return &types.Tenant{
Id: tenantId,
Name: tenantName,
Level: tenantLevel,
Status: tenantStatus,
}, nil
}
// matchAds 根据策略匹配广告
func (s *cidService) matchAds(ctx context.Context, req *dto.GenerateCIDReq, strategy *AdMatchingStrategy) ([]*dto.AdInfo, error) {
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 从指定广告源获取广告
func (s *cidService) getAdsFromSource(ctx context.Context, source string, req *dto.GenerateCIDReq, strategy *AdMatchingStrategy, weight int) ([]*dto.AdInfo, error) {
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 获取自营广告
func (s *cidService) getSelfServiceAds(ctx context.Context, req *dto.GenerateCIDReq, count int) ([]*dto.AdInfo, error) {
// 这里应该从数据库查询自营广告
// 暂时返回模拟数据
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广告
func (s *cidService) getGoogleAds(ctx context.Context, req *dto.GenerateCIDReq, count int) ([]*dto.AdInfo, error) {
// 这里应该调用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广告
func (s *cidService) getFacebookAds(ctx context.Context, req *dto.GenerateCIDReq, count int) ([]*dto.AdInfo, error) {
// 这里应该调用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
func (s *cidService) generateUniqueCID() string {
timestamp := time.Now().Unix()
random := rand.Intn(8999) + 1000
return fmt.Sprintf("CID_%d_%d", timestamp, random)
}
// recordCIDRequest 记录CID请求
func (s *cidService) recordCIDRequest(ctx context.Context, req *dto.GenerateCIDReq, tenant *types.Tenant, ads []*dto.AdInfo) {
// 转换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),
TenantID: fmt.Sprintf("%d", tenant.Id),
Response: &entity.CidResponse{
Ads: entityAds,
},
ProcessingTime: int64(rand.Intn(401) + 100), // 模拟处理时间
}
dao.CIDRequest.Create(ctx, request)
}
// GetCIDStatistics 获取CID统计信息
func (s *cidService) GetCIDStatistics(ctx context.Context, req *dto.GetCIDStatisticsReq) (res *dto.GetCIDStatisticsRes, err error) {
// 这里应该实现真实的统计逻辑
// 暂时返回模拟数据
return &dto.GetCIDStatisticsRes{
TotalRequests: int64(rand.Intn(9000) + 1000),
SuccessfulReq: int64(rand.Intn(8100) + 900),
AverageProcessTime: rand.Float64()*200 + 50,
TopSources: []string{"self", "google", "facebook"},
ConversionStats: map[string]float64{
"self": rand.Float64() * 0.1,
"google": rand.Float64() * 0.2,
"facebook": rand.Float64() * 0.15,
},
}, nil
}
// GetCIDHistory 获取CID请求历史
func (s *cidService) GetCIDHistory(ctx context.Context, userId int64, page, size int) (res *dto.GetCIDHistoryRes, err error) {
history, total, err := dao.CIDRequest.GetHistory(ctx, userId, page, size)
if err != nil {
return nil, err
}
var historyList []*dto.CIDRequestHistory
for _, record := range history {
// 解析TenantID
tenantId := int64(0)
if record.TenantID != "" {
tenantId, _ = strconv.ParseInt(record.TenantID, 10, 64)
}
// 解析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
}

238
service/strategy_service.go Normal file
View File

@@ -0,0 +1,238 @@
package service
import (
"cidService/dao"
"cidService/model/dto"
"cidService/model/entity"
"context"
"encoding/json"
"strconv"
"gitee.com/red-future---jilin-g/common/utils"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
)
var (
Strategy = strategyService{}
)
type strategyService struct{}
// CreateStrategy 创建策略
func (s *strategyService) CreateStrategy(ctx context.Context, req *dto.CreateStrategyReq) (id int64, err error) {
// 检查策略名称是否已存在
existingStrategy, err := dao.Strategy.GetByName(ctx, req.Name)
if err != nil {
return 0, err
}
if existingStrategy != nil {
return 0, gerror.New("策略名称已存在")
}
// 验证转化率范围
if req.MaxConversion <= req.MinConversion {
return 0, gerror.New("最高转化率必须大于最低转化率")
}
// 序列化权重配置
weightsJson, err := json.Marshal(req.SourceWeights)
if err != nil {
return 0, gerror.Wrap(err, "权重配置序列化失败")
}
// 获取当前用户信息
userInfo, err := utils.GetUserInfo(ctx)
if err != nil {
return 0, gerror.Wrap(err, "获取用户信息失败")
}
// 将UserName转换为int64如果失败则使用0
var userId int64
if uid, ok := userInfo.UserName.(string); ok {
if parsedId, err := strconv.ParseInt(uid, 10, 64); err == nil {
userId = parsedId
}
}
strategy := &entity.Strategy{
Name: req.Name,
Description: req.Description,
TenantLevel: req.TenantLevel,
MinConversion: req.MinConversion,
MaxConversion: req.MaxConversion,
SourceWeights: string(weightsJson),
MaxAdsPerReq: req.MaxAdsPerReq,
Priority: req.Priority,
Status: req.Status,
CreatedBy: userId,
UpdatedBy: userId,
}
return dao.Strategy.Create(ctx, strategy)
}
// UpdateStrategy 更新策略
func (s *strategyService) UpdateStrategy(ctx context.Context, req *dto.UpdateStrategyReq) (affected int64, err error) {
// 检查策略是否存在
existingStrategy, err := dao.Strategy.GetByID(ctx, req.Id)
if err != nil {
return 0, err
}
if existingStrategy == nil {
return 0, gerror.New("策略不存在")
}
// 如果更新名称,检查是否与其他策略冲突
if req.Name != "" && req.Name != existingStrategy.Name {
conflictStrategy, err := dao.Strategy.GetByName(ctx, req.Name)
if err != nil {
return 0, err
}
if conflictStrategy != nil {
return 0, gerror.New("策略名称已存在")
}
}
// 验证转化率范围
if req.MaxConversion <= req.MinConversion {
return 0, gerror.New("最高转化率必须大于最低转化率")
}
// 序列化权重配置
weightsJson, err := json.Marshal(req.SourceWeights)
if err != nil {
return 0, gerror.Wrap(err, "权重配置序列化失败")
}
// 获取当前用户信息
userInfo, err := utils.GetUserInfo(ctx)
if err != nil {
return 0, gerror.Wrap(err, "获取用户信息失败")
}
// 将UserName转换为int64如果失败则使用0
var userId int64
if uid, ok := userInfo.UserName.(string); ok {
if parsedId, err := strconv.ParseInt(uid, 10, 64); err == nil {
userId = parsedId
}
}
strategy := &entity.Strategy{
Id: req.Id,
Name: req.Name,
Description: req.Description,
TenantLevel: req.TenantLevel,
MinConversion: req.MinConversion,
MaxConversion: req.MaxConversion,
SourceWeights: string(weightsJson),
MaxAdsPerReq: req.MaxAdsPerReq,
Priority: req.Priority,
Status: req.Status,
UpdatedBy: userId,
}
return dao.Strategy.Update(ctx, strategy)
}
// DeleteStrategy 删除策略
func (s *strategyService) DeleteStrategy(ctx context.Context, id int64) (affected int64, err error) {
// 检查策略是否存在
existingStrategy, err := dao.Strategy.GetByID(ctx, id)
if err != nil {
return 0, err
}
if existingStrategy == nil {
return 0, gerror.New("策略不存在")
}
return dao.Strategy.Delete(ctx, id)
}
// GetStrategyByID 根据ID获取策略
func (s *strategyService) GetStrategyByID(ctx context.Context, id int64) (strategy *dto.StrategyRes, err error) {
entity, err := dao.Strategy.GetByID(ctx, id)
if err != nil {
return nil, err
}
if entity == nil {
return nil, gerror.New("策略不存在")
}
// 反序列化权重配置
var weights map[string]int
if entity.SourceWeights != "" {
err = json.Unmarshal([]byte(entity.SourceWeights), &weights)
if err != nil {
return nil, gerror.Wrap(err, "权重配置反序列化失败")
}
}
return &dto.StrategyRes{
Id: entity.Id,
Name: entity.Name,
Description: entity.Description,
TenantLevel: entity.TenantLevel,
MinConversion: entity.MinConversion,
MaxConversion: entity.MaxConversion,
SourceWeights: weights,
MaxAdsPerReq: entity.MaxAdsPerReq,
Priority: entity.Priority,
Status: entity.Status,
CreatedAt: entity.CreatedAt.String(),
UpdatedAt: entity.UpdatedAt.String(),
CreatedBy: entity.CreatedBy,
UpdatedBy: entity.UpdatedBy,
}, nil
}
// GetStrategyList 获取策略列表
func (s *strategyService) GetStrategyList(ctx context.Context, req *dto.GetStrategyListReq) (res *dto.GetStrategyListRes, err error) {
list, total, err := dao.Strategy.GetList(ctx, req.Page, req.Size, req.TenantLevel, req.Status)
if err != nil {
return nil, err
}
var strategyList []*dto.StrategyRes
for _, entity := range list {
// 反序列化权重配置
var weights map[string]int
if entity.SourceWeights != "" {
err = json.Unmarshal([]byte(entity.SourceWeights), &weights)
if err != nil {
g.Log().Warningf(ctx, "策略 %d 权重配置反序列化失败: %v", entity.Id, err)
weights = make(map[string]int)
}
}
strategyList = append(strategyList, &dto.StrategyRes{
Id: entity.Id,
Name: entity.Name,
Description: entity.Description,
TenantLevel: entity.TenantLevel,
MinConversion: entity.MinConversion,
MaxConversion: entity.MaxConversion,
SourceWeights: weights,
MaxAdsPerReq: entity.MaxAdsPerReq,
Priority: entity.Priority,
Status: entity.Status,
CreatedAt: entity.CreatedAt.String(),
UpdatedAt: entity.UpdatedAt.String(),
CreatedBy: entity.CreatedBy,
UpdatedBy: entity.UpdatedBy,
})
}
return &dto.GetStrategyListRes{
List: strategyList,
Total: total,
Page: req.Page,
Size: req.Size,
}, nil
}
// GetStrategyByTenantLevel 根据租户级别获取策略
func (s *strategyService) GetStrategyByTenantLevel(ctx context.Context, tenantLevel string) (strategy *entity.Strategy, err error) {
return dao.Strategy.GetByTenantLevel(ctx, tenantLevel)
}