Compare commits

29 Commits
Author SHA1 Message Date
19904408334 f6b241528b chore(cid): 更新数据库、Redis、Consul 及 Jaeger 地址与凭据 2026-09-04 13:33:53 +08:00
19904408334 8b07b3ae12 chore(deploy): 更新 PostgreSQL 密码 2026-09-02 17:05:09 +08:00
admin 13cd3875d3 saas隔离使用admin-go中的租户体系 2026-08-25 15:26:13 +08:00
admin 6ce3ebbb91 1、文档规范化
2、增加cid核心功能entity
2026-08-25 15:06:34 +08:00
admin b3c0855236 1、文档规范化
2、增加cid核心功能entity
2026-08-25 11:54:29 +08:00
lmk 25e87240f9 调整目录 2026-08-25 10:14:32 +08:00
admin 761e9718ec gitea,gitea-runner写入docker-compose 2026-08-06 09:40:34 +08:00
lmk 2fd858c97e cid适配ppgo_job 2026-07-18 10:10:23 +08:00
lmk d930266fbf 提交代码 2026-07-02 10:37:59 +08:00
admin aced1aa3a6 common版本更新 2026-06-25 17:07:13 +08:00
admin 0f4e97ff28 common版本更新 2026-06-25 17:05:16 +08:00
admin 03fe8be556 common版本升级 2026-06-23 18:32:03 +08:00
admin c93a06868d ci/cd调整 2026-06-10 16:10:10 +08:00
admin c96f8e0fb3 ci/cd调整 2026-06-10 15:41:58 +08:00
lmk ce420a54a7 去掉无用代码 2026-05-21 11:21:56 +08:00
lmk 3dbcf7a8e3 yidun送检新增账户下拉和导出优化 2026-05-15 14:01:15 +08:00
admin 307b01c0e3 封版 2026-05-15 10:34:51 +08:00
lmk c8cc19e8e7 yidun送检功能 2026-05-15 10:28:17 +08:00
lmk 51d26aeee7 yidun检测、查询 2026-05-08 09:33:40 +08:00
admin 22dd73c37f common版本更新 2026-04-22 18:00:33 +08:00
admin c835667d3d 增加dockerfile配置 2026-04-09 17:42:57 +08:00
admin ee63b80d36 增加dockerfile配置 2026-04-09 16:14:32 +08:00
admin 6bd81cf8ff golang版本升级1.26.0 2026-04-03 11:39:33 +08:00
admin a534964575 golang版本升级1.26.0 2026-04-03 11:30:13 +08:00
admin 98c5867e6a golang版本升级1.26.0 2026-04-03 11:05:05 +08:00
admin 32fc63c695 Dockerfile 2026-04-01 13:49:16 +08:00
admin 98d0bd0688 Dockerfile 2026-04-01 13:16:32 +08:00
admin 969e7ab53c Dockerfile 2026-04-01 13:04:59 +08:00
admin 827d55dbee Dockerfile 2026-03-23 14:08:11 +08:00
121 changed files with 8090 additions and 6211 deletions
+1
View File
@@ -0,0 +1 @@
.git
+5
View File
@@ -1 +1,6 @@
/.idea/*
/docs/*
# 运行日志
/resource/log/*
+108
View File
@@ -0,0 +1,108 @@
# CLAUDE.md
## 目录结构与职责(硬性约束)
> **`biz/` 是泛化占位名,不是固定目录命名**。表格中 `biz/` 代表「业务模块目录」,各项目必须按自身业务命名替换(本项目即 `biz/`),禁止新项目照抄 `biz/`;`data/`、`workspace/` 亦为本项目目录名,各项目按自身命名。
| 目录 | 职责 | 强约束 |
|---|---|---|
| common/ | 通用层:HTTP 服务与鉴权中间件、文件解析(parser + pdf/docx/html/text)、中文分词、向量 JSON、DAO 基类、查询缓存、协程池封装 | 不得依赖业务模块包;新增跨模块通用能力放这里 |
| biz/consts/ | 常量集中地:表名(table_name.go)、状态(status.go)、内容类型、默认参数与各协程池默认大小(consts.go) | 业务常量一律在此集中,禁止散落 magic number;新增池默认大小在此定义 |
| biz/model/ | entity(表结构,与 DAO 一一对应)、dto(请求/响应结构,`g.Meta` 内嵌定义路由)、domain(领域模型:跨表聚合与服务层组装值,可被 dto/entity 引用) | entity 只做表映射,不带业务逻辑;dto 是 controller 与 HTTP 的唯一出入口;domain 收纳不属于 dto 也不属于 entity 的类型(见下) |
| biz/dao/ | 单表数据访问,每表一个文件 | 无业务逻辑;查询经 base_dao 缓存 |
| biz/service/ | 业务逻辑:规则校验、文件读写、事务、跨表组装、调用 dao、LLM 编排 | 不直接写 HTTP 响应(例外见下);并行任务走 common 协程池 |
| biz/controller/ | 接口层:接收参数、调用 service、组装返回值 | 见「分层职责规范」;禁止调用 dao |
| 运行时数据目录 | SQLite 库、上传/解析文件(本项目 `data/` `workspace/`) | 不提交 git;删除即丢失数据,改动前先确认 |
## 分层职责规范(硬性要求)
严格分层 `controller → service → dao`,禁止跨层调用(controller 禁止直接调 dao)。
| 层 | 目录 | 职责 | 禁止 |
|---|---|---|---|
| controller | biz/controller | 接收 dto 请求参数(依赖 DTO `v` tag 自动校验)调用 service,原样返回 service 结果(返回类型与 service 一致,即 dto);**传参方式:整个 `*dto.XxxReq` 直接传给 service,禁止从 dto 拆出多个属性逐个传参** | 直接调用 dao;任何组装/映射/字段搬运;手写业务规则校验(库表依赖/跨字段,应下沉 service);文件 IO;状态流转;跨表数据组装 |
| service | biz/service | 业务逻辑:规则校验、文件读写、事务、跨表组装、调用 dao;只允许返回 dto 类型(返回与 controller 输出一致的 `*dto.XxxRes`),派生值(如 scene_name/node_count)在 service 用 dto 组装 | 直接写 HTTP 响应(例外见下);返回裸 gdb.Record 或任何非 dto 类型 |
| dao | biz/dao | 构建 SQL 并执行;行→结构体转换在 dao 内部用 GoFrame 自带方法(`Record.Struct` / `Result.Structs`,按 `orm` tag)完成,对外只允许返回 entity(或单值如 int/map) | 业务逻辑;返回裸 gdb.Record——**裸 gdb.Record 不允许作为任何分层方法的返回值**(含 service 内事务读),转换只发生在 dao 内部,不外泄 |
**例外**:SSE 流式响应、HTML/文件导出等"直接写响应体"的场景由 controller 完成——这是"值返回"的流式形式,事件序列化、心跳属 HTTP 协议职责,保留在 controller。
**分层锚定原则(controller 反向锚定,防跑偏)**:controller 只透传 ⇒ 接口返回类型以 dto 为准 ⇒ service 返回类型被 dto 锁死 ⇒ dao 输出被 entity 锁死。任何一层若出现"为下一层做数据搬运"(controller 映射 service 结果、service 逐键取裸 Record 字段),即违反本原则,应向上收敛:转换在 dao 内部(Record→entity)、组装在 service(entity→dto)、透传在 controller。
**教训(此前偏离原因,开发时引以为戒)**:
1. 自定义中转层不得替代 dto:domain 只收"不进 HTTP 出入参"的纯领域值(如结算输入 SettleState/结算结果 FinalSettle);凡出现在接口出入参中的类型一律用 dto,由 service 直接产出——否则 controller 被迫承担映射,违反"controller 只透传"
2. dao 必须完成行→结构体转换:裸 gdb.Record 的 string 键取值是魔法值,拼错列名编译期不报错,且会把键取值扩散到 service/controller;GoFrame 自带 `Record.Struct`/`Result.Structs`(按 `orm` tag)即转换手段
3. 新分层先对齐框架惯例:GoFrame 原生分层即 dao 转 entity / service 返回 dto / controller 薄透传,自定义设计前先核对框架默认范式
## 分层文件对齐与代码模式(硬性要求)
- 每张业务表对应一组 `entity / dao / service / controller / dto` 文件,数量严格对齐(核验方式:每层目录文件数 = 分层表数,分层表数 = 总表数 − 豁免表数);虚拟表(向量 vec0 / FTS5)与**流水/记录类表(如 point_log)豁免分层对齐**:不建任何独立分层文件(含 entity/dao),建表由主表 dao 统一管理(同虚拟表模式),由使用方 service 事务内直写,禁止为只写不读的审计表造分层门面;无任何读写引用的死表连表带分层整套删除,启动时 DROP 库内残留表与代码保持一致
- **非表文件一律不进业务分层目录**:路由注册与中间件装配、表初始化列表(建表 + 死表 DROP)直接写在 `main.go`;鉴权等跨模块通用能力放 `common/`;跨表业务流程归入所属表文件(如闯关 Choose 属 level 表)——分层目录出现非表文件即违反对齐,禁止以非表名开独立分层文件
- **不建 parser/rag 等技术目录**:纯技术能力(文档解析、中文分词、向量序列化)平铺在 `common/`;业务编排(分块、检索、工作流)归入对应 service 文件
- entity:每文件一张表,`orm` 标签与列名一致,时间字段用 `*gtime.Time`,只做表映射
- dto:请求/响应结构,`g.Meta` 内嵌定义路由;只描述 HTTP 出入参,不承载领域逻辑
- **domain(目录 `biz/model/domain/`,package domain)**:仅收纳**不进 HTTP 出入参**的纯领域值(如结算输入 SettleState/结算结果 FinalSettle,service 内部流转 + 单测使用);判断标准:类型是否出现在接口出入参中——出现即用 dto。entity 对表、dto 对 HTTP、domain 对纯领域;service 返回 dto(允许 import dto),dao 返回 entity(禁止外泄 gdb.Record)
- dao:单例 `var Xxx = &xxxDao{}`,`init()``CREATE TABLE IF NOT EXISTS` + 索引 + 迁移;通用 CRUD 复用 `common/base_dao.go`(InsertAndReturnId / GetOneByPk / UpdateByPk)
- controller:路由**反射注册**(`common.BindController`),path/method 唯一来源为 dto 的 `g.Meta`(携带 path/method/summary);新增接口只需写 dto + controller 方法,禁止在 main.go 手动逐条注册;跨组方法以 `H5` 前缀命名(h5 组只注册 H5 开头方法,admin 组跳过)
- **接口只允许 GET / POST**:写操作传 JSON body(或 multipart),读操作走 query params;无 PUT/DELETE
- dao 查询缓存:查询用 `gdb.CacheOption`(TTL 来自配置),**写操作后必须清对应缓存**,否则出现"库里已改、查询还是旧值"
## 错误处理规范(硬性要求)
- 所有可能失败的调用必须显式处理返回的 error:向上返回(保留上下文用 `gerror.Wrap`/`Newf`)或记录日志,禁止 `_, _ =` 静默丢弃——吞错会掩盖故障根因,修复问题必须先定位错误路径,不得以忽略 error 换取编译通过
- defer 关闭等无法向上返回的资源清理错误,用 `defer func() { _ = x.Close() }()` 显式声明忽略意图,禁止裸 `defer x.Close()` 隐式吞错
## 并发规范
- **可并行的场景**:纯 IO 任务——读查询、LLM/Embedding 调用、文件读取。SQLite 写一律回主 goroutine 串行(无 WAL 时并发写会 `database is locked`,锁定风险归零,并发只赢在 IO 等待上)
- **新增并行点的固定三处**:common 加池封装(grpool)→ `biz/consts` 加默认大小 → `config.yml``key: 并发度`(缺失或非法时回退默认值)
- 禁止直接用裸 `go` 启动并行工作负载,一律走 `common` 的池
- **防死锁**:等待链单向「主 → A池 → B池」,被等待池的任务内不得再等待任何池(会饿死 worker);池无 Wait 方法,等待用调用方 `sync.WaitGroup`,任务结果经 buffered channel 回主 goroutine
- **共享状态安全(内存)**:池内任务并发执行,共享实例(service 单例、model 句柄等)只允许**只读**访问;可变字段必须在**提交池之前**由主 goroutine 一次性预置,任务内禁止写共享字段——Go map 并发写直接 `fatal error: concurrent map writes`,无锁、无降级、不可恢复,只能崩溃重启。需要可变共享状态时按优先级:① 无共享(任务内新建、buffered channel 传递结果)② 锁(`sync.Mutex`/`RWMutex`,锁内只做内存操作,LLM/DB 等 IO 放锁外)③ `sync/atomic`(仅限 int 类标量计数/标志,如 `atomic.AddInt64`,并发计数禁用普通 `++`;复合结构不要用 atomic,指针 CAS 属例外)
- **锁的使用**:互斥场景唯一入口是 `common.WithLock[T any](ctx, key, expire, retries, retryInterval, fn func() (T, error)) (T, error)`——泛型回调,业务返回值经 T 原样透出给下游;内部按 config.yml 自动选择锁实现(配置了 `redis` 节点 → redis 锁,跨实例互斥,SET NX EX + token 对比删除防误删他人锁;未配置 → gcache 内存锁,单实例互斥),禁止直接用 gcache/gredis 自己实现加锁。拿不到锁(被占用,`ErrLockHeld`)最多重试 `retries` 次、每次间隔 `retryInterval`(`retries=0` 立即失败;ctx 取消/超时同样终止等待);中间件故障不重试直接返回。锁自动释放:无论 fn 成功、失败还是 panic,defer 释放。`expire` 必须 > 0(进程崩溃兜底不死锁),fn 耗时必须在 expire 前完成,fn 内禁止长耗时 IO(LLM/DB 调用);锁粒度按业务唯一键尽量小
- **裸 `go` 允许的例外**:`go func(){ wg.Wait(); close(ch) }()` 收尾惯用法、SSE 心跳、流式管道(Stream 读写)等长生命周期/非工作负载协程
## 文档职责(三文档体系)
| 文档 | 职责 | 何时补充/更新 |
|---|---|---|
| CLAUDE.md(本文件) | 公司通用开发规范:分层职责、代码模式、并发/事务/缓存约束、流程 | 规范变化时 |
| README.md | 项目功能介绍:架构、数据流、表清单、功能模块、API 清单、使用说明 | 功能增减时 |
| 技术设计.md | 实现细节与技术决策:DDL、检索参数、风险与备选方案 | 关键技术决策/参数变化时 |
## 开发流程(文档驱动,硬性要求)
永远以文档驱动开发:用户提出开发需求 → 先给出实现方案(技术选型、影响面、改动清单) → **用户确认后先补充文档再动手写代码**。补充哪个文档取决于内容性质:规范 → 本文件,功能 → README,实现细节/技术决策 → 技术设计.md。禁止未经确认直接开发,禁止先写代码后补文档。
## 数据访问规范(硬性要求)
- **事务**:涉及多张表的增删改操作必须包数据库事务,禁止逐表裸调用。**事务必须在 service 层**(`g.DB().Transaction` 包裹与事务内写方法如 `XxxInTx`,service 持有 tx 句柄编排多表),dao 层只做单表无状态 CRUD,不持事务;`tx.Begin` 后必须用 `defer` 防护已提交后的二次 Rollback
- **SQL 单表约束**:每个 SQL 只允许访问一张表,禁止 JOIN 与跨表子查询(`IN (SELECT ...)` / `EXISTS`);跨表数据一律拆为多条单表 SQL + 应用层内存组装——先取外键 id 列表,再对目标表 `IN` 查询;`IN` 参数须按 ≤100 分批(SQLite 变量数上限 999)
- **禁止 N+1 查询**:禁止在循环中逐条查库。循环场景一律改为批处理——一次 `ListByXxx` 取回后按外键在内存分组
- **缓存一致性**:DAO 查询走缓存(TTL 来自 `database.cache.ttl`),写操作后必须清对应缓存
- **批处理 SQL**:批量写入用 `InsertAll` 类方法,批量删除用 `IN` 子句,禁止循环单条 INSERT/DELETE
- **配置即使用**:config.yml 中出现 redis / mq 等中间件配置时,代码必须实际接入使用,禁止"配置了但代码不用"或"代码写死但配置缺失"
- **消息/回调幂等**:接入 MQ / Webhook 时,消费与回调处理必须幂等——MQ 至少一次语义、webhook 失败重试都可能重复投递同一事件,禁止依赖"只投一次"假设。幂等手段:以业务唯一键(如 `任务ID + 事件类型`)先查重或建唯一约束再落库,重复事件直接忽略;重试与补偿逻辑同样要防重复执行
## 运维部署规范(硬性要求)
- **部署形态**:Docker Compose 单机部署,前后端一体单端口;运行时数据目录挂载持久化,容器重建不丢数据。部署文件在项目根目录(`Dockerfile` / `docker-compose.yml`),启动与使用见 README
- **数据即文件**:文件型存储,备份 = 打包「数据库目录(小而关键)+ 上传文件目录(大而可重建)」两个目录;迁移 = 拷贝到新机器即可
- **运行时数据与代码分离**:数据目录不提交 git;**删除即丢数据,改动前先确认**
- **配置即文件**:项目配置文件为唯一配置入口(监听端口、并发度等),环境变量覆盖无效
## 金额单位规范(硬性要求)
- **金额一律以「分」为单位存储与传输**:所有金额字段(零售价、进价、单价、小计、总金额)在数据库与接口中使用整数分(entity/dto 字段类型 `int64`),禁止浮点元
- **展示转换在前端**:前端展示 ÷100 转元保留两位小数,提交 ×100 转分;禁止后端做元↔分转换,后端只处理整数分
- **金额计算**:`数量(浮点克) × 单价(分)` 后必须 `common.RoundInt` 四舍五入到整数分,禁止裸浮点累加
- **量纲区分**:数量/库存/用量为浮点克(REAL),金额为整数分;混算时必须显式转换
- 存量库迁移以 `PRAGMA user_version` 版本化标记,禁止重复执行
## 约定
- controller 方法签名固定为 `(ctx, *dto.XxxReq) (*dto.XxxRes, error)`,实例注册模式 `var Xxx = &xxx{}`
- **参数校验优先用 GoFrame DTO 校验**:请求结构体用 `v` tag(required / regex / in 等)声明,框架自动校验并返回错误,controller 不手写校验;仅 DTO 表达不了的业务规则(跨字段依赖、查库校验如重名、取值范围依赖配置)放 service。JSON 格式解析可留在 controller 或下沉 service,但须保持与调用点一致
- 响应组装(实体 → DTO 字段映射)在 controller 进行
- service 方法签名 ctx 开头,错误统一用 `gerror`
- 编译验证:`go build ./...`
+21 -53
View File
@@ -1,57 +1,25 @@
FROM golang:1.25.3
RUN go env -w GO111MODULE=on
RUN go env -w GOPROXY=https://goproxy.cn,direct
ENV WORKDIR /usr/local/bin/app
WORKDIR $WORKDIR
# 阶段1: 构建
FROM golang:alpine AS builder
ENV TIME_ZONE=Asia/Seoul
RUN useradd -ms /bin/bash golang
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
apk add --no-cache git ca-certificates tzdata
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ENV GO111MODULE=on
ENV GOPROXY=https://goproxy.cn,direct
ENV CGO_ENABLED=0
ENV GOTOOLCHAIN=auto
WORKDIR /build
COPY . .
RUN go mod download && go mod tidy
RUN go build -ldflags="-s -w" -o main ./main.go
RUN ln -sf /usr/share/zoneinfo/$TIME_ZONE /etc/localtime \
&& chmod 0755 /usr/bin/wall \
&& chmod 0755 /usr/bin/passwd \
&& chmod 0755 /usr/bin/newgrp \
&& chmod 0755 /usr/bin/chfn \
&& chmod 0755 /usr/bin/chage \
&& chmod 0755 /usr/bin/gpasswd \
&& chmod 0755 /usr/bin/chsh \
&& chmod 0755 /usr/bin/expiry \
&& chmod 0755 /bin/umount \
&& chmod 0755 /bin/mount \
&& chmod 0755 /bin/su \
&& chmod 0755 /sbin/unix_chkpwd \
&& chmod 110 /usr/bin/chfn \
&& chmod 110 /usr/bin/passwd \
&& chmod 110 /usr/bin/newgrp \
&& chmod 110 /usr/bin/chsh \
&& chmod 110 /usr/bin/wall \
&& chmod 110 /usr/bin/gpasswd \
&& chmod 110 /usr/bin/expiry \
&& chmod 110 /usr/bin/chage \
&& chmod 110 /bin/mount \
&& chmod 110 /bin/umount \
&& chmod 110 /bin/su \
&& chmod 110 /sbin/unix_chkpwd \
&& chmod 110 /usr/lib/openssh/ssh-keysign \
&& chmod 110 /usr/bin/ssh-agent
EXPOSE 3001
COPY go.mod go.sum ./
# RUN chown -R golang:golang $WORKDIR
RUN go mod download && go mod verify
COPY ../../cid $WORKDIR
RUN chown -R golang:golang $WORKDIR
# Remove SetUID, SetGID
RUN chmod 0755 /usr/local/bin/app/api \
&& chmod 0755 /usr/local/bin/app/common \
&& chmod 0755 /usr/local/bin/app/middleware \
&& chmod 0755 /usr/local/bin/app/model \
&& rm -rf .git \
&& rm -rf .gitignore \
&& rm -rf .gitlab-ci.yml \
&& rm -rf Dockerfile.bak \
&& rm -rf config-local.yml
USER golang
RUN go build -v -o /usr/local/bin/app ./...
EXPOSE 3002
CMD ./cid
CMD ["./main"]
+122
View File
@@ -0,0 +1,122 @@
# CID
CID 服务,承载三块业务:
1. **内容审核(现有)**:将广告素材(图片/视频)提交易盾进行内容审核,提供 REST API、定时送检调度器和前端管理页
2. **投放平台(规划中)**:外投对接广告平台营销 API(巨量/聚光/磁力等)建计划、传素材、启停调优、拉报表;自有 DSP 投自有媒体流量(PD 直投/程序化直购 + RTB 实时竞价)
3. **CID 归因平台(规划中)**:连接投放与电商平台(京东/淘宝等),记录广告点击、归因转化、回传转化给广告平台(自有流量转化回流自身优化)
## 目录结构与业务模块
分层 + 业务模块结构:顶层按层分目录,每层下按业务域分子模块,各层模块目录严格镜像。
```
controller/ HTTP 接口层,按模块分子目录
service/ 业务逻辑层,按模块分子目录
dao/ 数据访问层,按模块分子目录(各模块 db.go 提供 Model helper 选择数据源库)
model/
├── entity/ 数据库表实体(每表一个文件:Entity + Col + Cols 三段式)
├── dto/ 请求/响应结构
└── event/ 事件体(MQ/ClickHouse 消息契约,规划中)
consts/ 常量(表名、状态),按模块分子目录
resource/frontend/ 静态前端页面
sql/ DDL 与迁移脚本
```
| 模块 | 业务域 | 主要表 |
|---|---|---|
| check/ | 内容审核(现有):易盾检测提交/回调/轮询、素材校验、送检调度 | tencent_image、tencent_video、material_verify_log、tencent_content_check_log、tencent_account_relation |
| delivery/ | 投放域(规划):计划树四层(计划/单元/广告/创意),本地实体 + 平台 ID 双轨同步 | campaign、ad_group、ad、creative |
| material/ | 素材域(规划):素材文件库与多通道审核状态机 | material、material_audit |
| dsp/ | 自有流量域(规划):自有媒体/广告位、PD 预订单、RTB 请求与展示计费 | media、ad_slot、placement、bid_request_log、auction_log |
| attribution/ | 归因域(规划):点击记录、CID 生成、订单归因与转化回传(点击→转化→回传为同一链路) | click_log、conversion_order、report_task |
| platform/ | 平台配置域(规划):广告账户(多级层级 + API 凭据 + 能力)、下游电商平台对接配置与商品映射 | ad_account、commerce_platform_config、item_mapping |
| report/ | 报表域(规划):统一消耗/效果汇总(外投平台报表 + DSP 聚合) | daily_report |
> 租户/广告主信息由 admin-go 承载,CID 各表以 `tenant_id` 隔离,不建账户/租户表。
## 数据流
**内容审核链路**
```
素材表(tencent_image/tencent_video, dataengine库)
→ 定时送检调度器 / 手动送检
→ 易盾检测(回调模式或轮询模式)
→ 结果写入 material_verify_log(cid库) + 回填素材表审核状态
```
**投放链路(规划)**
```
外投:CID 创建计划(campaign/单元/广告/创意,本地草稿)
→ 同步平台营销 API(计划/单元/广告/创意/素材上传)
→ 平台审核(素材审核状态机)→ 启停/调价 → 拉取平台报表 → daily_report
自有DSPPD直投(placement 预订单)或 RTB 竞价(bid_request_log → auction_log
→ 展示/点击计费 → 点击进归因链路(channel=self_dsp
```
**CID 归因链路(规划)**
```
用户点击广告 → 广告平台/自有媒体跳转 CID(携带广告参数)
→ 生成 click_id、记录点击(click_log)→ 302 跳转电商平台(URL 带 click_id
→ 用户下单支付 → 电商平台回调 CID(订单 + click_id
→ 归因去重(conversion_order,幂等)→ 回传转化给广告平台(report_task 队列 + 失败重试)
```
## 表清单
### check 模块(现有)
| 表 | 库 | 说明 |
|---|---|---|
| tencent_image | dataengine | 图片素材(外部系统共享) |
| tencent_video | dataengine | 视频素材(外部系统共享) |
| material_verify_log | cid | 素材校验日志(易盾结果落库) |
| tencent_content_check_log | cid | 送检日志 |
| tencent_account_relation | cid | 腾讯广告账户关系 |
### 投放/归因模块(规划,DDL 见技术设计.md)
| 表 | 库 | 说明 |
|---|---|---|
| ad_account | cid | 广告账户(多级层级 + API 凭据 + 能力,由 ad_platform_account 改造) |
| campaign / ad_group / ad / creative | cid | 计划树四层(本地实体 + 平台 ID 双轨) |
| material / material_audit | cid | 素材库 + 多通道审核状态机 |
| media / ad_slot / placement | cid | 自有媒体/广告位/PD 预订单 |
| bid_request_log / auction_log | cid | RTB 请求日志 / 展示计费(按 day 分区) |
| click_log | cid | 点击日志(不可变事件),按 day 分区 |
| conversion_order | cid | 转化订单(归因与回传状态) |
| report_task | cid | 转化回传任务队列(失败退避重试) |
| daily_report | cid | 统一消耗/效果汇总(外投报表 + DSP 聚合) |
| commerce_platform_config | cid | 下游电商平台对接配置 |
| item_mapping | cid | 广告商品 ↔ 电商商品映射 |
## API 清单
路由由 common 库 `http.RouteRegister` 反射注册:路径按控制器结构体名 + 方法名自动派生(如 `YidunController``DetectText``/yidun/controller/detect-text`)。main.go 注册的控制器:
| 控制器 | 职责 |
|---|---|
| check.YidunController | 文本/图片/视频检测提交、结果查询 |
| check.YidunCallback | 易盾回调接收、结果轮询 |
| check.ContentCheck | 送检调度器启停、状态查询、手动送检 |
| check.MaterialVerify | 素材校验列表/统计/人工审核/导出 |
## 构建与运行
```bash
# 构建
docker build -t cid .
# 本地运行(需 Go 1.26
go mod download && go mod tidy
go run main.go
# 编译
go build -ldflags="-s -w" -o main ./main.go
```
配置入口为 `config.yml`(易盾凭据、callback_mode、content_check 调度参数、数据库连接等)。
+103 -8
View File
@@ -1,18 +1,58 @@
server:
address : ":3001"
name: "cid"
workerId: 1
logPath: "resource/log/server"
logStdout: true
errorStack: true
rate:
limit: 200
burst: 300
mongo:
logger:
level: "all"
stdout: true
address: "mongodb://116.204.74.41:27017/cid_service?retryWrites=true"
# Database.
database:
cid:
- type: "pgsql"
host: "192.168.0.83"
port: "15432"
user: "sql9f15b63fd203b36e"
pass: "1ec94b1acdaf57b66030242d418fad5a"
name: "cid"
role: "master"
maxIdle: "5"
maxOpen: "20"
maxLifetime: "60s"
charset: "utf8mb4"
debug: true
dryRun: false
createdAt: "created_at"
updatedAt: "updated_at"
deletedAt: "deleted_at"
timeMaintainDisabled: false
# data-engine 数据库配置(用于存放 tencent_image, tencent_video 等送检表)
dataEngine:
- type: "pgsql"
host: "192.168.0.83"
port: "15432"
user: "sql9f15b63fd203b36e"
pass: "1ec94b1acdaf57b66030242d418fad5a"
name: "engine"
role: "master"
maxIdle: "5"
maxOpen: "20"
maxLifetime: "60s"
charset: "utf8mb4"
debug: true
dryRun: false
createdAt: "created_at"
updatedAt: "updated_at"
deletedAt: "deleted_at"
timeMaintainDisabled: false
redis:
# 集群模式配置方法
default:
address: 116.204.74.41:6379
address: 192.168.0.83:6379
db: 0
idleTimeout: "60s" #连接最大空闲时间,使用时间字符串例如30s/1m/1d
maxConnLifetime: "90s" #连接最长存活时间,使用时间字符串例如30s/1m/1d
@@ -22,7 +62,62 @@ redis:
writeTimeout: "30s" #TCP的Write操作超时时间,使用时间字符串例如30s/1m/1d
maxActive: 100
consul:
address: 116.204.74.41:8500
address: 192.168.0.83:8500
# pass: jiahui8888
jaeger: #链路追踪
addr: 116.204.74.41:4318
addr: 192.168.0.83:4318
yidun:
# 回调模式开关: true=使用回调模式(需要公网地址), false=使用轮询模式
callback_mode: false
# 易盾回调 IP 白名单(逗号分隔),留空则不校验
callback_allowed_ips: ""
# 视频检测配置
video:
secret_id: "f58a38341ca6227014df7c3bf0e6f16f"
secret_key: "526aa631ba5d518aedeb70b5a3b67371"
region: "cn-hangzhou"
protocol: "https"
max_retry_count: 3
# 易盾回调地址(用于接收检测结果推送)
# 替换为实际可访问的地址
callback_url: "http://your-domain.com:3001/yidun/callback/receiveVideo"
# 图片检测配置
image:
business_id: "20acccc525cbc5cb6bed3d8f6f6b2f77"
secret_id: "9a82f90bfec61eb40d1c95605b894817"
secret_key: "f73a78954417a3713c36ec2d14eb2b5f"
region: "cn-hangzhou"
protocol: "https"
max_retry_count: 3
# 易盾回调地址(用于接收检测结果推送)
# 替换为实际可访问的地址
callback_url: "http://your-domain.com:3001/yidun/callback/receiveImage"
# 文本检测配置(如需要请补充)
text:
business_id: "YOUR_TEXT_BUSINESS_ID"
secret_id: "YOUR_TEXT_SECRET_ID"
secret_key: "YOUR_TEXT_SECRET_KEY"
region: "cn-hangzhou"
protocol: "https"
max_retry_count: 3
# 易盾回调地址(用于接收检测结果推送)
# 替换为实际可访问的地址
callback_url: "http://your-domain.com:3001/yidun/callback/receiveText"
# 内容送检定时任务配置
content_check:
# 是否启动定时送检任务(true=启动定时任务自动送检,false=不启动,仅通过API手动送检)
scheduler_enabled: false
# 每批处理数量
batch_size: 10
# 是否启用图片检测
image_enabled: false
# 是否启用视频检测
video_enabled: false
# 定时任务执行间隔(秒)
interval_seconds: 30
poll_interval: 60
-57
View File
@@ -1,57 +0,0 @@
package consts
// AdFormatType 广告格式类型枚举
type AdFormatType string
const (
AdFormatTypeBanner AdFormatType = "banner" // 横幅广告
AdFormatTypeVideo AdFormatType = "video" // 视频广告
AdFormatTypeNative AdFormatType = "native" // 原生广告
AdFormatTypeInterstitial AdFormatType = "interstitial" // 插屏广告
)
// GetAllAdFormatTypes 获取所有广告格式类型
func GetAllAdFormatTypes() []AdFormatType {
return []AdFormatType{
AdFormatTypeBanner,
AdFormatTypeVideo,
AdFormatTypeNative,
AdFormatTypeInterstitial,
}
}
type AdFormatTypeKeyValue struct {
Key AdFormatType
Value string
}
var (
AdFormatTypeBannerKeyValue = AdFormatTypeKeyValue{Key: AdFormatTypeBanner, Value: "横幅广告"}
AdFormatTypeVideoKeyValue = AdFormatTypeKeyValue{Key: AdFormatTypeVideo, Value: "视频广告"}
AdFormatTypeNativeKeyValue = AdFormatTypeKeyValue{Key: AdFormatTypeNative, Value: "原生广告"}
AdFormatTypeInterstitialKeyValue = AdFormatTypeKeyValue{Key: AdFormatTypeInterstitial, Value: "插屏广告"}
)
func GetAllAdFormatTypeKeyValue() []AdFormatTypeKeyValue {
return []AdFormatTypeKeyValue{
AdFormatTypeBannerKeyValue,
AdFormatTypeVideoKeyValue,
AdFormatTypeNativeKeyValue,
AdFormatTypeInterstitialKeyValue,
}
}
var adFormatTypeValueMap = map[AdFormatType]string{
AdFormatTypeBanner: AdFormatTypeBannerKeyValue.Value,
AdFormatTypeVideo: AdFormatTypeVideoKeyValue.Value,
AdFormatTypeNative: AdFormatTypeNativeKeyValue.Value,
AdFormatTypeInterstitial: AdFormatTypeInterstitialKeyValue.Value,
}
func GetAdFormatTypeValueByKey(key AdFormatType) (value string) {
value, exists := adFormatTypeValueMap[key]
if !exists {
value = "未知广告格式"
}
return
}
-52
View File
@@ -1,52 +0,0 @@
package consts
// AdSourceHealth 广告源健康状态枚举
type AdSourceHealth string
const (
AdSourceHealthHealthy AdSourceHealth = "healthy" // 健康
AdSourceHealthDegraded AdSourceHealth = "degraded" // 降级
AdSourceHealthUnhealthy AdSourceHealth = "unhealthy" // 不健康
)
// GetAllAdSourceHealths 获取所有广告源健康状态
func GetAllAdSourceHealths() []AdSourceHealth {
return []AdSourceHealth{
AdSourceHealthHealthy,
AdSourceHealthDegraded,
AdSourceHealthUnhealthy,
}
}
type AdSourceHealthKeyValue struct {
Key AdSourceHealth
Value string
}
var (
AdSourceHealthHealthyKeyValue = AdSourceHealthKeyValue{Key: AdSourceHealthHealthy, Value: "健康"}
AdSourceHealthDegradedKeyValue = AdSourceHealthKeyValue{Key: AdSourceHealthDegraded, Value: "降级"}
AdSourceHealthUnhealthyKeyValue = AdSourceHealthKeyValue{Key: AdSourceHealthUnhealthy, Value: "不健康"}
)
func GetAllAdSourceHealthKeyValue() []AdSourceHealthKeyValue {
return []AdSourceHealthKeyValue{
AdSourceHealthHealthyKeyValue,
AdSourceHealthDegradedKeyValue,
AdSourceHealthUnhealthyKeyValue,
}
}
var adSourceHealthValueMap = map[AdSourceHealth]string{
AdSourceHealthHealthy: AdSourceHealthHealthyKeyValue.Value,
AdSourceHealthDegraded: AdSourceHealthDegradedKeyValue.Value,
AdSourceHealthUnhealthy: AdSourceHealthUnhealthyKeyValue.Value,
}
func GetAdSourceHealthValueByKey(key AdSourceHealth) (value string) {
value, exists := adSourceHealthValueMap[key]
if !exists {
value = "未知健康状态"
}
return
}
-57
View File
@@ -1,57 +0,0 @@
package consts
// AdSourceProvider 广告源提供商枚举
type AdSourceProvider string
const (
AdSourceProviderGoogle AdSourceProvider = "google" // Google
AdSourceProviderBaidu AdSourceProvider = "baidu" // 百度
AdSourceProviderTencent AdSourceProvider = "tencent" // 腾讯
AdSourceProviderSelf AdSourceProvider = "self" // 自营
)
// GetAllAdSourceProviders 获取所有广告源提供商
func GetAllAdSourceProviders() []AdSourceProvider {
return []AdSourceProvider{
AdSourceProviderGoogle,
AdSourceProviderBaidu,
AdSourceProviderTencent,
AdSourceProviderSelf,
}
}
type AdSourceProviderKeyValue struct {
Key AdSourceProvider
Value string
}
var (
AdSourceProviderGoogleKeyValue = AdSourceProviderKeyValue{Key: AdSourceProviderGoogle, Value: "Google"}
AdSourceProviderBaiduKeyValue = AdSourceProviderKeyValue{Key: AdSourceProviderBaidu, Value: "百度"}
AdSourceProviderTencentKeyValue = AdSourceProviderKeyValue{Key: AdSourceProviderTencent, Value: "腾讯"}
AdSourceProviderSelfKeyValue = AdSourceProviderKeyValue{Key: AdSourceProviderSelf, Value: "自营"}
)
func GetAllAdSourceProviderKeyValue() []AdSourceProviderKeyValue {
return []AdSourceProviderKeyValue{
AdSourceProviderGoogleKeyValue,
AdSourceProviderBaiduKeyValue,
AdSourceProviderTencentKeyValue,
AdSourceProviderSelfKeyValue,
}
}
var adSourceProviderValueMap = map[AdSourceProvider]string{
AdSourceProviderGoogle: AdSourceProviderGoogleKeyValue.Value,
AdSourceProviderBaidu: AdSourceProviderBaiduKeyValue.Value,
AdSourceProviderTencent: AdSourceProviderTencentKeyValue.Value,
AdSourceProviderSelf: AdSourceProviderSelfKeyValue.Value,
}
func GetAdSourceProviderValueByKey(key AdSourceProvider) (value string) {
value, exists := adSourceProviderValueMap[key]
if !exists {
value = "未知提供商"
}
return
}
-52
View File
@@ -1,52 +0,0 @@
package consts
// AdSourceStatus 广告源状态枚举
type AdSourceStatus string
const (
AdSourceStatusActive AdSourceStatus = "active" // 活跃
AdSourceStatusInactive AdSourceStatus = "inactive" // 非活跃
AdSourceStatusMaintenance AdSourceStatus = "maintenance" // 维护中
)
// GetAllAdSourceStatuses 获取所有广告源状态
func GetAllAdSourceStatuses() []AdSourceStatus {
return []AdSourceStatus{
AdSourceStatusActive,
AdSourceStatusInactive,
AdSourceStatusMaintenance,
}
}
type AdSourceStatusKeyValue struct {
Key AdSourceStatus
Value string
}
var (
AdSourceStatusActiveKeyValue = AdSourceStatusKeyValue{Key: AdSourceStatusActive, Value: "活跃"}
AdSourceStatusInactiveKeyValue = AdSourceStatusKeyValue{Key: AdSourceStatusInactive, Value: "非活跃"}
AdSourceStatusMaintenanceKeyValue = AdSourceStatusKeyValue{Key: AdSourceStatusMaintenance, Value: "维护中"}
)
func GetAllAdSourceStatusKeyValue() []AdSourceStatusKeyValue {
return []AdSourceStatusKeyValue{
AdSourceStatusActiveKeyValue,
AdSourceStatusInactiveKeyValue,
AdSourceStatusMaintenanceKeyValue,
}
}
var adSourceStatusValueMap = map[AdSourceStatus]string{
AdSourceStatusActive: AdSourceStatusActiveKeyValue.Value,
AdSourceStatusInactive: AdSourceStatusInactiveKeyValue.Value,
AdSourceStatusMaintenance: AdSourceStatusMaintenanceKeyValue.Value,
}
func GetAdSourceStatusValueByKey(key AdSourceStatus) (value string) {
value, exists := adSourceStatusValueMap[key]
if !exists {
value = "未知状态"
}
return
}
-52
View File
@@ -1,52 +0,0 @@
package consts
// AdSourceType 广告源类型枚举
type AdSourceType string
const (
AdSourceTypeSelf AdSourceType = "self" // 自营
AdSourceTypeThirdParty AdSourceType = "third_party" // 第三方
AdSourceTypeExchange AdSourceType = "exchange" // 广告交易平台
)
// GetAllAdSourceTypes 获取所有广告源类型
func GetAllAdSourceTypes() []AdSourceType {
return []AdSourceType{
AdSourceTypeSelf,
AdSourceTypeThirdParty,
AdSourceTypeExchange,
}
}
type AdSourceTypeKeyValue struct {
Key AdSourceType
Value string
}
var (
AdSourceTypeSelfKeyValue = AdSourceTypeKeyValue{Key: AdSourceTypeSelf, Value: "自营"}
AdSourceTypeThirdPartyKeyValue = AdSourceTypeKeyValue{Key: AdSourceTypeThirdParty, Value: "第三方"}
AdSourceTypeExchangeKeyValue = AdSourceTypeKeyValue{Key: AdSourceTypeExchange, Value: "广告交易平台"}
)
func GetAllAdSourceTypeKeyValue() []AdSourceTypeKeyValue {
return []AdSourceTypeKeyValue{
AdSourceTypeSelfKeyValue,
AdSourceTypeThirdPartyKeyValue,
AdSourceTypeExchangeKeyValue,
}
}
var adSourceTypeValueMap = map[AdSourceType]string{
AdSourceTypeSelf: AdSourceTypeSelfKeyValue.Value,
AdSourceTypeThirdParty: AdSourceTypeThirdPartyKeyValue.Value,
AdSourceTypeExchange: AdSourceTypeExchangeKeyValue.Value,
}
func GetAdSourceTypeValueByKey(key AdSourceType) (value string) {
value, exists := adSourceTypeValueMap[key]
if !exists {
value = "未知类型"
}
return
}
-52
View File
@@ -1,52 +0,0 @@
package consts
// AuthType 认证类型枚举
type AuthType string
const (
AuthTypeAPIKey AuthType = "api_key" // API密钥
AuthTypeOAuth AuthType = "oauth" // OAuth
AuthTypeBasic AuthType = "basic" // Basic认证
)
// GetAllAuthTypes 获取所有认证类型
func GetAllAuthTypes() []AuthType {
return []AuthType{
AuthTypeAPIKey,
AuthTypeOAuth,
AuthTypeBasic,
}
}
type AuthTypeKeyValue struct {
Key AuthType
Value string
}
var (
AuthTypeAPIKeyKeyValue = AuthTypeKeyValue{Key: AuthTypeAPIKey, Value: "API密钥"}
AuthTypeOAuthKeyValue = AuthTypeKeyValue{Key: AuthTypeOAuth, Value: "OAuth"}
AuthTypeBasicKeyValue = AuthTypeKeyValue{Key: AuthTypeBasic, Value: "Basic认证"}
)
func GetAllAuthTypeKeyValue() []AuthTypeKeyValue {
return []AuthTypeKeyValue{
AuthTypeAPIKeyKeyValue,
AuthTypeOAuthKeyValue,
AuthTypeBasicKeyValue,
}
}
var authTypeValueMap = map[AuthType]string{
AuthTypeAPIKey: AuthTypeAPIKeyKeyValue.Value,
AuthTypeOAuth: AuthTypeOAuthKeyValue.Value,
AuthTypeBasic: AuthTypeBasicKeyValue.Value,
}
func GetAuthTypeValueByKey(key AuthType) (value string) {
value, exists := authTypeValueMap[key]
if !exists {
value = "未知认证类型"
}
return
}
-57
View File
@@ -1,57 +0,0 @@
package consts
// BiddingType 竞价类型枚举
type BiddingType string
const (
BiddingTypeCPM BiddingType = "cpm" // 千次展示成本
BiddingTypeCPC BiddingType = "cpc" // 每次点击成本
BiddingTypeCPA BiddingType = "cpa" // 每次行动成本
BiddingTypeRTB BiddingType = "rtb" // 实时竞价
)
// GetAllBiddingTypes 获取所有竞价类型
func GetAllBiddingTypes() []BiddingType {
return []BiddingType{
BiddingTypeCPM,
BiddingTypeCPC,
BiddingTypeCPA,
BiddingTypeRTB,
}
}
type BiddingTypeKeyValue struct {
Key BiddingType
Value string
}
var (
BiddingTypeCPMKeyValue = BiddingTypeKeyValue{Key: BiddingTypeCPM, Value: "千次展示成本"}
BiddingTypeCPCKeypValue = BiddingTypeKeyValue{Key: BiddingTypeCPC, Value: "每次点击成本"}
BiddingTypeCPAKeyValue = BiddingTypeKeyValue{Key: BiddingTypeCPA, Value: "每次行动成本"}
BiddingTypeRTBKeyValue = BiddingTypeKeyValue{Key: BiddingTypeRTB, Value: "实时竞价"}
)
func GetAllBiddingTypeKeyValue() []BiddingTypeKeyValue {
return []BiddingTypeKeyValue{
BiddingTypeCPMKeyValue,
BiddingTypeCPCKeypValue,
BiddingTypeCPAKeyValue,
BiddingTypeRTBKeyValue,
}
}
var biddingTypeValueMap = map[BiddingType]string{
BiddingTypeCPM: BiddingTypeCPMKeyValue.Value,
BiddingTypeCPC: BiddingTypeCPCKeypValue.Value,
BiddingTypeCPA: BiddingTypeCPAKeyValue.Value,
BiddingTypeRTB: BiddingTypeRTBKeyValue.Value,
}
func GetBiddingTypeValueByKey(key BiddingType) (value string) {
value, exists := biddingTypeValueMap[key]
if !exists {
value = "未知竞价类型"
}
return
}
-57
View File
@@ -1,57 +0,0 @@
package consts
// BillingModel 计费模式枚举
type BillingModel string
const (
BillingModelCPM BillingModel = "cpm" // 千次展示成本
BillingModelCPC BillingModel = "cpc" // 每次点击成本
BillingModelCPA BillingModel = "cpa" // 每次行动成本
BillingModelRevShare BillingModel = "rev_share" // 收入分成
)
// GetAllBillingModels 获取所有计费模式
func GetAllBillingModels() []BillingModel {
return []BillingModel{
BillingModelCPM,
BillingModelCPC,
BillingModelCPA,
BillingModelRevShare,
}
}
type BillingModelKeyValue struct {
Key BillingModel
Value string
}
var (
BillingModelCPMKeyValue = BillingModelKeyValue{Key: BillingModelCPM, Value: "千次展示成本"}
BillingModelCPCKeypValue = BillingModelKeyValue{Key: BillingModelCPC, Value: "每次点击成本"}
BillingModelCPAKeyValue = BillingModelKeyValue{Key: BillingModelCPA, Value: "每次行动成本"}
BillingModelRevShareKeyValue = BillingModelKeyValue{Key: BillingModelRevShare, Value: "收入分成"}
)
func GetAllBillingModelKeyValue() []BillingModelKeyValue {
return []BillingModelKeyValue{
BillingModelCPMKeyValue,
BillingModelCPCKeypValue,
BillingModelCPAKeyValue,
BillingModelRevShareKeyValue,
}
}
var billingModelValueMap = map[BillingModel]string{
BillingModelCPM: BillingModelCPMKeyValue.Value,
BillingModelCPC: BillingModelCPCKeypValue.Value,
BillingModelCPA: BillingModelCPAKeyValue.Value,
BillingModelRevShare: BillingModelRevShareKeyValue.Value,
}
func GetBillingModelValueByKey(key BillingModel) (value string) {
value, exists := billingModelValueMap[key]
if !exists {
value = "未知计费模式"
}
return
}
+22
View File
@@ -0,0 +1,22 @@
package check
// 送检状态常量
const (
// SourceTable 来源表标识
SourceTableTencentImage = "tencent_image"
SourceTableTencentVideo = "tencent_video"
// CheckStatus 送检状态
CheckStatusPending = "PENDING" // 待送检
CheckStatusSubmitting = "SUBMITTING" // 送检中
CheckStatusSuccess = "SUCCESS" // 送检成功
CheckStatusFailed = "FAILED" // 送检失败
CheckStatusCompleted = "COMPLETED" // 检测完成
)
// Suggestion 处置建议
const (
SuggestionPass = 0 // 通过
SuggestionReview = 1 // 嫌疑,需人工审核
SuggestionBlock = 2 // 不通过
)
+9
View File
@@ -0,0 +1,9 @@
package check
// PostgreSQL表名常量
const (
TencentImageTable = "tencent_image" // 图片送检表
TencentVideoTable = "tencent_video" // 视频送检表
TencentContentCheckLogTable = "tencent_content_check_log" // 送检日志表
TencentAccountRelationTable = "tencent_account_relation" // 腾讯广告账户关系表
)
-18
View File
@@ -1,18 +0,0 @@
package consts
// MongoDB集合名称常量
const (
AdPositionCollection = "ad_position" // 广告位集合
AdSourceCollection = "ad_source" // 广告源集合
AdvertisementCollection = "advertisement" // 广告集合
AdvertiserCollection = "advertiser" // 广告主集合
ApplicationCollection = "application" // 应用集合
CidRequestCollection = "cid_request" // CID请求集合
StrategyCollection = "strategy" // 策略集合
AdCreativeCollection = "ad_creative" // 广告创意集合
AdPlatformCollection = "ad_platform" // 广告平台集合
AdTypeCollection = "ad_type" // 广告类型集合
AppPlatformConfigCollection = "app_platform_config" // 应用平台配置集合
PlatformDeliveryRuleCollection = "platform_delivery_rule" // 平台投放规则集合
TargetingCollection = "targeting" // 定向规则集合
)
-19
View File
@@ -1,19 +0,0 @@
package consts
// 默认配置值
const (
DefaultTimeout = 5000 // 默认超时时间(毫秒)
DefaultRetryCount = 3 // 默认重试次数
DefaultPriority = 1 // 默认优先级
)
// 错误消息
const (
ErrAdSourceNotFound = "广告源不存在"
ErrAdSourceNameExists = "广告源名称已存在"
ErrAdSourceCodeExists = "广告源编码已存在"
ErrAdSourceInactive = "广告源非活跃状态"
ErrAdSourceUnhealthy = "广告源健康状态异常"
ErrRateLimitExceeded = "请求频率超限"
ErrInvalidConfiguration = "配置参数无效"
)
-16
View File
@@ -1,16 +0,0 @@
package consts
// 注意:以下枚举常量已迁移到单独的枚举文件中,请使用新的枚举类型:
// - AdSourceStatus -> consts.AdSourceStatus (ad_source_status.go)
// - AdSourceHealth -> consts.AdSourceHealth (ad_source_health.go)
// - AdSourceType -> consts.AdSourceType (ad_source_type.go)
// - AdSourceProvider -> consts.AdSourceProvider (ad_source_provider.go)
// - AuthType -> consts.AuthType (auth_type.go)
// - BiddingType -> consts.BiddingType (bidding_type.go)
// - AdFormatType -> consts.AdFormatType (ad_format_type.go)
// - BillingModel -> consts.BillingModel (billing_model.go)
// - PaymentTerms -> consts.PaymentTerms (payment_terms.go)
// 配置值常量已迁移到 config.go 文件中
// 错误消息常量已迁移到 config.go 文件中
// MongoDB集合名称常量已迁移到 collections.go 文件中
-56
View File
@@ -1,56 +0,0 @@
package consts
import "errors"
// 广告管理错误码
const (
ErrAdNotFound = 1001 // 广告不存在
ErrAdStatusInvalid = 1002 // 广告状态无效
ErrAdAuditedRejected = 1003 // 广告审核被拒绝
ErrAdBudgetInsufficient = 1004 // 广告预算不足
)
// 广告主管理错误码
const (
ErrAdvertiserNotFound = 2001 // 广告主不存在
ErrAdvertiserStatusInvalid = 2002 // 广告主状态无效
ErrAdvertiserAuditedRejected = 2003 // 广告主审核被拒绝
ErrAdvertiserBalanceLow = 2004 // 广告主余额不足
ErrCreditLimitInvalid = 2005 // 授信额度无效
)
// 广告位管理错误码
const (
ErrAdPositionNotFound = 3001 // 广告位不存在
ErrAdPositionStatusInvalid = 3002 // 广告位状态无效
ErrAdPositionCodeExists = 3003 // 广告位编码已存在
ErrAdNotMatched = 3004 // 无匹配广告
)
// 报表管理错误码
const (
ErrReportNotFound = 4001 // 报表不存在
ErrReportNotGenerated = 4002 // 报表未生成
ErrReportExpired = 4003 // 报表已过期
ErrReportInvalidFormat = 4004 // 报表格式无效
)
// 配置验证错误
var (
ErrInvalidPriority = errors.New("优先级必须为非负数")
ErrInvalidWeight = errors.New("权重必须在0到1之间")
ErrInvalidBidAmount = errors.New("出价金额必须为非负数")
ErrInvalidBidRange = errors.New("最小出价不能大于最大出价")
ErrInvalidROAS = errors.New("ROAS必须为非负数")
ErrInvalidBudget = errors.New("预算金额必须为非负数")
ErrInvalidTimeRange = errors.New("开始时间不能大于结束时间")
ErrInvalidTimeout = errors.New("超时时间必须为正数")
ErrInvalidRetryCount = errors.New("重试次数必须为非负数")
ErrInvalidFileSize = errors.New("文件大小必须为正数")
ErrInvalidDuration = errors.New("时长必须为正数")
ErrInvalidRateLimit = errors.New("速率限制必须为正数")
ErrInvalidCommission = errors.New("佣金比例必须在0到1之间")
ErrInvalidRevShare = errors.New("收入分成比例必须在0到1之间")
ErrInvalidAge = errors.New("年龄必须为正数且最小年龄不能大于最大年龄")
ErrInvalidFrequency = errors.New("频次限制必须为非负数")
)
-52
View File
@@ -1,52 +0,0 @@
package consts
// PaymentTerms 支付条款枚举
type PaymentTerms string
const (
PaymentTermsNet30 PaymentTerms = "net_30" // 30天
PaymentTermsNet60 PaymentTerms = "net_60" // 60天
PaymentTermsNet90 PaymentTerms = "net_90" // 90天
)
// GetAllPaymentTerms 获取所有支付条款
func GetAllPaymentTerms() []PaymentTerms {
return []PaymentTerms{
PaymentTermsNet30,
PaymentTermsNet60,
PaymentTermsNet90,
}
}
type PaymentTermsKeyValue struct {
Key PaymentTerms
Value string
}
var (
PaymentTermsNet30KeyValue = PaymentTermsKeyValue{Key: PaymentTermsNet30, Value: "30天"}
PaymentTermsNet60KeyValue = PaymentTermsKeyValue{Key: PaymentTermsNet60, Value: "60天"}
PaymentTermsNet90KeyValue = PaymentTermsKeyValue{Key: PaymentTermsNet90, Value: "90天"}
)
func GetAllPaymentTermsKeyValue() []PaymentTermsKeyValue {
return []PaymentTermsKeyValue{
PaymentTermsNet30KeyValue,
PaymentTermsNet60KeyValue,
PaymentTermsNet90KeyValue,
}
}
var paymentTermsValueMap = map[PaymentTerms]string{
PaymentTermsNet30: PaymentTermsNet30KeyValue.Value,
PaymentTermsNet60: PaymentTermsNet60KeyValue.Value,
PaymentTermsNet90: PaymentTermsNet90KeyValue.Value,
}
func GetPaymentTermsValueByKey(key PaymentTerms) (value string) {
value, exists := paymentTermsValueMap[key]
if !exists {
value = "未知支付条款"
}
return
}
-31
View File
@@ -1,31 +0,0 @@
package consts
const (
AdRequestLimitKeyPrefix = "ad_request_limit:" // 广告请求限流键前缀
RateLimitKeyPrefix = "rate_limit:" // 通用限流键前缀
SessionCacheKeyPrefix = "session_cache:" // 会话缓存键前缀
)
// 广告缓存键
const (
AdCacheKeyPrefix = "cid:ad:" // 广告缓存前缀
AdPositionCacheKeyPrefix = "cid:pos:" // 广告位缓存前缀
AdvertiserCacheKeyPrefix = "cid:adv:" // 广告主缓存前缀
)
// 广告匹配键
const (
AdMatchingKeyPrefix = "cid:match:" // 广告匹配键前缀
UserProfileKeyPrefix = "cid:user:" // 用户画像键前缀
)
// 限流键
const (
ApiRequestLimitKeyPrefix = "cid:limit:api:" // API请求限流键前缀
)
// Stream键
const (
AdEventStreamKey = "cid:stream:ad_event" // 广告事件流
UserBehaviorStreamKey = "cid:stream:user_behavior" // 用户行为流
)
-65
View File
@@ -1,65 +0,0 @@
package controller
import (
"context"
"cid/model/dto"
"cid/service"
"gitea.com/red-future/common/beans"
)
type adPosition struct{}
var AdPosition = new(adPosition)
// Add 添加广告位
func (c *adPosition) Add(ctx context.Context, req *dto.AddAdPositionReq) (res *dto.AddAdPositionRes, err error) {
return service.AdPosition.Add(ctx, req)
}
// Update 更新广告位
func (c *adPosition) Update(ctx context.Context, req *dto.UpdateAdPositionReq) (res *beans.ResponseEmpty, err error) {
err = service.AdPosition.Update(ctx, req)
return
}
// UpdateStatus 更新广告位状态
func (c *adPosition) UpdateStatus(ctx context.Context, req *dto.UpdateAdPositionStatusReq) (res *beans.ResponseEmpty, err error) {
err = service.AdPosition.UpdateStatus(ctx, req)
return
}
// GetOne 获取广告位详情
func (c *adPosition) GetOne(ctx context.Context, req *dto.GetAdPositionReq) (res *dto.GetAdPositionRes, err error) {
return service.AdPosition.GetOne(ctx, req)
}
// List 获取广告位列表
func (c *adPosition) List(ctx context.Context, req *dto.ListAdPositionReq) (res *dto.ListAdPositionRes, err error) {
return service.AdPosition.List(ctx, req)
}
// GetAvailableAdPositions 获取可用的广告位列表
func (c *adPosition) GetAvailableAdPositions(ctx context.Context, _ *dto.GetAvailableAdPositionsReq) (res *dto.GetAvailableAdPositionsRes, err error) {
list, err := service.AdPosition.GetAvailableAdPositions(ctx)
if err != nil {
return nil, err
}
return &dto.GetAvailableAdPositionsRes{
List: list,
}, nil
}
// MatchAd 匹配广告
func (c *adPosition) MatchAd(ctx context.Context, req *dto.MatchAdReq) (res *dto.MatchAdRes, err error) {
ad, err := service.AdPosition.MatchAd(ctx, req.PositionCode, req.UserInfo)
if err != nil {
return nil, err
}
return &dto.MatchAdRes{
Advertisement: ad,
}, nil
}
-94
View File
@@ -1,94 +0,0 @@
package controller
import (
"context"
"cid/model/dto"
"cid/service"
"github.com/gogf/gf/v2/errors/gerror"
)
var AdSource = new(adSource)
type adSource struct{}
// Create 创建广告源
func (c *adSource) Create(ctx context.Context, req *dto.CreateAdSourceReq) (res *dto.GetAdSourceRes, err error) {
id, err := service.AdSource.CreateAdSource(ctx, req)
if err != nil {
return nil, err
}
adSource, err := service.AdSource.GetAdSourceByID(ctx, id)
if err != nil {
return nil, err
}
return &dto.GetAdSourceRes{
AdSource: adSource,
}, nil
}
// Update 更新广告源
func (c *adSource) Update(ctx context.Context, req *dto.UpdateAdSourceReq) (res *dto.GetAdSourceRes, err error) {
affected, err := service.AdSource.UpdateAdSource(ctx, req.Id, req)
if err != nil {
return nil, err
}
if affected == 0 {
return nil, gerror.New("广告源更新失败")
}
adSource, err := service.AdSource.GetAdSourceByID(ctx, req.Id)
if err != nil {
return nil, err
}
return &dto.GetAdSourceRes{
AdSource: adSource,
}, nil
}
// Delete 删除广告源
func (c *adSource) Delete(ctx context.Context, req *dto.DeleteAdSourceReq) (res *dto.DeleteAdSourceRes, err error) {
affected, err := service.AdSource.DeleteAdSource(ctx, req.Id)
if err != nil {
return nil, err
}
if affected == 0 {
return nil, gerror.New("广告源删除失败")
}
return &dto.DeleteAdSourceRes{
Success: true,
}, nil
}
// GetByID 根据ID获取广告源
func (c *adSource) GetByID(ctx context.Context, req *dto.GetAdSourceReq) (res *dto.GetAdSourceRes, err error) {
adSource, err := service.AdSource.GetAdSourceByID(ctx, req.Id)
if err != nil {
return nil, err
}
if adSource == nil {
return nil, gerror.New("广告源不存在")
}
return &dto.GetAdSourceRes{
AdSource: adSource,
}, nil
}
// List 获取广告源列表
func (c *adSource) List(ctx context.Context, req *dto.ListAdSourceReq) (res *dto.ListAdSourceRes, err error) {
adSources, err := service.AdSource.GetAvailableSources(ctx)
if err != nil {
return nil, err
}
return &dto.ListAdSourceRes{
List: adSources,
Total: len(adSources),
}, nil
}
-46
View File
@@ -1,46 +0,0 @@
package controller
import (
"cid/model/dto"
"cid/service"
"context"
"gitea.com/red-future/common/beans"
)
type advertisement struct{}
var Advertisement = new(advertisement)
// Add 添加广告
func (c *advertisement) Add(ctx context.Context, req *dto.AddAdvertisementReq) (res *dto.AddAdvertisementRes, err error) {
return service.Advertisement.Add(ctx, req)
}
// Update 更新广告
func (c *advertisement) Update(ctx context.Context, req *dto.UpdateAdvertisementReq) (res *beans.ResponseEmpty, err error) {
err = service.Advertisement.Update(ctx, req)
return
}
// UpdateStatus 更新广告状态
func (c *advertisement) UpdateStatus(ctx context.Context, req *dto.UpdateAdStatusReq) (res *beans.ResponseEmpty, err error) {
err = service.Advertisement.UpdateStatus(ctx, req)
return
}
// Audit 审核广告
func (c *advertisement) Audit(ctx context.Context, req *dto.AuditAdvertisementReq) (res *beans.ResponseEmpty, err error) {
err = service.Advertisement.Audit(ctx, req)
return
}
// GetOne 获取广告详情
func (c *advertisement) GetOne(ctx context.Context, req *dto.GetAdvertisementReq) (res *dto.GetAdvertisementRes, err error) {
return service.Advertisement.GetOne(ctx, req)
}
// List 获取广告列表
func (c *advertisement) List(ctx context.Context, req *dto.ListAdvertisementReq) (res *dto.ListAdvertisementRes, err error) {
return service.Advertisement.List(ctx, req)
}
-71
View File
@@ -1,71 +0,0 @@
package controller
import (
"cid/model/dto"
"cid/service"
"context"
"gitea.com/red-future/common/beans"
)
type advertiser struct{}
var Advertiser = new(advertiser)
// Add 添加广告主
func (c *advertiser) Add(ctx context.Context, req *dto.AddAdvertiserReq) (res *dto.AddAdvertiserRes, err error) {
return service.Advertiser.Add(ctx, req)
}
// Update 更新广告主
func (c *advertiser) Update(ctx context.Context, req *dto.UpdateAdvertiserReq) (res *beans.ResponseEmpty, err error) {
err = service.Advertiser.Update(ctx, req)
return
}
// UpdateStatus 更新广告主状态
func (c *advertiser) UpdateStatus(ctx context.Context, req *dto.UpdateAdvertiserStatusReq) (res *beans.ResponseEmpty, err error) {
err = service.Advertiser.UpdateStatus(ctx, req)
return
}
// Audit 审核广告主
func (c *advertiser) Audit(ctx context.Context, req *dto.AuditAdvertiserReq) (res *beans.ResponseEmpty, err error) {
err = service.Advertiser.Audit(ctx, req)
return
}
// Recharge 充值
func (c *advertiser) Recharge(ctx context.Context, req *dto.RechargeAdvertiserReq) (res *beans.ResponseEmpty, err error) {
err = service.Advertiser.Recharge(ctx, req)
return
}
// UpdateCreditLimit 更新授信额度
func (c *advertiser) UpdateCreditLimit(ctx context.Context, req *dto.UpdateCreditLimitReq) (res *beans.ResponseEmpty, err error) {
err = service.Advertiser.UpdateCreditLimit(ctx, req)
return
}
// GetOne 获取广告主详情
func (c *advertiser) GetOne(ctx context.Context, req *dto.GetAdvertiserReq) (res *dto.GetAdvertiserRes, err error) {
return service.Advertiser.GetOne(ctx, req)
}
// List 获取广告主列表
func (c *advertiser) List(ctx context.Context, req *dto.ListAdvertiserReq) (res *dto.ListAdvertiserRes, err error) {
return service.Advertiser.List(ctx, req)
}
// GetBalance 获取广告主余额
func (c *advertiser) GetBalance(ctx context.Context, req *dto.GetAdvertiserBalanceReq) (res *dto.GetAdvertiserBalanceRes, err error) {
balance, creditLimit, err := service.Advertiser.GetBalance(ctx, req.Id)
if err != nil {
return nil, err
}
return &dto.GetAdvertiserBalanceRes{
Balance: balance,
CreditLimit: creditLimit,
}, nil
}
-159
View File
@@ -1,159 +0,0 @@
package controller
import (
"context"
"strconv"
"cid/model/dto"
"cid/service"
)
var Application = new(application)
type application struct{}
// CreateApplication 创建应用
func (c *application) CreateApplication(ctx context.Context, req *dto.CreateApplicationReq) (res *dto.CreateApplicationRes, err error) {
idStr, err := service.Application.CreateApplication(ctx, req)
if err != nil {
return nil, err
}
// 将字符串ID转换为int64
id, _ := strconv.ParseInt(idStr, 10, 64)
return &dto.CreateApplicationRes{
ID: id,
}, nil
}
// UpdateApplication 更新应用
func (c *application) UpdateApplication(ctx context.Context, req *dto.UpdateApplicationReq) (res *dto.UpdateApplicationRes, err error) {
affected, err := service.Application.UpdateApplication(ctx, strconv.FormatInt(req.ID, 10), req)
if err != nil {
return nil, err
}
return &dto.UpdateApplicationRes{
Success: affected > 0,
}, nil
}
// GetApplication 获取应用信息
func (c *application) GetApplication(ctx context.Context, req *dto.GetApplicationReq) (res *dto.GetApplicationRes, err error) {
app, err := service.Application.GetApplicationByID(ctx, strconv.FormatInt(req.ID, 10))
if err != nil {
return nil, err
}
// 将ObjectId的十六进制字符串转换为int64,如果失败则使用0
id, _ := strconv.ParseInt(app.Id.Hex(), 16, 64)
// Application实体中没有TenantId字段,暂时设为0
tenantID := int64(0)
return &dto.GetApplicationRes{
ID: id,
TenantID: tenantID,
Name: app.Name,
Code: app.Code,
Description: app.Description,
Platform: app.Platform,
PackageName: app.PackageName,
AppStoreURL: app.AppStoreURL,
Categories: app.Categories,
Tags: app.Tags,
AdTypes: app.AdTypes,
Status: app.Status,
AppKey: app.AppKey,
CallbackURL: app.CallbackURL,
CreatedAt: app.CreatedAt.Unix(),
UpdatedAt: app.UpdatedAt.Unix(),
}, nil
}
// ListApplications 获取应用列表
func (c *application) ListApplications(ctx context.Context, req *dto.ListApplicationsReq) (res *dto.ListApplicationsRes, err error) {
list, total, err := service.Application.GetApplicationsByTenant(ctx, strconv.FormatInt(req.TenantID, 10), req.Platform, req.Status, req.Page, req.Size)
if err != nil {
return nil, err
}
// 转换为响应格式
appItems := make([]dto.ApplicationItem, len(list))
for i, app := range list {
id, _ := strconv.ParseInt(app.Id.Hex(), 16, 64)
appItems[i] = dto.ApplicationItem{
ID: id,
Name: app.Name,
Code: app.Code,
Description: app.Description,
Platform: app.Platform,
PackageName: app.PackageName,
Categories: app.Categories,
Tags: app.Tags,
AdTypes: app.AdTypes,
Status: app.Status,
DailyRequests: app.DailyRequests,
MonthlyRequests: app.MonthlyRequests,
CreatedAt: app.CreatedAt.Unix(),
}
}
return &dto.ListApplicationsRes{
List: appItems,
Total: total,
Page: req.Page,
Size: req.Size,
}, nil
}
// ResetAPIKeys 重置API密钥
func (c *application) ResetAPIKeys(ctx context.Context, req *dto.ResetAPIKeysReq) (res *dto.ResetAPIKeysRes, err error) {
appKey, appSecret, err := service.Application.ResetAPIKeys(ctx, strconv.FormatInt(req.ID, 10))
if err != nil {
return nil, err
}
return &dto.ResetAPIKeysRes{
AppKey: appKey,
AppSecret: appSecret,
}, nil
}
// ValidateApplication 验证应用权限
func (c *application) ValidateApplication(ctx context.Context, req *dto.ValidateApplicationReq) (res *dto.ValidateApplicationRes, err error) {
app, err := service.Application.ValidateApplication(ctx, req.AppKey, req.AppSecret)
if err != nil {
return &dto.ValidateApplicationRes{
Valid: false,
}, nil
}
// 将ObjectId的十六进制字符串转换为int64,如果失败则使用0
appID, _ := strconv.ParseInt(app.Id.Hex(), 16, 64)
// Application实体中没有TenantId字段,暂时设为0
tenantID := int64(0)
tentantName := ""
return &dto.ValidateApplicationRes{
Valid: true,
AppID: appID,
AppName: app.Name,
TenantID: tenantID,
TenantName: tentantName,
Platform: app.Platform,
AdTypes: app.AdTypes,
}, nil
}
// DeleteApplication 删除应用
func (c *application) DeleteApplication(ctx context.Context, req *dto.DeleteApplicationReq) (res *dto.DeleteApplicationRes, err error) {
affected, err := service.Application.DeleteApplication(ctx, strconv.FormatInt(req.ID, 10))
if err != nil {
return nil, err
}
return &dto.DeleteApplicationRes{
Success: affected > 0,
}, nil
}
@@ -0,0 +1,176 @@
package check
import (
dto "cid/model/dto/check"
check2 "cid/service/check"
"context"
"fmt"
"gitea.redpowerfuture.com/red-future/common/beans"
)
// ContentCheckController 内容送检控制器
type ContentCheckController struct{}
// ContentCheck 内容送检控制器单例
var ContentCheck = new(ContentCheckController)
// StatusRes 状态响应
type StatusRes struct {
Running bool `json:"running"`
Config check2.ContentCheckConfig `json:"config"`
PendingStats map[string]int `json:"pending_stats"`
}
// Start 启动送检服务
func (c *ContentCheckController) Start(ctx context.Context, req *dto.StartCheckReq) (res *beans.ResponseEmpty, err error) {
ctx = WithAdminUser(ctx)
if check2.TencentContentCheck.IsRunning() {
return &beans.ResponseEmpty{}, nil
}
// 如果有配置参数,更新配置
if req.BatchSize > 0 || req.IntervalSeconds > 0 {
config := check2.ContentCheckConfig{
BatchSize: req.BatchSize,
ImageEnabled: req.ImageEnabled,
VideoEnabled: req.VideoEnabled,
IntervalSeconds: req.IntervalSeconds,
}
check2.TencentContentCheck.SetConfig(config)
}
err = check2.TencentContentCheck.Start(ctx)
if err != nil {
return nil, err
}
return &beans.ResponseEmpty{}, nil
}
// Stop 停止送检服务
func (c *ContentCheckController) Stop(ctx context.Context, req *dto.EmptyReq) (res *beans.ResponseEmpty, err error) {
ctx = WithAdminUser(ctx)
check2.TencentContentCheck.Stop(ctx)
return
}
// Status 获取送检服务状态
func (c *ContentCheckController) Status(ctx context.Context, req *dto.EmptyReq) (res *StatusRes, err error) {
ctx = WithAdminUser(ctx)
res = &StatusRes{
Running: check2.TencentContentCheck.IsRunning(),
Config: check2.TencentContentCheck.GetConfig(),
PendingStats: check2.TencentContentCheck.GetPendingStats(ctx),
}
return
}
// ProcessImageCallback 处理图片检测回调
func (c *ContentCheckController) ProcessImageCallback(ctx context.Context, req *dto.ProcessImageCallbackReq) (res *beans.ResponseEmpty, err error) {
ctx = WithAdminUser(ctx)
if req.CallbackData == "" {
return nil, fmt.Errorf("callbackData不能为空")
}
err = check2.TencentContentCallback.ProcessImageCallback(ctx, req.CallbackData)
return
}
// ProcessVideoCallback 处理视频检测回调
func (c *ContentCheckController) ProcessVideoCallback(ctx context.Context, req *dto.ProcessVideoCallbackReq) (res *beans.ResponseEmpty, err error) {
ctx = WithAdminUser(ctx)
if req.CallbackData == "" {
return nil, fmt.Errorf("callbackData不能为空")
}
err = check2.TencentContentCallback.ProcessVideoCallback(ctx, req.CallbackData)
return
}
// ProcessImageResult 查询并处理图片检测结果(轮询模式)
func (c *ContentCheckController) ProcessImageResult(ctx context.Context, req *dto.ProcessImageResultReq) (res *beans.ResponseEmpty, err error) {
ctx = WithAdminUser(ctx)
if req.TaskID == "" {
return nil, fmt.Errorf("taskId不能为空")
}
err = check2.TencentContentCallback.ProcessImageResult(ctx, req.TaskID)
return
}
// ProcessVideoResult 查询并处理视频检测结果(轮询模式)
func (c *ContentCheckController) ProcessVideoResult(ctx context.Context, req *dto.ProcessVideoResultReq) (res *beans.ResponseEmpty, err error) {
ctx = WithAdminUser(ctx)
if req.TaskID == "" {
return nil, fmt.Errorf("taskId不能为空")
}
err = check2.TencentContentCallback.ProcessVideoResult(ctx, req.TaskID)
return
}
// ManualSubmitImageByID 根据图片ID手动提交送检
func (c *ContentCheckController) ManualSubmitImageByID(ctx context.Context, req *dto.ManualSubmitImageByIDReq) (res *dto.ManualSubmitRes, err error) {
ctx = WithAdminUser(ctx)
result, err := check2.TencentContentCheck.SubmitImageByID(ctx, req.ImageID)
if err != nil {
return nil, err
}
res = &dto.ManualSubmitRes{
TaskID: result.TaskID,
}
return
}
// ManualSubmitVideoByID 根据视频ID手动提交送检
func (c *ContentCheckController) ManualSubmitVideoByID(ctx context.Context, req *dto.ManualSubmitVideoByIDReq) (res *dto.ManualSubmitRes, err error) {
ctx = WithAdminUser(ctx)
result, err := check2.TencentContentCheck.SubmitVideoByID(ctx, req.VideoID)
if err != nil {
return nil, err
}
res = &dto.ManualSubmitRes{
TaskID: result.TaskID,
}
return
}
// GetImageCheckLogs 获取图片的送检日志
func (c *ContentCheckController) GetImageCheckLogs(ctx context.Context, req *dto.GetImageCheckLogsReq) (res *dto.GetCheckLogsRes, err error) {
ctx = WithAdminUser(ctx)
logs, err := check2.TencentContentCallback.GetCheckLogsByImageID(ctx, req.ImageID)
if err != nil {
return nil, err
}
res = &dto.GetCheckLogsRes{
List: logs,
}
return
}
// GetVideoCheckLogs 获取视频的送检日志
func (c *ContentCheckController) GetVideoCheckLogs(ctx context.Context, req *dto.GetVideoCheckLogsReq) (res *dto.GetCheckLogsRes, err error) {
ctx = WithAdminUser(ctx)
logs, err := check2.TencentContentCallback.GetCheckLogsByVideoID(ctx, req.VideoID)
if err != nil {
return nil, err
}
res = &dto.GetCheckLogsRes{
List: logs,
}
return
}
@@ -0,0 +1,634 @@
package check
import (
consts "cid/consts/check"
dao "cid/dao/check"
entity "cid/model/entity/check"
serviceDataengine "cid/service/check"
"context"
"fmt"
"time"
"github.com/gogf/gf/v2/frame/g"
)
// MaterialVerifyController 素材校验控制器
type MaterialVerifyController struct{}
// MaterialVerify 控制器单例
var MaterialVerify = new(MaterialVerifyController)
// =============================================================================
// 请求/响应结构体
// =============================================================================
// ImageListReq 图片列表请求
type ImageListReq struct {
Status string `json:"status"`
AccountID int64 `json:"accountId"`
Page int `json:"page"`
PageSize int `json:"pageSize"`
StartTime int64 `json:"startTime"`
EndTime int64 `json:"endTime"`
}
// ImageListRes 图片列表响应
type ImageListRes struct {
List interface{} `json:"list"`
Total int `json:"total"`
}
// StatsRes 统计响应
type StatsRes struct {
Pending int `json:"pending"`
Verified int `json:"verified"`
Rejected int `json:"rejected"`
}
// VideoListReq 视频列表请求
type VideoListReq struct {
Status string `json:"status"`
AccountID int64 `json:"accountId"`
Page int `json:"page"`
PageSize int `json:"pageSize"`
StartTime int64 `json:"startTime"`
EndTime int64 `json:"endTime"`
}
// VideoListRes 视频列表响应
type VideoListRes struct {
List interface{} `json:"list"`
Total int `json:"total"`
}
// LogListReq 日志列表请求
type LogListReq struct {
MaterialType string `json:"materialType"`
MaterialID string `json:"materialId"`
VerifyStatus string `json:"verifyStatus"`
AccountID int64 `json:"accountId"`
Page int `json:"page"`
PageSize int `json:"pageSize"`
StartTime int64 `json:"startTime"`
EndTime int64 `json:"endTime"`
}
// ManualVerifyReq 手动校验请求
type ManualVerifyReq struct {
MaterialID string `json:"materialId" v:"required#素材ID不能为空"`
}
// TaskIDReq 任务ID请求
type TaskIDReq struct {
TaskID string `json:"taskId" v:"required#任务ID不能为空"`
}
// ImageCallbackReq 图片回调请求
type ImageCallbackReq struct {
CallbackData string `json:"callbackData"`
}
// VideoCallbackReq 视频回调请求
type VideoCallbackReq struct {
CallbackData string `json:"callbackData"`
}
// BatchVerifyReq 批量校验请求
type BatchVerifyReq struct {
Limit int `json:"limit"`
}
// =============================================================================
// 图片素材接口
// =============================================================================
// ListImage 图片素材列表
func (c *MaterialVerifyController) ListImage(ctx context.Context, req *ImageListReq) (res *ImageListRes, err error) {
ctx = WithAdminUser(ctx)
if req.Page == 0 {
req.Page = 1
}
if req.PageSize == 0 {
req.PageSize = 20
}
condition := make(map[string]interface{})
if req.Status != "" {
condition[entity.TencentImageCols.VerifyStatus] = req.Status
}
if req.AccountID > 0 {
condition[entity.TencentImageCols.AccountID] = req.AccountID
}
data, total, err := dao.TencentImage.GetByCondition(ctx, condition, req.Page, req.PageSize)
if err != nil {
return nil, err
}
return &ImageListRes{
List: data,
Total: total,
}, nil
}
// StatsImage 图片素材统计
func (c *MaterialVerifyController) StatsImage(ctx context.Context, req *ImageListReq) (res *StatsRes, err error) {
ctx = WithAdminUser(ctx)
// 使用实体中定义的正确状态值:PENDING=待校验, VERIFIED=校验通过, REJECTED=校验不通过
pending, err := dao.TencentImage.CountByStatus(ctx, entity.VerifyStatusPending)
if err != nil {
g.Log().Errorf(ctx, "统计待校验图片数量失败: %v", err)
}
verified, err := dao.TencentImage.CountByStatus(ctx, entity.VerifyStatusVerified)
if err != nil {
g.Log().Errorf(ctx, "统计已通过图片数量失败: %v", err)
}
rejected, err := dao.TencentImage.CountByStatus(ctx, entity.VerifyStatusRejected)
if err != nil {
g.Log().Errorf(ctx, "统计不通过图片数量失败: %v", err)
}
return &StatsRes{
Pending: pending,
Verified: verified,
Rejected: rejected,
}, nil
}
// =============================================================================
// 视频素材接口
// =============================================================================
// ListVideo 视频素材列表
func (c *MaterialVerifyController) ListVideo(ctx context.Context, req *VideoListReq) (res *VideoListRes, err error) {
ctx = WithAdminUser(ctx)
if req.Page == 0 {
req.Page = 1
}
if req.PageSize == 0 {
req.PageSize = 20
}
condition := make(map[string]interface{})
if req.Status != "" {
condition[entity.TencentVideoCols.VerifyStatus] = req.Status
}
if req.AccountID > 0 {
condition[entity.TencentVideoCols.AccountID] = req.AccountID
}
data, total, err := dao.TencentVideo.GetByCondition(ctx, condition, req.Page, req.PageSize)
if err != nil {
return nil, err
}
return &VideoListRes{
List: data,
Total: total,
}, nil
}
// StatsVideo 视频素材统计
func (c *MaterialVerifyController) StatsVideo(ctx context.Context, req *VideoListReq) (res *StatsRes, err error) {
ctx = WithAdminUser(ctx)
// 使用实体中定义的正确状态值:PENDING=待校验, VERIFIED=校验通过, REJECTED=校验不通过
pending, err := dao.TencentVideo.CountByStatus(ctx, entity.VerifyStatusPending)
if err != nil {
g.Log().Errorf(ctx, "统计待校验视频数量失败: %v", err)
}
verified, err := dao.TencentVideo.CountByStatus(ctx, entity.VerifyStatusVerified)
if err != nil {
g.Log().Errorf(ctx, "统计已通过视频数量失败: %v", err)
}
rejected, err := dao.TencentVideo.CountByStatus(ctx, entity.VerifyStatusRejected)
if err != nil {
g.Log().Errorf(ctx, "统计不通过视频数量失败: %v", err)
}
return &StatsRes{
Pending: pending,
Verified: verified,
Rejected: rejected,
}, nil
}
// =============================================================================
// 校验日志接口
// =============================================================================
// ListLogRes 日志列表响应
type ListLogRes struct {
List interface{} `json:"list"`
Total int `json:"total"`
}
// ListLog 日志列表
func (c *MaterialVerifyController) ListLog(ctx context.Context, req *LogListReq) (res *ListLogRes, err error) {
ctx = WithAdminUser(ctx)
if req.Page == 0 {
req.Page = 1
}
if req.PageSize == 0 {
req.PageSize = 20
}
condition := make(map[string]interface{})
if req.MaterialType != "" {
condition[entity.MaterialVerifyLogCols.MaterialType] = req.MaterialType
}
if req.MaterialID != "" {
condition[entity.MaterialVerifyLogCols.MaterialID] = req.MaterialID
}
if req.VerifyStatus != "" {
condition[entity.MaterialVerifyLogCols.VerifyStatus] = req.VerifyStatus
}
if req.AccountID > 0 {
condition[entity.MaterialVerifyLogCols.AccountID] = req.AccountID
}
data, total, err := serviceDataengine.MaterialVerify.GetLogsByCondition(ctx, condition, req.Page, req.PageSize)
if err != nil {
return nil, err
}
return &ListLogRes{
List: data,
Total: total,
}, nil
}
// LogDetailRes 日志详情响应
type LogDetailRes struct {
*entity.MaterialVerifyLog
PreviewURL string `json:"previewURL"`
}
// GetLogDetailReq 日志详情请求
type GetLogDetailReq struct {
Id int64 `json:"id" v:"required#日志ID不能为空"`
}
// GetLogDetail 日志详情
func (c *MaterialVerifyController) GetLogDetail(ctx context.Context, req *GetLogDetailReq) (res *LogDetailRes, err error) {
ctx = WithAdminUser(ctx)
log, err := serviceDataengine.MaterialVerify.GetLogByID(ctx, req.Id)
if err != nil {
return nil, err
}
if log == nil {
return nil, fmt.Errorf("日志不存在")
}
// 获取来源数据预览
res = &LogDetailRes{
MaterialVerifyLog: log,
}
if log.SourceTable == consts.SourceTableTencentImage {
image, _ := dao.TencentImage.GetByID(ctx, log.SourceID)
if image != nil {
res.PreviewURL = image.PreviewURL
}
} else if log.SourceTable == consts.SourceTableTencentVideo {
video, _ := dao.TencentVideo.GetByID(ctx, log.SourceID)
if video != nil {
res.PreviewURL = video.PreviewURL
}
}
return
}
// StatsLogRes 日志统计响应
type StatsLogRes struct {
Total int `json:"total"`
Pending int `json:"pending"`
Verified int `json:"verified"`
Rejected int `json:"rejected"`
}
// StatsLog 日志统计
func (c *MaterialVerifyController) StatsLog(ctx context.Context, req *LogListReq) (res *StatsLogRes, err error) {
ctx = WithAdminUser(ctx)
stats, err := serviceDataengine.MaterialVerify.GetStats(ctx)
if err != nil {
return nil, err
}
return &StatsLogRes{
Total: stats["total"],
Pending: stats["pending"],
Verified: stats["verified"],
Rejected: stats["rejected"],
}, nil
}
// =============================================================================
// 手动校验接口
// =============================================================================
// ManualVerifyImageRes 手动校验响应
type ManualVerifyImageRes struct {
Id int64 `json:"id"`
TaskID string `json:"taskId"`
SourceID string `json:"sourceId"`
}
// ManualVerifyImage 手动校验图片
func (c *MaterialVerifyController) ManualVerifyImage(ctx context.Context, req *ManualVerifyReq) (res *ManualVerifyImageRes, err error) {
ctx = WithAdminUser(ctx)
log, err := serviceDataengine.MaterialVerify.VerifyImageByID(ctx, req.MaterialID)
if err != nil {
return nil, err
}
return &ManualVerifyImageRes{
Id: log.Id,
TaskID: log.TaskID,
SourceID: fmt.Sprintf("%d", log.SourceID),
}, nil
}
// ManualVerifyVideo 手动校验视频
func (c *MaterialVerifyController) ManualVerifyVideo(ctx context.Context, req *ManualVerifyReq) (res *ManualVerifyImageRes, err error) {
ctx = WithAdminUser(ctx)
log, err := serviceDataengine.MaterialVerify.VerifyVideoByID(ctx, req.MaterialID)
if err != nil {
return nil, err
}
return &ManualVerifyImageRes{
Id: log.Id,
TaskID: log.TaskID,
SourceID: fmt.Sprintf("%d", log.SourceID),
}, nil
}
// =============================================================================
// 批量校验接口
// =============================================================================
// BatchVerifyRes 批量校验响应
type BatchVerifyRes struct {
Success int `json:"success"`
Fail int `json:"fail"`
Total int `json:"total"`
Message string `json:"message"`
}
// BatchVerifyImage 批量校验图片
func (c *MaterialVerifyController) BatchVerifyImage(ctx context.Context, req *BatchVerifyReq) (res *BatchVerifyRes, err error) {
ctx = WithAdminUser(ctx)
if req.Limit <= 0 {
req.Limit = 100
}
images, err := dao.TencentImage.GetPendingList(ctx, req.Limit)
if err != nil {
return nil, err
}
successCount := 0
failCount := 0
for _, image := range images {
_, err := serviceDataengine.MaterialVerify.VerifyImageByID(ctx, image.ImageID)
if err != nil {
failCount++
g.Log().Errorf(ctx, "图片校验失败: %s, error: %v", image.ImageID, err)
} else {
successCount++
}
time.Sleep(100 * time.Millisecond)
}
return &BatchVerifyRes{
Success: successCount,
Fail: failCount,
Total: len(images),
Message: fmt.Sprintf("批量提交完成,成功: %d,失败: %d。请通过轮询接口获取检测结果", successCount, failCount),
}, nil
}
// BatchVerifyVideo 批量校验视频
func (c *MaterialVerifyController) BatchVerifyVideo(ctx context.Context, req *BatchVerifyReq) (res *BatchVerifyRes, err error) {
ctx = WithAdminUser(ctx)
if req.Limit <= 0 {
req.Limit = 100
}
videos, err := dao.TencentVideo.GetPendingList(ctx, req.Limit)
if err != nil {
return nil, err
}
successCount := 0
failCount := 0
for _, video := range videos {
_, err := serviceDataengine.MaterialVerify.VerifyVideoByID(ctx, video.VideoID)
if err != nil {
failCount++
g.Log().Errorf(ctx, "视频校验失败: %s, error: %v", video.VideoID, err)
} else {
successCount++
}
time.Sleep(100 * time.Millisecond)
}
return &BatchVerifyRes{
Success: successCount,
Fail: failCount,
Total: len(videos),
Message: fmt.Sprintf("批量提交完成,成功: %d,失败: %d。请通过轮询接口获取检测结果", successCount, failCount),
}, nil
}
// =============================================================================
// 账户列表接口
// =============================================================================
// ListAccountsReq 账户列表请求
type ListAccountsReq struct{}
// AccountItem 账户列表项
type AccountItem struct {
AccountID int64 `json:"accountId"`
CorporationName string `json:"corporationName"`
}
// ListAccountsRes 账户列表响应
type ListAccountsRes struct {
List []AccountItem `json:"list"`
}
// ListAccounts 获取所有启用的广告账户列表(用于前端下拉筛选)
func (c *MaterialVerifyController) ListAccounts(ctx context.Context, req *ListAccountsReq) (res *ListAccountsRes, err error) {
ctx = WithAdminUser(ctx)
accounts, err := dao.TencentAccountRelation.GetAll(ctx)
if err != nil {
return nil, err
}
var items []AccountItem
for _, acc := range accounts {
items = append(items, AccountItem{
AccountID: acc.AccountID,
CorporationName: acc.CorporationName,
})
}
return &ListAccountsRes{
List: items,
}, nil
}
// =============================================================================
// 导出接口 - 不通过数据
// =============================================================================
// ExportRejectedReq 导出不通过数据请求
type ExportRejectedReq struct {
MaterialType string `json:"materialType"` // IMAGE/VIDEO,为空则导出全部
}
// ExportRejectedItem 导出的不通过数据项
type ExportRejectedItem struct {
ID int64 `json:"id"`
MaterialID string `json:"materialId"`
AccountID int64 `json:"accountId"`
CorporationName string `json:"corporationName"`
PreviewURL string `json:"previewUrl"`
Description string `json:"description"`
ErrorMsg string `json:"errorMsg"`
MaterialType string `json:"materialType"`
ImageUsage string `json:"imageUsage,omitempty"`
CreatedAt string `json:"createdAt"`
}
// ExportRejectedRes 导出不通过数据响应
type ExportRejectedRes struct {
Items []ExportRejectedItem `json:"items"`
Total int `json:"total"`
}
// ExportRejected 导出不通过的图片/视频数据(含失败原因)
func (c *MaterialVerifyController) ExportRejected(ctx context.Context, req *ExportRejectedReq) (res *ExportRejectedRes, err error) {
ctx = WithAdminUser(ctx)
items, err := serviceDataengine.MaterialVerify.ExportRejectedData(ctx, req.MaterialType)
if err != nil {
return nil, err
}
// 转换为响应结构
var respItems []ExportRejectedItem
for _, item := range items {
respItems = append(respItems, ExportRejectedItem{
ID: item.ID,
MaterialID: item.MaterialID,
AccountID: item.AccountID,
CorporationName: item.CorporationName,
PreviewURL: item.PreviewURL,
Description: item.Description,
ErrorMsg: item.ErrorMsg,
MaterialType: item.MaterialType,
ImageUsage: item.ImageUsage,
CreatedAt: item.CreatedAt,
})
}
return &ExportRejectedRes{
Items: respItems,
Total: len(respItems),
}, nil
}
// =============================================================================
// 回调处理接口
// =============================================================================
// CallbackRes 回调响应
type CallbackRes struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
// ImageCallback 图片校验回调
func (c *MaterialVerifyController) ImageCallback(ctx context.Context, req *ImageCallbackReq) (res *CallbackRes, err error) {
if !CheckCallbackIP(ctx) {
return &CallbackRes{Code: 403, Msg: "IP not allowed"}, nil
}
ctx = WithCallbackUser(ctx)
if req.CallbackData == "" {
return &CallbackRes{Code: 400, Msg: "callbackData不能为空"}, nil
}
err = serviceDataengine.MaterialVerify.ProcessImageCallback(ctx, req.CallbackData)
if err != nil {
return &CallbackRes{Code: 500, Msg: err.Error()}, nil
}
return &CallbackRes{Code: 0, Msg: "处理成功"}, nil
}
// VideoCallback 视频校验回调
func (c *MaterialVerifyController) VideoCallback(ctx context.Context, req *VideoCallbackReq) (res *CallbackRes, err error) {
if !CheckCallbackIP(ctx) {
return &CallbackRes{Code: 403, Msg: "IP not allowed"}, nil
}
ctx = WithCallbackUser(ctx)
if req.CallbackData == "" {
return &CallbackRes{Code: 400, Msg: "callbackData不能为空"}, nil
}
err = serviceDataengine.MaterialVerify.ProcessVideoCallback(ctx, req.CallbackData)
if err != nil {
return &CallbackRes{Code: 500, Msg: err.Error()}, nil
}
return &CallbackRes{Code: 0, Msg: "处理成功"}, nil
}
// ResultRes 结果查询响应
type ResultRes struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
// ImageResult 图片校验结果查询(轮询模式)
func (c *MaterialVerifyController) ImageResult(ctx context.Context, req *TaskIDReq) (res *ResultRes, err error) {
ctx = WithAdminUser(ctx)
if req.TaskID == "" {
return &ResultRes{Code: 400, Msg: "taskId不能为空"}, nil
}
err = serviceDataengine.MaterialVerify.ProcessImageResultByTask(ctx, req.TaskID)
if err != nil {
return &ResultRes{Code: 500, Msg: err.Error()}, nil
}
return &ResultRes{Code: 0, Msg: "处理成功"}, nil
}
// VideoResult 视频校验结果查询(轮询模式)
func (c *MaterialVerifyController) VideoResult(ctx context.Context, req *TaskIDReq) (res *ResultRes, err error) {
ctx = WithAdminUser(ctx)
if req.TaskID == "" {
return &ResultRes{Code: 400, Msg: "taskId不能为空"}, nil
}
err = serviceDataengine.MaterialVerify.ProcessVideoResultByTask(ctx, req.TaskID)
if err != nil {
return &ResultRes{Code: 500, Msg: err.Error()}, nil
}
return &ResultRes{Code: 0, Msg: "处理成功"}, nil
}
+42
View File
@@ -0,0 +1,42 @@
// Package internal 提供 controller 层的共享工具函数
package check
import (
"context"
"strings"
"gitea.redpowerfuture.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
// WithAdminUser 在 context 中注入 admin 用户信息
func WithAdminUser(ctx context.Context) context.Context {
return context.WithValue(ctx, "user", &beans.User{UserName: "admin", TenantId: 1})
}
// WithCallbackUser 在 context 中注入 yidun_callback 用户信息
func WithCallbackUser(ctx context.Context) context.Context {
return context.WithValue(ctx, "user", &beans.User{UserName: "yidun_callback", TenantId: 1})
}
// CheckCallbackIP 校验回调请求 IP 是否在白名单内
// 读取配置 check.callback_allowed_ips,若未配置则跳过校验
func CheckCallbackIP(ctx context.Context) bool {
r := ghttp.RequestFromCtx(ctx)
if r == nil {
return true
}
allowedIPs := g.Cfg().MustGet(ctx, "check.callback_allowed_ips", "").String()
if allowedIPs == "" {
return true
}
clientIP := r.GetClientIp()
for _, ip := range strings.Split(allowedIPs, ",") {
if strings.TrimSpace(ip) == clientIP {
return true
}
}
g.Log().Warningf(ctx, "回调IP不在白名单中, clientIP=%s, allowedIPs=%s", clientIP, allowedIPs)
return false
}
@@ -0,0 +1,382 @@
package check
import (
dataengineService "cid/service/check"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
// YidunCallbackController 易盾回调控制器
// 用于接收易盾检测结果的主动推送或手动轮询查询
type YidunCallbackController struct{}
// YidunCallback 易盾回调控制器单例
var YidunCallback = new(YidunCallbackController)
// CallbackResult 通用回调响应
type CallbackResult struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data,omitempty"`
}
// PollResult 轮询结果
type PollResult struct {
SuccessCount int `json:"success_count"`
FailCount int `json:"fail_count"`
PendingCount int `json:"pending_count"`
}
// =============================================================================
// 易盾主动推送模式回调接口
// 易盾会在检测完成后主动 POST 数据到这些接口
// =============================================================================
// ReceiveImageCallback 接收易盾图片检测结果推送
// 易盾回调格式: POST /check/callback/receiveImage
// Body: callbackData={"antispam":{...}}
func (c *YidunCallbackController) ReceiveImageCallback(r *ghttp.Request) {
// IP 白名单校验
if !CheckCallbackIP(r.Context()) {
r.Response.WriteJson(CallbackResult{Code: 403, Msg: "IP not allowed"})
return
}
ctx := r.Context()
ctx = WithCallbackUser(ctx)
// 易盾推送的数据在请求体中
var callbackData string
// 尝试从表单数据获取
callbackData = r.GetForm("callbackData", "").String()
if callbackData == "" {
// 尝试从请求体JSON获取
var reqBody map[string]interface{}
if err := r.Parse(&reqBody); err == nil {
if v, ok := reqBody["callbackData"]; ok {
callbackData = toString(v)
}
}
}
// 尝试直接从请求体获取原始数据
if callbackData == "" {
callbackData = string(r.GetBody())
}
if callbackData == "" {
g.Log().Warningf(ctx, "图片回调数据为空")
r.Response.WriteJson(CallbackResult{Code: 400, Msg: "callbackData不能为空"})
return
}
g.Log().Infof(ctx, "收到易盾图片回调, data长度: %d", len(callbackData))
// 处理回调 - 更新 material_verify_log 和 tencent_image 表
err := dataengineService.MaterialVerify.ProcessImageCallback(ctx, callbackData)
if err != nil {
g.Log().Errorf(ctx, "处理易盾图片回调失败: %v", err)
r.Response.WriteJson(CallbackResult{Code: 500, Msg: err.Error()})
return
}
r.Response.WriteJson(CallbackResult{Code: 0, Msg: "success"})
}
// ReceiveVideoCallback 接收易盾视频检测结果推送
// 易盾回调格式: POST /check/callback/receiveVideo
// Body: callbackData={"antispam":{...}}
func (c *YidunCallbackController) ReceiveVideoCallback(r *ghttp.Request) {
// IP 白名单校验
if !CheckCallbackIP(r.Context()) {
r.Response.WriteJson(CallbackResult{Code: 403, Msg: "IP not allowed"})
return
}
ctx := r.Context()
ctx = WithCallbackUser(ctx)
// 易盾推送的数据在请求体中
var callbackData string
// 尝试从表单数据获取
callbackData = r.GetForm("callbackData", "").String()
if callbackData == "" {
// 尝试从请求体JSON获取
var reqBody map[string]interface{}
if err := r.Parse(&reqBody); err == nil {
if v, ok := reqBody["callbackData"]; ok {
callbackData = toString(v)
}
}
}
// 尝试直接从请求体获取原始数据
if callbackData == "" {
callbackData = string(r.GetBody())
}
if callbackData == "" {
g.Log().Warningf(ctx, "视频回调数据为空")
r.Response.WriteJson(CallbackResult{Code: 400, Msg: "callbackData不能为空"})
return
}
g.Log().Infof(ctx, "收到易盾视频回调, data长度: %d", len(callbackData))
// 处理回调 - 更新 material_verify_log 和 tencent_video 表
err := dataengineService.MaterialVerify.ProcessVideoCallback(ctx, callbackData)
if err != nil {
g.Log().Errorf(ctx, "处理易盾视频回调失败: %v", err)
r.Response.WriteJson(CallbackResult{Code: 500, Msg: err.Error()})
return
}
r.Response.WriteJson(CallbackResult{Code: 0, Msg: "success"})
}
// =============================================================================
// 轮询模式 - 手动查询检测结果
// =============================================================================
// PollAllResults 轮询所有待查询的检测结果(图片+视频)
// 格式: POST /check/callback/poll
func (c *YidunCallbackController) PollAllResults(r *ghttp.Request) {
ctx := r.Context()
ctx = WithAdminUser(ctx)
g.Log().Info(ctx, "开始轮询所有待查询的检测结果...")
// 先获取待处理数量
// 执行轮询
successCount, failCount, err := dataengineService.MaterialVerify.PollPendingResults(ctx)
// 轮询后再查一下剩余待处理的明细
pendingItems, _ := dataengineService.MaterialVerify.GetPendingResultsDetail(ctx, 50)
msg := fmt.Sprintf("✅ 成功处理 %d 条 | ❌ 失败 %d 条 | ⏳ 还剩 %d 条待处理",
successCount, failCount, len(pendingItems))
if err != nil {
r.Response.WriteJson(CallbackResult{
Code: 500,
Msg: fmt.Sprintf("部分完成,但有错误: %v", err),
Data: g.Map{
"summary": g.Map{"success": successCount, "fail": failCount, "pending": len(pendingItems)},
"pending_detail": pendingItems,
},
})
return
}
r.Response.WriteJson(CallbackResult{
Code: 0,
Msg: msg,
Data: g.Map{
"summary": g.Map{"success": successCount, "fail": failCount, "pending": len(pendingItems)},
"pending_detail": pendingItems,
},
})
}
// PollImageResults 轮询图片待查询的检测结果
// 格式: POST /check/callback/pollImage
func (c *YidunCallbackController) PollImageResults(r *ghttp.Request) {
ctx := r.Context()
ctx = WithAdminUser(ctx)
g.Log().Info(ctx, "开始轮询图片待查询的检测结果...")
successCount, failCount, err := dataengineService.MaterialVerify.PollPendingImageResults(ctx)
if err != nil {
r.Response.WriteJson(CallbackResult{
Code: 500,
Msg: fmt.Sprintf("轮询失败: %v", err),
Data: PollResult{SuccessCount: successCount, FailCount: failCount},
})
return
}
r.Response.WriteJson(CallbackResult{
Code: 0,
Msg: fmt.Sprintf("轮询完成,成功处理 %d 条,失败 %d 条", successCount, failCount),
Data: PollResult{SuccessCount: successCount, FailCount: failCount},
})
}
// PollVideoResults 轮询视频待查询的检测结果
// 格式: POST /check/callback/pollVideo
func (c *YidunCallbackController) PollVideoResults(r *ghttp.Request) {
ctx := r.Context()
ctx = WithAdminUser(ctx)
g.Log().Info(ctx, "开始轮询视频待查询的检测结果...")
successCount, failCount, err := dataengineService.MaterialVerify.PollPendingVideoResults(ctx)
if err != nil {
r.Response.WriteJson(CallbackResult{
Code: 500,
Msg: fmt.Sprintf("轮询失败: %v", err),
Data: PollResult{SuccessCount: successCount, FailCount: failCount},
})
return
}
r.Response.WriteJson(CallbackResult{
Code: 0,
Msg: fmt.Sprintf("轮询完成,成功处理 %d 条,失败 %d 条", successCount, failCount),
Data: PollResult{SuccessCount: successCount, FailCount: failCount},
})
}
// PollByTaskID 根据任务ID查询单个检测结果
// 格式: POST /check/callback/pollTask
func (c *YidunCallbackController) PollByTaskID(r *ghttp.Request) {
ctx := r.Context()
ctx = WithAdminUser(ctx)
taskID := r.Get("taskId", "").String()
taskType := r.Get("type", "").String() // image 或 video
if taskID == "" {
r.Response.WriteJson(CallbackResult{Code: 400, Msg: "taskId不能为空"})
return
}
g.Log().Infof(ctx, "查询单个检测结果, taskId=%s, type=%s", taskID, taskType)
var err error
if taskType == "video" || taskType == "" {
// 尝试视频
err = dataengineService.MaterialVerify.ProcessVideoResultByTask(ctx, taskID)
if err != nil {
// 如果失败且没有指定类型,尝试图片
if taskType == "" {
err = dataengineService.MaterialVerify.ProcessImageResultByTask(ctx, taskID)
}
}
} else if taskType == "image" {
err = dataengineService.MaterialVerify.ProcessImageResultByTask(ctx, taskID)
}
if err != nil {
r.Response.WriteJson(CallbackResult{Code: 500, Msg: err.Error()})
return
}
r.Response.WriteJson(CallbackResult{Code: 0, Msg: "查询并处理成功"})
}
// PendingListRes 待查询结果明细
type PendingListRes struct {
Total int `json:"total"`
List []dataengineService.PendingResultItem `json:"list"`
}
// GetPendingDetail 获取待查询结果的明细
// 格式: GET /check/callback/pendingDetail
func (c *YidunCallbackController) GetPendingDetail(r *ghttp.Request) {
ctx := r.Context()
ctx = WithAdminUser(ctx)
items, err := dataengineService.MaterialVerify.GetPendingResultsDetail(ctx, 50)
if err != nil {
r.Response.WriteJson(CallbackResult{Code: 500, Msg: err.Error()})
return
}
r.Response.WriteJson(g.Map{
"code": 0,
"data": PendingListRes{
Total: len(items),
List: items,
},
})
}
// GetPendingCount 获取待查询结果的数量
// 格式: GET /check/callback/pendingCount
func (c *YidunCallbackController) GetPendingCount(r *ghttp.Request) {
ctx := r.Context()
ctx = WithAdminUser(ctx)
count, err := dataengineService.MaterialVerify.GetPendingResultsCount(ctx)
if err != nil {
r.Response.WriteJson(CallbackResult{Code: 500, Msg: err.Error()})
return
}
r.Response.WriteJson(g.Map{
"code": 0,
"data": g.Map{
"pending_count": count,
"description": "待查询结果的日志数量(状态为pending且有taskID",
},
})
}
// =============================================================================
// 兼容旧接口(手动触发回调处理)
// =============================================================================
// ProcessImageCallback 手动处理图片回调(兼容旧接口)
// 格式: POST /check/callback/processImage
func (c *YidunCallbackController) ProcessImageCallback(r *ghttp.Request) {
ctx := r.Context()
ctx = WithAdminUser(ctx)
var req struct {
CallbackData string `json:"callbackData" v:"required#回调数据不能为空"`
}
if err := r.Parse(&req); err != nil {
r.Response.WriteJson(CallbackResult{Code: 400, Msg: err.Error()})
return
}
err := dataengineService.MaterialVerify.ProcessImageCallback(ctx, req.CallbackData)
if err != nil {
g.Log().Errorf(ctx, "处理图片回调失败: %v", err)
r.Response.WriteJson(CallbackResult{Code: 500, Msg: err.Error()})
return
}
r.Response.WriteJson(CallbackResult{Code: 0, Msg: "success"})
}
// ProcessVideoCallback 手动处理视频回调(兼容旧接口)
// 格式: POST /check/callback/processVideo
func (c *YidunCallbackController) ProcessVideoCallback(r *ghttp.Request) {
ctx := r.Context()
ctx = WithAdminUser(ctx)
var req struct {
CallbackData string `json:"callbackData" v:"required#回调数据不能为空"`
}
if err := r.Parse(&req); err != nil {
r.Response.WriteJson(CallbackResult{Code: 400, Msg: err.Error()})
return
}
err := dataengineService.MaterialVerify.ProcessVideoCallback(ctx, req.CallbackData)
if err != nil {
g.Log().Errorf(ctx, "处理视频回调失败: %v", err)
r.Response.WriteJson(CallbackResult{Code: 500, Msg: err.Error()})
return
}
r.Response.WriteJson(CallbackResult{Code: 0, Msg: "success"})
}
// toString 转换interface{}为string
func toString(v interface{}) string {
if s, ok := v.(string); ok {
return s
}
return ""
}
+175
View File
@@ -0,0 +1,175 @@
package check
import (
"cid/service/check"
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/yidun/yidun-golang-sdk/yidun/service/antispam/text/v5/check/async/single"
)
type yidunController struct{}
// YidunController 易盾控制器
var YidunController = new(yidunController)
// DetectTextReq 文本检测请求
type DetectTextReq struct {
DataID string `json:"data_id"`
Content string `json:"content" v:"required#待检测文本不能为空"`
IP string `json:"ip"`
Token string `json:"token"`
}
// DetectImageReq 图片检测请求
type DetectImageReq struct {
DataID string `json:"data_id"`
ImageURL string `json:"image_url" v:"required#图片URL不能为空"`
CallbackURL string `json:"callback_url"`
}
// DetectVideoReq 视频检测请求
type DetectVideoReq struct {
DataID string `json:"data_id"`
VideoURL string `json:"video_url" v:"required#视频URL不能为空"`
CallbackURL string `json:"callback_url"`
}
// DetectText 文本检测
func (c *yidunController) DetectText(ctx context.Context, req *DetectTextReq) (string, error) {
ctx = WithAdminUser(ctx)
businessId := g.Cfg().MustGet(ctx, "check.text.business_id").String()
sdkReq := single.NewTextAsyncCheckRequest(businessId)
sdkReq.SetDataID(req.DataID)
sdkReq.SetContent(req.Content)
if req.IP != "" {
sdkReq.SetIP(req.IP)
}
if req.Token != "" {
sdkReq.SetToken(req.Token)
}
return check.TextDetection.DetectText(ctx, sdkReq)
}
// DetectImage 图片检测
func (c *yidunController) DetectImage(ctx context.Context, req *DetectImageReq) (*check.ImageSubmitResult, error) {
ctx = WithAdminUser(ctx)
return check.ImageDetection.DetectImage(ctx, req.ImageURL, req.DataID, req.CallbackURL)
}
// DetectVideo 视频检测
func (c *yidunController) DetectVideo(ctx context.Context, req *DetectVideoReq) (*check.VideoSubmitResult, error) {
ctx = WithAdminUser(ctx)
return check.VideoDetection.DetectVideo(ctx, req.VideoURL, req.DataID, req.CallbackURL)
}
// ImageCallbackResult 图片检测回调响应
type ImageCallbackResult struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
// ReceiveImageCallback 接收图片检测结果推送
func (c *yidunController) ReceiveImageCallback(r *ghttp.Request) {
// IP 白名单校验
if !CheckCallbackIP(r.Context()) {
r.Response.WriteJson(ImageCallbackResult{Code: 403, Msg: "IP not allowed"})
return
}
ctx := r.Context()
ctx = WithAdminUser(ctx)
callbackData := r.GetForm("callbackData", "").String()
if callbackData == "" {
r.Response.WriteJson(ImageCallbackResult{Code: 400, Msg: "callbackData不能为空"})
return
}
err := check.MaterialVerify.ProcessImageCallback(ctx, callbackData)
if err != nil {
g.Log().Errorf(ctx, "处理图片检测回调失败: %v", err)
r.Response.WriteJson(ImageCallbackResult{Code: 500, Msg: err.Error()})
return
}
r.Response.WriteJson(ImageCallbackResult{Code: 0, Msg: "success"})
}
// VideoCallbackResult 视频检测回调响应
type VideoCallbackResult struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
// ReceiveVideoCallback 接收视频检测结果推送
func (c *yidunController) ReceiveVideoCallback(r *ghttp.Request) {
// IP 白名单校验
if !CheckCallbackIP(r.Context()) {
r.Response.WriteJson(VideoCallbackResult{Code: 403, Msg: "IP not allowed"})
return
}
ctx := r.Context()
ctx = WithAdminUser(ctx)
callbackData := r.GetForm("callbackData", "").String()
if callbackData == "" {
r.Response.WriteJson(VideoCallbackResult{Code: 400, Msg: "callbackData不能为空"})
return
}
err := check.MaterialVerify.ProcessVideoCallback(ctx, callbackData)
if err != nil {
g.Log().Errorf(ctx, "处理视频检测回调失败: %v", err)
r.Response.WriteJson(VideoCallbackResult{Code: 500, Msg: err.Error()})
return
}
r.Response.WriteJson(VideoCallbackResult{Code: 0, Msg: "success"})
}
// GetVideoResult 获取视频检测结果
func (c *yidunController) GetVideoResult(r *ghttp.Request) {
ctx := r.Context()
ctx = WithAdminUser(ctx)
taskId := r.Get("taskId", "").String()
if taskId == "" {
r.Response.WriteJson(g.Map{"code": 400, "msg": "taskId不能为空"})
return
}
result, err := check.VideoDetection.GetVideoResult(ctx, taskId)
if err != nil {
g.Log().Errorf(ctx, "查询视频检测结果失败: %v", err)
r.Response.WriteJson(g.Map{"code": 500, "msg": err.Error()})
return
}
r.Response.WriteJson(result)
}
// GetImageResult 获取图片检测结果
func (c *yidunController) GetImageResult(r *ghttp.Request) {
ctx := r.Context()
ctx = WithAdminUser(ctx)
taskId := r.Get("taskId", "").String()
if taskId == "" {
r.Response.WriteJson(g.Map{"code": 400, "msg": "taskId不能为空"})
return
}
result, err := check.ImageDetection.GetImageResult(ctx, taskId)
if err != nil {
g.Log().Errorf(ctx, "查询图片检测结果失败: %v", err)
r.Response.WriteJson(g.Map{"code": 500, "msg": err.Error()})
return
}
r.Response.WriteJson(result)
}
-47
View File
@@ -1,47 +0,0 @@
package controller
import (
"context"
"cid/model/dto"
"cid/service"
"github.com/gogf/gf/v2/errors/gerror"
)
var CID = new(cid)
type cid struct{}
// GenerateCID 生成CID广告
func (c *cid) GenerateCID(ctx context.Context, req *dto.GenerateCIDReq) (res *dto.GenerateCIDRes, err error) {
if req == nil {
return nil, gerror.New("请求参数不能为空")
}
if req.RequestType == "" {
req.RequestType = "default" // 默认请求类型
}
result, err := service.CID.GenerateCID(ctx, req)
if err != nil {
return nil, err
}
return result, nil
}
// GetCIDHistory 获取CID历史记录
func (c *cid) GetCIDHistory(ctx context.Context, req *dto.GetCIDHistoryReq) (res *dto.GetCIDHistoryRes, err error) {
if req == nil {
return nil, gerror.New("请求参数不能为空")
}
// 查询历史记录
history, err := service.CID.GetCIDHistory(ctx, 1, req.Page, req.Size) // 临时使用固定用户ID
if err != nil {
return nil, err
}
return history, nil
}
-37
View File
@@ -1,37 +0,0 @@
package controller
import (
"context"
"cid/model/dto"
"cid/service"
)
var RateLimit = new(rateLimit)
type rateLimit struct{}
// SetTenantRateLimit 设置租户限流配置
func (c *rateLimit) SetTenantRateLimit(ctx context.Context, req *dto.SetTenantRateLimitReq) (res *dto.SetTenantRateLimitRes, err error) {
// 注意:实际使用的是config.yml中的全局配置,此接口仅用于兼容旧API
// 实际限流参数请修改config.yml中的tenantRateLimit部分
return &dto.SetTenantRateLimitRes{
Success: true,
}, nil
}
// GetTenantRateLimitUsage 获取租户限流使用情况
func (c *rateLimit) GetTenantRateLimitUsage(ctx context.Context, req *dto.GetTenantRateLimitUsageReq) (res *dto.GetTenantRateLimitUsageRes, err error) {
current, max, err := service.RateLimit.GetTenantCurrentUsage(ctx, req.TenantID, nil)
if err != nil {
return nil, err
}
return &dto.GetTenantRateLimitUsageRes{
TenantID: req.TenantID,
CurrentUsed: current,
MaxAllowed: max,
UsagePercent: float64(current) / float64(max) * 100,
}, nil
}
-77
View File
@@ -1,77 +0,0 @@
package controller
import (
"context"
"cid/model/dto"
"cid/service"
"github.com/gogf/gf/v2/errors/gerror"
)
var Strategy = new(strategy)
type strategy struct{}
// Create 创建策略
func (c *strategy) Create(ctx context.Context, req *dto.CreateStrategyReq) (res *dto.StrategyRes, err error) {
id, err := service.Strategy.CreateStrategy(ctx, req)
if err != nil {
return nil, err
}
strategy, err := service.Strategy.GetStrategyByID(ctx, id)
if err != nil {
return nil, err
}
return strategy, nil
}
// Update 更新策略
func (c *strategy) Update(ctx context.Context, req *dto.UpdateStrategyReq) (res *dto.StrategyRes, err error) {
affected, err := service.Strategy.UpdateStrategy(ctx, req)
if err != nil {
return nil, err
}
if affected == 0 {
return nil, gerror.New("策略更新失败")
}
strategy, err := service.Strategy.GetStrategyByID(ctx, req.Id)
if err != nil {
return nil, err
}
return strategy, nil
}
// Delete 删除策略
func (c *strategy) Delete(ctx context.Context, req *dto.DeleteStrategyReq) (res *dto.DeleteStrategyRes, err error) {
affected, err := service.Strategy.DeleteStrategy(ctx, req.Id)
if err != nil {
return nil, err
}
if affected == 0 {
return nil, gerror.New("策略删除失败")
}
return &dto.DeleteStrategyRes{
Success: true,
}, nil
}
// GetByID 根据ID获取策略
func (c *strategy) GetByID(ctx context.Context, req *dto.GetStrategyReq) (res *dto.StrategyRes, err error) {
strategy, err := service.Strategy.GetStrategyByID(ctx, req.Id)
if err != nil {
return nil, err
}
return strategy, nil
}
// GetList 获取策略列表
func (c *strategy) GetList(ctx context.Context, req *dto.GetStrategyListReq) (res *dto.GetStrategyListRes, err error) {
return service.Strategy.GetStrategyList(ctx, req)
}
-126
View File
@@ -1,126 +0,0 @@
package dao
import (
"cid/model/dto"
"cid/model/entity"
"context"
"gitea.com/red-future/common/beans"
"gitea.com/red-future/common/db/mongo"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
"go.mongodb.org/mongo-driver/v2/bson"
)
var AdPosition = &adPosition{}
type adPosition struct {
}
// Insert 插入广告位
func (d *adPosition) Insert(ctx context.Context, req *dto.AddAdPositionReq) (ids []any, err error) {
var result entity.AdPosition
if err = gconv.Struct(req, &result); err != nil {
return
}
ids, err = mongo.DB().Insert(ctx, []interface{}{&result}, entity.AdPositionCollection)
return
}
// Update 更新广告位
func (d *adPosition) Update(ctx context.Context, id *bson.ObjectID, updateData *entity.AdPosition) (err error) {
filter := bson.M{"_id": id}
if !g.IsEmpty(updateData) {
bsonm, err := mongo.BuildUpdateData(ctx, updateData)
if err != nil {
return err
}
update := bson.M{"$set": bsonm}
_, err = mongo.DB().Update(ctx, filter, update, entity.AdPositionCollection)
}
return
}
// UpdateStatus 更新广告位状态
func (d *adPosition) UpdateStatus(ctx context.Context, id *bson.ObjectID, status string) (err error) {
filter := bson.M{"_id": id}
update := bson.M{"$set": bson.M{"status": status}}
_, err = mongo.DB().Update(ctx, filter, update, entity.AdPositionCollection)
return
}
// GetOne 获取单个广告位
func (d *adPosition) GetOne(ctx context.Context, id *bson.ObjectID) (adPosition *entity.AdPosition, err error) {
filter := bson.M{"_id": id}
adPosition = &entity.AdPosition{}
err = mongo.DB().FindOne(ctx, filter, adPosition, entity.AdPositionCollection)
return
}
// Delete 删除广告位
func (d *adPosition) Delete(ctx context.Context, id *bson.ObjectID) (err error) {
filter := bson.M{"_id": id}
_, err = mongo.DB().Delete(ctx, filter, entity.AdPositionCollection)
return
}
// buildListFilter 构建列表查询的过滤条件
func (d *adPosition) buildListFilter(req *dto.ListAdPositionReq) bson.M {
filter := bson.M{}
if !g.IsEmpty(req.Name) {
filter["name"] = bson.M{"$regex": req.Name, "$options": "i"}
}
if !g.IsEmpty(req.PositionCode) {
filter["positionCode"] = req.PositionCode
}
if !g.IsEmpty(req.PageName) {
filter["page"] = req.PageName
}
if !g.IsEmpty(req.Section) {
filter["section"] = req.Section
}
if !g.IsEmpty(req.Status) {
filter["status"] = req.Status
}
if !g.IsEmpty(req.AdFormat) {
filter["adFormat"] = req.AdFormat
}
// 处理日期范围
if len(req.DateRange) == 2 {
startTime := gconv.Int64(req.DateRange[0])
endTime := gconv.Int64(req.DateRange[1])
filter["createdAt"] = bson.M{
"$gte": startTime,
"$lte": endTime,
}
}
return filter
}
// List 获取广告位列表
func (d *adPosition) List(ctx context.Context, req *dto.ListAdPositionReq) (list []*entity.AdPosition, total int64, err error) {
// 构建查询过滤条件
filter := d.buildListFilter(req)
// 使用common/mongo的Find方法,自动处理分页、租户等
total, err = mongo.DB().Find(ctx, filter, &list, entity.AdPositionCollection, req.Page, nil)
return
}
// GetAvailableAdPositions 获取可用的广告位列表
func (d *adPosition) GetAvailableAdPositions(ctx context.Context) (list []*entity.AdPosition, err error) {
filter := bson.M{
"status": "启用", // 只返回启用的广告位
}
// 使用空的Page参数获取所有数据
page := &beans.Page{PageNum: 1, PageSize: -1} // -1表示不分页
_, err = mongo.DB().Find(ctx, filter, &list, entity.AdPositionCollection, page, nil)
return
}
-84
View File
@@ -1,84 +0,0 @@
package dao
import (
"context"
"cid/consts"
"cid/model/entity"
"gitea.com/red-future/common/beans"
"gitea.com/red-future/common/db/mongo"
"go.mongodb.org/mongo-driver/v2/bson"
)
var AdSource = &adSourceDao{}
type adSourceDao struct {
}
// GetByName 根据名称获取广告源
func (d *adSourceDao) GetByName(ctx context.Context, name string) (adSource *entity.AdSource, err error) {
err = mongo.DB().FindOne(ctx, bson.M{"name": name}, &adSource, consts.AdSourceCollection)
return
}
// GetAvailableSources 获取可用的广告源
func (d *adSourceDao) GetAvailableSources(ctx context.Context) (list []*entity.AdSource, err error) {
// 使用空的Page参数获取所有数据
page := &beans.Page{PageNum: 1, PageSize: -1} // -1表示不分页
_, err = mongo.DB().Find(ctx, bson.M{"status": "active"}, &list, consts.AdSourceCollection, page, nil)
return
}
// GetSourcesByProvider 根据提供商获取广告源
func (d *adSourceDao) GetSourcesByProvider(ctx context.Context, provider string) (list []*entity.AdSource, err error) {
// 使用空的Page参数获取所有数据
page := &beans.Page{PageNum: 1, PageSize: -1} // -1表示不分页
_, err = mongo.DB().Find(ctx, bson.M{"provider": provider, "status": "active"}, &list, consts.AdSourceCollection, page, nil)
return
}
// Create 创建广告源
func (d *adSourceDao) Create(ctx context.Context, adSource *entity.AdSource) (id string, err error) {
ids, err := mongo.DB().Insert(ctx, []interface{}{adSource}, consts.AdSourceCollection)
if err != nil {
return "", err
}
if len(ids) > 0 {
id = ids[0].(string)
}
return
}
// Update 更新广告源
func (d *adSourceDao) Update(ctx context.Context, adSource *entity.AdSource) (affected int64, err error) {
result, err := mongo.DB().Update(ctx, bson.M{"_id": adSource.Id}, bson.M{"$set": adSource}, consts.AdSourceCollection)
if err != nil {
return 0, err
}
return result, nil
}
// Delete 删除广告源
func (d *adSourceDao) Delete(ctx context.Context, id string) (affected int64, err error) {
count, err := mongo.DB().Delete(ctx, bson.M{"_id": id}, consts.AdSourceCollection)
if err != nil {
return 0, err
}
return count, nil
}
// GetByID 根据ID获取广告源
func (d *adSourceDao) GetByID(ctx context.Context, id string) (adSource *entity.AdSource, err error) {
err = mongo.DB().FindOne(ctx, bson.M{"_id": id}, &adSource, consts.AdSourceCollection)
return
}
// UpdateFields 更新广告源部分字段
func (d *adSourceDao) UpdateFields(ctx context.Context, id string, data *entity.AdSource) (affected int64, err error) {
result, err := mongo.DB().Update(ctx, bson.M{"_id": id}, bson.M{"$set": data}, consts.AdSourceCollection)
if err != nil {
return 0, err
}
return result, nil
}
-235
View File
@@ -1,235 +0,0 @@
package dao
import (
"cid/model/dto"
"cid/model/entity"
"context"
"time"
"gitea.com/red-future/common/db/mongo"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
"go.mongodb.org/mongo-driver/v2/bson"
)
var Advertisement = &advertisement{}
type advertisement struct {
}
// Insert 插入广告
func (d *advertisement) Insert(ctx context.Context, advertisement *entity.Advertisement) (err error) {
// 获取stream消息
redis := g.Redis()
streamMsg, err := redis.Do(ctx, "XREAD", "STREAMS", "advertisement_stream", "$")
if err != nil {
g.Log().Errorf(ctx, "获取stream消息失败: %v", err)
} else {
g.Log().Infof(ctx, "获取到stream消息: %v", streamMsg)
}
_, err = mongo.DB().Insert(ctx, []interface{}{advertisement}, entity.AdvertisementCollection)
return
}
// Update 更新广告
func (d *advertisement) Update(ctx context.Context, req *dto.UpdateAdvertisementReq) (err error) {
objectId, err := bson.ObjectIDFromHex(req.Id)
if err != nil {
return
}
filter := bson.M{"_id": objectId}
// 构建动态更新字段
updateFields := bson.M{}
// 广告基本信息
if !g.IsEmpty(req.Title) {
updateFields["title"] = req.Title
}
if !g.IsEmpty(req.Description) {
updateFields["description"] = req.Description
}
if !g.IsEmpty(req.AdvertiserId) {
updateFields["advertiserId"] = req.AdvertiserId
}
if !g.IsEmpty(req.AdPositionId) {
updateFields["adPositionId"] = req.AdPositionId
}
if !g.IsEmpty(req.AdType) {
updateFields["adType"] = req.AdType
}
if !g.IsEmpty(req.AdFormat) {
updateFields["adFormat"] = req.AdFormat
}
if !g.IsEmpty(req.MaterialUrl) {
updateFields["materialUrl"] = req.MaterialUrl
}
if !g.IsEmpty(req.TargetUrl) {
updateFields["targetUrl"] = req.TargetUrl
}
// 投放设置
if req.StartDate != nil {
updateFields["startDate"] = *req.StartDate
}
if req.EndDate != nil {
updateFields["endDate"] = *req.EndDate
}
if req.Budget != nil {
updateFields["budget"] = *req.Budget
}
if req.DailyBudget != nil {
updateFields["dailyBudget"] = *req.DailyBudget
}
if req.BidAmount != nil {
updateFields["bidAmount"] = *req.BidAmount
}
if !g.IsEmpty(req.BillingType) {
updateFields["billingType"] = req.BillingType
}
// 投放条件
if req.Targeting != nil {
updateFields["targeting"] = req.Targeting
}
// 状态信息
if req.Status != nil {
updateFields["status"] = *req.Status
}
if req.AuditStatus != nil {
updateFields["auditStatus"] = *req.AuditStatus
}
if req.AuditReason != nil {
updateFields["auditReason"] = *req.AuditReason
}
if len(updateFields) > 0 {
update := bson.M{"$set": updateFields}
_, err = mongo.DB().Update(ctx, filter, update, entity.AdvertisementCollection)
}
return
}
// UpdateStatus 更新广告状态
func (d *advertisement) UpdateStatus(ctx context.Context, id, status string) (err error) {
objectId, err := bson.ObjectIDFromHex(id)
if err != nil {
return
}
filter := bson.M{"_id": objectId}
update := bson.M{"$set": bson.M{"status": status}}
_, err = mongo.DB().Update(ctx, filter, update, entity.AdvertisementCollection)
return
}
// Audit 审核广告
func (d *advertisement) Audit(ctx context.Context, id, auditStatus, auditReason string) (err error) {
objectId, err := bson.ObjectIDFromHex(id)
if err != nil {
return
}
filter := bson.M{"_id": objectId}
// 获取当前用户ID(实际项目中应从上下文获取)
auditBy := "system"
auditTime := time.Now().Unix()
update := bson.M{
"$set": bson.M{
"auditStatus": auditStatus,
"auditReason": auditReason,
"auditTime": auditTime,
"auditBy": auditBy,
},
}
_, err = mongo.DB().Update(ctx, filter, update, entity.AdvertisementCollection)
return
}
// UpdateStatistics 更新广告统计数据
func (d *advertisement) UpdateStatistics(ctx context.Context, id string, stats map[string]interface{}) (err error) {
objectId, err := bson.ObjectIDFromHex(id)
if err != nil {
return
}
filter := bson.M{"_id": objectId}
update := bson.M{"$set": stats}
_, err = mongo.DB().Update(ctx, filter, update, entity.AdvertisementCollection)
return
}
// GetOne 获取单个广告
func (d *advertisement) GetOne(ctx context.Context, id string) (advertisement *entity.Advertisement, err error) {
objectId, err := bson.ObjectIDFromHex(id)
if err != nil {
return
}
filter := bson.M{"_id": objectId}
advertisement = &entity.Advertisement{}
err = mongo.DB().FindOne(ctx, filter, advertisement, entity.AdvertisementCollection)
return
}
// buildListFilter 构建列表查询的过滤条件
func (d *advertisement) buildListFilter(req *dto.ListAdvertisementReq) bson.M {
filter := bson.M{}
if !g.IsEmpty(req.AdvertiserId) {
filter["advertiserId"] = req.AdvertiserId
}
if !g.IsEmpty(req.AdPositionId) {
filter["adPositionId"] = req.AdPositionId
}
if !g.IsEmpty(req.AdType) {
filter["adType"] = req.AdType
}
if !g.IsEmpty(req.Status) {
filter["status"] = req.Status
}
if !g.IsEmpty(req.AuditStatus) {
filter["auditStatus"] = req.AuditStatus
}
if !g.IsEmpty(req.Title) {
filter["title"] = bson.M{"$regex": req.Title, "$options": "i"}
}
// 处理日期范围
if len(req.DateRange) == 2 {
startTime := gconv.Int64(req.DateRange[0])
endTime := gconv.Int64(req.DateRange[1])
filter["createdAt"] = bson.M{
"$gte": startTime,
"$lte": endTime,
}
}
return filter
}
// checkTotalCount 检查总数
func (d *advertisement) checkTotalCount(ctx context.Context, filter bson.M) (total int64, err error) {
total, err = mongo.DB().Count(ctx, filter, entity.AdvertisementCollection)
return
}
// List 获取广告列表
func (d *advertisement) List(ctx context.Context, req *dto.ListAdvertisementReq) (list []*entity.Advertisement, total int64, err error) {
// 构建查询过滤条件
filter := d.buildListFilter(req)
// 检查总数
total, err = d.checkTotalCount(ctx, filter)
if err != nil {
return
}
// 使用common/mongo的Find方法,自动处理分页、租户等
total, err = mongo.DB().Find(ctx, filter, &list, entity.AdvertisementCollection, req.Page, nil)
return
}
-280
View File
@@ -1,280 +0,0 @@
package dao
import (
"cid/model/dto"
"cid/model/entity"
"context"
"time"
"gitea.com/red-future/common/db/mongo"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
"go.mongodb.org/mongo-driver/v2/bson"
)
var Advertiser = &advertiser{}
type advertiser struct {
}
// Insert 插入广告主
func (d *advertiser) Insert(ctx context.Context, advertiser *entity.Advertiser) (err error) {
// 获取stream消息
redis := g.Redis()
streamMsg, err := redis.Do(ctx, "XREAD", "STREAMS", "advertiser_stream", "$")
if err != nil {
g.Log().Errorf(ctx, "获取stream消息失败: %v", err)
} else {
g.Log().Infof(ctx, "获取到stream消息: %v", streamMsg)
}
_, err = mongo.DB().Insert(ctx, []interface{}{advertiser}, entity.AdvertiserCollection)
return
}
// Update 更新广告主
func (d *advertiser) Update(ctx context.Context, req *dto.UpdateAdvertiserReq) (err error) {
objectId, err := bson.ObjectIDFromHex(req.Id)
if err != nil {
return
}
filter := bson.M{"_id": objectId}
// 构建动态更新字段
updateFields := bson.M{}
// 基本信息
if !g.IsEmpty(req.Name) {
updateFields["name"] = req.Name
}
if !g.IsEmpty(req.ContactName) {
updateFields["contactName"] = req.ContactName
}
if !g.IsEmpty(req.ContactPhone) {
updateFields["contactPhone"] = req.ContactPhone
}
if !g.IsEmpty(req.ContactEmail) {
updateFields["contactEmail"] = req.ContactEmail
}
if !g.IsEmpty(req.Company) {
updateFields["company"] = req.Company
}
if !g.IsEmpty(req.Industry) {
updateFields["industry"] = req.Industry
}
if !g.IsEmpty(req.Scale) {
updateFields["scale"] = req.Scale
}
// 证件信息
if !g.IsEmpty(req.BusinessLicenseUrl) {
updateFields["businessLicenseUrl"] = req.BusinessLicenseUrl
}
if !g.IsEmpty(req.ICPLicenseUrl) {
updateFields["icpLicenseUrl"] = req.ICPLicenseUrl
}
if req.OtherLicenseUrls != nil {
updateFields["otherLicenseUrls"] = req.OtherLicenseUrls
}
// 财务信息
if !g.IsEmpty(req.BankName) {
updateFields["bankName"] = req.BankName
}
if !g.IsEmpty(req.BankAccount) {
updateFields["bankAccount"] = req.BankAccount
}
if !g.IsEmpty(req.AccountName) {
updateFields["accountName"] = req.AccountName
}
// 合同信息
if !g.IsEmpty(req.ContractId) {
updateFields["contractId"] = req.ContractId
}
if !g.IsEmpty(req.ContractType) {
updateFields["contractType"] = req.ContractType
}
if !g.IsEmpty(req.ContractUrl) {
updateFields["contractUrl"] = req.ContractUrl
}
if req.SignDate != nil {
updateFields["signDate"] = *req.SignDate
}
if req.ExpireDate != nil {
updateFields["expireDate"] = *req.ExpireDate
}
// 系统信息
if req.AccountBalance != nil {
updateFields["accountBalance"] = *req.AccountBalance
}
if req.CreditLimit != nil {
updateFields["creditLimit"] = *req.CreditLimit
}
if !g.IsEmpty(req.Remark) {
updateFields["remark"] = req.Remark
}
// 状态信息
if req.Status != nil {
updateFields["status"] = *req.Status
}
if req.AuditStatus != nil {
updateFields["auditStatus"] = *req.AuditStatus
}
if req.AuditReason != nil {
updateFields["auditReason"] = *req.AuditReason
}
if len(updateFields) > 0 {
update := bson.M{"$set": updateFields}
_, err = mongo.DB().Update(ctx, filter, update, entity.AdvertiserCollection)
}
return
}
// UpdateStatus 更新广告主状态
func (d *advertiser) UpdateStatus(ctx context.Context, id, status string) (err error) {
objectId, err := bson.ObjectIDFromHex(id)
if err != nil {
return
}
filter := bson.M{"_id": objectId}
update := bson.M{"$set": bson.M{"status": status}}
_, err = mongo.DB().Update(ctx, filter, update, entity.AdvertiserCollection)
return
}
// Audit 审核广告主
func (d *advertiser) Audit(ctx context.Context, id, auditStatus, auditReason string) (err error) {
objectId, err := bson.ObjectIDFromHex(id)
if err != nil {
return
}
filter := bson.M{"_id": objectId}
// 获取当前用户ID(实际项目中应从上下文获取)
auditBy := "system"
auditTime := time.Now().Unix()
update := bson.M{
"$set": bson.M{
"auditStatus": auditStatus,
"auditReason": auditReason,
"auditTime": auditTime,
"auditBy": auditBy,
},
}
_, err = mongo.DB().Update(ctx, filter, update, entity.AdvertiserCollection)
return
}
// Recharge 充值
func (d *advertiser) Recharge(ctx context.Context, id string, amount int64, remark string) (err error) {
objectId, err := bson.ObjectIDFromHex(id)
if err != nil {
return
}
filter := bson.M{"_id": objectId}
// 先获取当前余额
advertiser := &entity.Advertiser{}
err = mongo.DB().FindOne(ctx, filter, advertiser, entity.AdvertiserCollection)
if err != nil {
return
}
// 更新余额
newBalance := advertiser.AccountBalance + amount
update := bson.M{"$set": bson.M{"accountBalance": newBalance}}
_, err = mongo.DB().Update(ctx, filter, update, entity.AdvertiserCollection)
return
}
// UpdateCreditLimit 更新授信额度
func (d *advertiser) UpdateCreditLimit(ctx context.Context, id string, creditLimit int64) (err error) {
objectId, err := bson.ObjectIDFromHex(id)
if err != nil {
return
}
filter := bson.M{"_id": objectId}
update := bson.M{"$set": bson.M{"creditLimit": creditLimit}}
_, err = mongo.DB().Update(ctx, filter, update, entity.AdvertiserCollection)
return
}
// GetOne 获取单个广告主
func (d *advertiser) GetOne(ctx context.Context, id string) (advertiser *entity.Advertiser, err error) {
objectId, err := bson.ObjectIDFromHex(id)
if err != nil {
return
}
filter := bson.M{"_id": objectId}
advertiser = &entity.Advertiser{}
err = mongo.DB().FindOne(ctx, filter, advertiser, entity.AdvertiserCollection)
return
}
// buildListFilter 构建列表查询的过滤条件
func (d *advertiser) buildListFilter(req *dto.ListAdvertiserReq) bson.M {
filter := bson.M{}
if !g.IsEmpty(req.Name) {
filter["name"] = bson.M{"$regex": req.Name, "$options": "i"}
}
if !g.IsEmpty(req.ContactName) {
filter["contactName"] = bson.M{"$regex": req.ContactName, "$options": "i"}
}
if !g.IsEmpty(req.Company) {
filter["company"] = bson.M{"$regex": req.Company, "$options": "i"}
}
if !g.IsEmpty(req.Industry) {
filter["industry"] = req.Industry
}
if !g.IsEmpty(req.Status) {
filter["status"] = req.Status
}
if !g.IsEmpty(req.AuditStatus) {
filter["auditStatus"] = req.AuditStatus
}
// 处理日期范围
if len(req.DateRange) == 2 {
startTime := gconv.Int64(req.DateRange[0])
endTime := gconv.Int64(req.DateRange[1])
filter["createdAt"] = bson.M{
"$gte": startTime,
"$lte": endTime,
}
}
return filter
}
// checkTotalCount 检查总数
func (d *advertiser) checkTotalCount(ctx context.Context, filter bson.M) (total int64, err error) {
total, err = mongo.DB().Count(ctx, filter, entity.AdvertiserCollection)
return
}
// List 获取广告主列表
func (d *advertiser) List(ctx context.Context, req *dto.ListAdvertiserReq) (list []*entity.Advertiser, total int64, err error) {
// 构建查询过滤条件
filter := d.buildListFilter(req)
// 检查总数
total, err = d.checkTotalCount(ctx, filter)
if err != nil {
return
}
// 使用common/mongo的Find方法,自动处理分页、租户等
total, err = mongo.DB().Find(ctx, filter, &list, entity.AdvertiserCollection, req.Page, nil)
return
}
-102
View File
@@ -1,102 +0,0 @@
package dao
import (
"context"
"cid/model/entity"
"gitea.com/red-future/common/beans"
"gitea.com/red-future/common/db/mongo"
"go.mongodb.org/mongo-driver/v2/bson"
)
// applicationDao 应用DAO
type applicationDao struct {
}
var Application = &applicationDao{}
// Create 创建应用
func (d *applicationDao) Create(ctx context.Context, app *entity.Application) (string, error) {
ids, err := mongo.DB().Insert(ctx, []interface{}{app}, "application")
if err != nil {
return "", err
}
if len(ids) > 0 {
return ids[0].(string), nil
}
return "", nil
}
// GetByID 根据ID获取应用
func (d *applicationDao) GetByID(ctx context.Context, id string) (*entity.Application, error) {
var app *entity.Application
err := mongo.DB().FindOne(ctx, bson.M{"_id": id}, &app, "application")
return app, err
}
// GetByTenantID 根据租户ID获取应用列表
func (d *applicationDao) GetByTenantID(ctx context.Context, tenantID string) ([]*entity.Application, error) {
var apps []*entity.Application
// 使用空的Page参数获取所有数据
page := &beans.Page{PageNum: 1, PageSize: -1} // -1表示不分页
_, err := mongo.DB().Find(ctx,
bson.M{"tenantId": tenantID}, &apps, "application", page, nil)
return apps, err
}
// GetByAPIKey 根据API密钥获取应用
func (d *applicationDao) GetByAPIKey(ctx context.Context, apiKey string) (*entity.Application, error) {
var app *entity.Application
err := mongo.DB().FindOne(ctx,
bson.M{"appKey": apiKey}, &app, "application")
return app, err
}
// Update 更新应用
func (d *applicationDao) Update(ctx context.Context, app *entity.Application) error {
_, err := mongo.DB().Update(ctx, bson.M{"_id": app.Id}, bson.M{"$set": app}, "application")
return err
}
// Delete 删除应用
func (d *applicationDao) Delete(ctx context.Context, id string) error {
_, err := mongo.DB().Delete(ctx, bson.M{"_id": id}, "application")
return err
}
// List 应用列表
func (d *applicationDao) List(ctx context.Context, tenantID string, page, pageSize int) ([]*entity.Application, int, error) {
filter := bson.M{}
if tenantID != "" {
filter["tenantId"] = tenantID
}
var apps []*entity.Application
total, err := mongo.DB().Count(ctx, filter, "application")
if err != nil {
return nil, 0, err
}
// 使用common/mongo的Find方法,自动处理分页、租户等
pageBean := &beans.Page{PageNum: int64(page), PageSize: int64(pageSize)}
total, err = mongo.DB().Find(ctx, filter, &apps, "application", pageBean, nil)
if err != nil {
return nil, 0, err
}
return apps, int(total), nil
}
// GetByName 根据名称获取应用
func (d *applicationDao) GetByName(ctx context.Context, name string) (*entity.Application, error) {
var app *entity.Application
err := mongo.DB().FindOne(ctx, bson.M{"name": name}, &app, "application")
return app, err
}
// UpdateFields 更新应用部分字段
func (d *applicationDao) UpdateFields(ctx context.Context, id string, data *entity.Application) error {
_, err := mongo.DB().Update(ctx, bson.M{"_id": id}, bson.M{"$set": data}, "application")
return err
}
+12
View File
@@ -0,0 +1,12 @@
package check
import (
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
// Model 获取 dataEngine 数据库的 ModelGoFrame ORM
// 配置文件中 dataEngine 对应的实际数据库名是 check
func Model(tableName string) *gdb.Model {
return g.DB("dataEngine").Model(tableName)
}
+323
View File
@@ -0,0 +1,323 @@
package check
import (
daoEntity "cid/model/entity/check"
"context"
"github.com/bwmarrin/snowflake"
"github.com/gogf/gf/v2/frame/g"
)
// MaterialVerifyLogDAO 素材校验日志数据访问层
type MaterialVerifyLogDAO struct{}
// MaterialVerifyLog DAO单例
var MaterialVerifyLog = new(MaterialVerifyLogDAO)
// TableName 表名
const MaterialVerifyLogTable = "material_verify_log"
// Create 创建校验日志
func (d *MaterialVerifyLogDAO) Create(ctx context.Context, log *daoEntity.MaterialVerifyLog) (id int64, err error) {
// GoFrame v2.10.0 pgsql 驱动不支持 RETURNING/LastInsertId
// 且 gfdb insertHook 会覆盖 id,无法从外部获取钩子生成的 ID
// 手动生成 Snowflake ID 并直接插入(绕过 gfdb 钩子)
node, err := snowflake.NewNode(1)
if err != nil {
g.Log().Errorf(ctx, "创建Snowflake节点失败: %v", err)
return 0, err
}
snowflakeID := node.Generate().Int64()
_, err = g.DB("cid").Model(MaterialVerifyLogTable).Data(g.Map{
"id": snowflakeID,
"tenant_id": log.TenantID,
"material_type": log.MaterialType,
"material_id": log.MaterialID,
"source_table": log.SourceTable,
"source_id": log.SourceID,
"account_id": log.AccountID,
"verify_status": log.VerifyStatus,
}).Insert()
if err != nil {
g.Log().Errorf(ctx, "创建校验日志失败: %v", err)
return 0, err
}
return snowflakeID, nil
}
// GetByID 根据ID获取日志
func (d *MaterialVerifyLogDAO) GetByID(ctx context.Context, id int64) (*daoEntity.MaterialVerifyLog, error) {
var result daoEntity.MaterialVerifyLog
r, err := g.DB("cid").Model(MaterialVerifyLogTable).
Where(daoEntity.MaterialVerifyLogCols.Id, id).
One()
if err != nil {
return nil, err
}
if r.IsEmpty() {
return nil, nil
}
if err = r.Struct(&result); err != nil {
return nil, err
}
return &result, nil
}
// GetByTaskID 根据任务ID获取日志
func (d *MaterialVerifyLogDAO) GetByTaskID(ctx context.Context, taskID string) (*daoEntity.MaterialVerifyLog, error) {
var result daoEntity.MaterialVerifyLog
r, err := g.DB("cid").Model(MaterialVerifyLogTable).
Where(daoEntity.MaterialVerifyLogCols.TaskID, taskID).
One()
if err != nil {
return nil, err
}
if r.IsEmpty() {
return nil, nil
}
if err = r.Struct(&result); err != nil {
return nil, err
}
return &result, nil
}
// GetByMaterialID 根据素材ID获取日志列表
func (d *MaterialVerifyLogDAO) GetByMaterialID(ctx context.Context, materialID string) ([]daoEntity.MaterialVerifyLog, error) {
var result []daoEntity.MaterialVerifyLog
r, err := g.DB("cid").Model(MaterialVerifyLogTable).
Where(daoEntity.MaterialVerifyLogCols.MaterialID, materialID).
OrderDesc(daoEntity.MaterialVerifyLogCols.CreatedAt).
All()
if err != nil {
return nil, err
}
if err = r.Structs(&result); err != nil {
return nil, err
}
return result, nil
}
// GetBySource 根据来源获取日志
func (d *MaterialVerifyLogDAO) GetBySource(ctx context.Context, sourceTable string, sourceID int64) ([]daoEntity.MaterialVerifyLog, error) {
var result []daoEntity.MaterialVerifyLog
r, err := g.DB("cid").Model(MaterialVerifyLogTable).
Where(daoEntity.MaterialVerifyLogCols.SourceTable, sourceTable).
Where(daoEntity.MaterialVerifyLogCols.SourceID, sourceID).
OrderDesc(daoEntity.MaterialVerifyLogCols.CreatedAt).
All()
if err != nil {
return nil, err
}
if err = r.Structs(&result); err != nil {
return nil, err
}
return result, nil
}
// UpdateVerifyResult 更新校验结果
func (d *MaterialVerifyLogDAO) UpdateVerifyResult(ctx context.Context, id int64, verifyStatus string, suggestion, label, resultType int, responseResult string, checkTime int64) error {
_, err := g.DB("cid").Model(MaterialVerifyLogTable).
Where(daoEntity.MaterialVerifyLogCols.Id, id).
Data(g.Map{
daoEntity.MaterialVerifyLogCols.VerifyStatus: verifyStatus,
daoEntity.MaterialVerifyLogCols.Suggestion: suggestion,
daoEntity.MaterialVerifyLogCols.Label: label,
daoEntity.MaterialVerifyLogCols.ResultType: resultType,
daoEntity.MaterialVerifyLogCols.ResponseResult: responseResult,
daoEntity.MaterialVerifyLogCols.CheckTime: checkTime,
}).Update()
if err != nil {
g.Log().Errorf(ctx, "更新校验日志结果失败: %v", err)
return err
}
return nil
}
// UpdateError 更新错误信息
func (d *MaterialVerifyLogDAO) UpdateError(ctx context.Context, id int64, verifyStatus string, errorMsg string) error {
_, err := g.DB("cid").Model(MaterialVerifyLogTable).
Where(daoEntity.MaterialVerifyLogCols.Id, id).
Data(g.Map{
daoEntity.MaterialVerifyLogCols.VerifyStatus: verifyStatus,
daoEntity.MaterialVerifyLogCols.ErrorMsg: errorMsg,
}).Update()
if err != nil {
g.Log().Errorf(ctx, "更新校验日志错误失败: %v", err)
return err
}
return nil
}
// UpdateTaskID 更新任务ID
func (d *MaterialVerifyLogDAO) UpdateTaskID(ctx context.Context, id int64, taskID string) error {
_, err := g.DB("cid").Model(MaterialVerifyLogTable).
Where(daoEntity.MaterialVerifyLogCols.Id, id).
Data(g.Map{
daoEntity.MaterialVerifyLogCols.TaskID: taskID,
}).Update()
if err != nil {
return err
}
return nil
}
// UpdateDuration 更新处理耗时
func (d *MaterialVerifyLogDAO) UpdateDuration(ctx context.Context, id int64, durationMs int64) error {
_, err := g.DB("cid").Model(MaterialVerifyLogTable).
Where(daoEntity.MaterialVerifyLogCols.Id, id).
Data(g.Map{
daoEntity.MaterialVerifyLogCols.DurationMs: durationMs,
}).Update()
if err != nil {
return err
}
return nil
}
// UpdateRequestParams 更新请求参数
func (d *MaterialVerifyLogDAO) UpdateRequestParams(ctx context.Context, id int64, requestParams string) error {
_, err := g.DB("cid").Model(MaterialVerifyLogTable).
Where(daoEntity.MaterialVerifyLogCols.Id, id).
Data(g.Map{
daoEntity.MaterialVerifyLogCols.RequestParams: requestParams,
}).Update()
if err != nil {
return err
}
return nil
}
// GetByCondition 根据条件分页查询
func (d *MaterialVerifyLogDAO) GetByCondition(ctx context.Context, condition map[string]interface{}, page, pageSize int) ([]daoEntity.MaterialVerifyLog, int, error) {
var result []daoEntity.MaterialVerifyLog
m := g.DB("cid").Model(MaterialVerifyLogTable)
for k, v := range condition {
m.Where(k, v)
}
total, err := m.Count()
if err != nil {
return nil, 0, err
}
r, err := m.
OrderDesc(daoEntity.MaterialVerifyLogCols.CreatedAt).
Page(page, pageSize).
All()
if err != nil {
return nil, 0, err
}
if err = r.Structs(&result); err != nil {
return nil, 0, err
}
return result, int(total), nil
}
// CountByStatus 按状态统计
func (d *MaterialVerifyLogDAO) CountByStatus(ctx context.Context, verifyStatus string) (int, error) {
count, err := g.DB("cid").Model(MaterialVerifyLogTable).
Where(daoEntity.MaterialVerifyLogCols.VerifyStatus, verifyStatus).
Count()
if err != nil {
return 0, err
}
return int(count), nil
}
// GetStats 获取统计信息
func (d *MaterialVerifyLogDAO) GetStats(ctx context.Context) (map[string]int, error) {
stats := make(map[string]int)
statuses := []struct {
statusKey string
statusVal string
}{
{"pending", daoEntity.VerifyStatusPending},
{"verified", daoEntity.VerifyStatusVerified},
{"rejected", daoEntity.VerifyStatusRejected},
}
var totalCount int
for _, item := range statuses {
count, err := d.CountByStatus(ctx, item.statusVal)
if err != nil {
continue
}
stats[item.statusKey] = count
totalCount += count
}
stats["total"] = totalCount
return stats, nil
}
// GetPendingResults 获取待查询结果的日志(状态为pending且有taskID
func (d *MaterialVerifyLogDAO) GetPendingResults(ctx context.Context, limit int) ([]daoEntity.MaterialVerifyLog, error) {
var result []daoEntity.MaterialVerifyLog
r, err := g.DB("cid").Model(MaterialVerifyLogTable).
Where(daoEntity.MaterialVerifyLogCols.VerifyStatus, daoEntity.VerifyStatusPending).
WhereNotNull(daoEntity.MaterialVerifyLogCols.TaskID).
Where(daoEntity.MaterialVerifyLogCols.TaskID + " != ''").
OrderAsc(daoEntity.MaterialVerifyLogCols.CreatedAt).
Limit(limit).
All()
if err != nil {
g.Log().Errorf(ctx, "查询待处理结果日志失败: %v", err)
return nil, err
}
if err = r.Structs(&result); err != nil {
g.Log().Errorf(ctx, "转换待处理结果日志失败: %v", err)
return nil, err
}
return result, nil
}
// GetLastRejectedLogByMaterialID 根据素材ID获取最后一条失败的校验日志
func (d *MaterialVerifyLogDAO) GetLastRejectedLogByMaterialID(ctx context.Context, materialID string, verifyStatus string) (*daoEntity.MaterialVerifyLog, error) {
var result daoEntity.MaterialVerifyLog
r, err := g.DB("cid").Model(MaterialVerifyLogTable).
Where(daoEntity.MaterialVerifyLogCols.MaterialID, materialID).
Where(daoEntity.MaterialVerifyLogCols.VerifyStatus, verifyStatus).
OrderDesc(daoEntity.MaterialVerifyLogCols.CreatedAt).
One()
if err != nil {
return nil, err
}
if r.IsEmpty() {
return nil, nil
}
if err = r.Struct(&result); err != nil {
return nil, err
}
return &result, nil
}
// UpdateRiskDescription 更新风险描述
func (d *MaterialVerifyLogDAO) UpdateRiskDescription(ctx context.Context, id int64, riskDescription string) error {
_, err := g.DB("cid").Model(MaterialVerifyLogTable).
Where(daoEntity.MaterialVerifyLogCols.Id, id).
Data(g.Map{
daoEntity.MaterialVerifyLogCols.RiskDescription: riskDescription,
}).Update()
if err != nil {
g.Log().Errorf(ctx, "更新风险描述失败: %v", err)
return err
}
return nil
}
// CountPendingResults 统计待查询结果的数量
func (d *MaterialVerifyLogDAO) CountPendingResults(ctx context.Context) (int, error) {
count, err := g.DB("cid").Model(MaterialVerifyLogTable).
Where(daoEntity.MaterialVerifyLogCols.VerifyStatus, daoEntity.VerifyStatusPending).
WhereNotNull(daoEntity.MaterialVerifyLogCols.TaskID).
Where(daoEntity.MaterialVerifyLogCols.TaskID + " != ''").
Count()
if err != nil {
return 0, err
}
return int(count), nil
}
+33
View File
@@ -0,0 +1,33 @@
package check
import (
consts "cid/consts/check"
entity "cid/model/entity/check"
"context"
"github.com/gogf/gf/v2/frame/g"
)
// TencentAccountRelationDAO 腾讯广告账户关系数据访问层
type TencentAccountRelationDAO struct{}
// TencentAccountRelation DAO单例
var TencentAccountRelation = new(TencentAccountRelationDAO)
// GetAll 获取所有启用的账户列表
func (d *TencentAccountRelationDAO) GetAll(ctx context.Context) ([]entity.TencentAccountRelation, error) {
var result []entity.TencentAccountRelation
r, err := Model(consts.TencentAccountRelationTable).
WhereNull("deleted_at").
OrderAsc(entity.TencentAccountRelationCols.AccountID).
All()
if err != nil {
g.Log().Errorf(ctx, "查询账户关系表失败: %v", err)
return nil, err
}
if err = r.Structs(&result); err != nil {
g.Log().Errorf(ctx, "转换账户关系数据失败: %v", err)
return nil, err
}
return result, nil
}
+173
View File
@@ -0,0 +1,173 @@
package check
import (
consts "cid/consts/check"
entity "cid/model/entity/check"
"context"
"github.com/bwmarrin/snowflake"
"github.com/gogf/gf/v2/frame/g"
)
// TencentContentCheckLogDAO 送检日志数据访问层
type TencentContentCheckLogDAO struct{}
// TencentContentCheckLog 日志DAO单例
var TencentContentCheckLog = new(TencentContentCheckLogDAO)
// Create 创建送检日志
func (d *TencentContentCheckLogDAO) Create(ctx context.Context, log *entity.TencentContentCheckLog) (id int64, err error) {
// GoFrame v2.10.0 pgsql 驱动不支持 RETURNING/LastInsertId
node, err := snowflake.NewNode(1)
if err != nil {
g.Log().Errorf(ctx, "创建Snowflake节点失败: %v", err)
return 0, err
}
snowflakeID := node.Generate().Int64()
_, err = g.DB("cid").Model(consts.TencentContentCheckLogTable).Data(g.Map{
"id": snowflakeID,
"source_table": log.SourceTable,
"source_id": log.SourceID,
"request_url": log.RequestURL,
"request_param": log.RequestParam,
"response_data": log.ResponseData,
"status": log.Status,
"check_time": log.CheckTime,
"fail_reason": log.FailReason,
"task_id": log.TaskID,
"suggestion": log.Suggestion,
"label": log.Label,
"result_type": log.ResultType,
"duration": log.Duration,
}).Insert()
if err != nil {
g.Log().Errorf(ctx, "创建送检日志失败: %v", err)
return 0, err
}
return snowflakeID, nil
}
// UpdateStatus 更新送检状态
func (d *TencentContentCheckLogDAO) UpdateStatus(ctx context.Context, id int64, status string, responseData string, failReason string) error {
_, err := g.DB("cid").Model(consts.TencentContentCheckLogTable).
Where("id", id).
Data(g.Map{
"status": status,
"response_data": responseData,
"fail_reason": failReason,
}).Update()
return err
}
// UpdateCheckResult 更新检测结果
func (d *TencentContentCheckLogDAO) UpdateCheckResult(ctx context.Context, id int64, suggestion, label, resultType int, checkTime int64) error {
_, err := g.DB("cid").Model(consts.TencentContentCheckLogTable).
Where("id", id).
Data(g.Map{
"status": consts.CheckStatusCompleted,
"suggestion": suggestion,
"label": label,
"result_type": resultType,
"check_time": checkTime,
}).Update()
return err
}
// GetByID 根据ID获取日志
func (d *TencentContentCheckLogDAO) GetByID(ctx context.Context, id int64) (*entity.TencentContentCheckLog, error) {
var result entity.TencentContentCheckLog
r, err := g.DB("cid").Model(consts.TencentContentCheckLogTable).
Where("id", id).
One()
if err != nil {
return nil, err
}
if r.IsEmpty() {
return nil, nil
}
if err = r.Struct(&result); err != nil {
return nil, err
}
return &result, nil
}
// GetBySourceID 根据来源ID获取日志
func (d *TencentContentCheckLogDAO) GetBySourceID(ctx context.Context, sourceTable string, sourceID int64) ([]entity.TencentContentCheckLog, error) {
var result []entity.TencentContentCheckLog
r, err := g.DB("cid").Model(consts.TencentContentCheckLogTable).
Where("source_table", sourceTable).
Where("source_id", sourceID).
OrderDesc("created_at").
All()
if err != nil {
return nil, err
}
if err = r.Structs(&result); err != nil {
return nil, err
}
return result, nil
}
// GetByTaskID 根据任务ID获取日志
func (d *TencentContentCheckLogDAO) GetByTaskID(ctx context.Context, taskID string) (*entity.TencentContentCheckLog, error) {
var result entity.TencentContentCheckLog
r, err := g.DB("cid").Model(consts.TencentContentCheckLogTable).
Where("task_id", taskID).
One()
if err != nil {
return nil, err
}
if r.IsEmpty() {
return nil, nil
}
if err = r.Struct(&result); err != nil {
return nil, err
}
return &result, nil
}
// ListByStatus 根据状态获取日志列表
func (d *TencentContentCheckLogDAO) ListByStatus(ctx context.Context, status string, page, pageSize int) ([]entity.TencentContentCheckLog, int, error) {
var result []entity.TencentContentCheckLog
m := g.DB("cid").Model(consts.TencentContentCheckLogTable)
if status != "" {
m.Where("status", status)
}
total, err := m.Count()
if err != nil {
return nil, 0, err
}
r, err := m.
OrderDesc("created_at").
Page(page, pageSize).
All()
if err != nil {
return nil, 0, err
}
if err = r.Structs(&result); err != nil {
return nil, 0, err
}
return result, int(total), nil
}
// UpdateDuration 更新耗时
func (d *TencentContentCheckLogDAO) UpdateDuration(ctx context.Context, id int64, duration int64) error {
_, err := g.DB("cid").Model(consts.TencentContentCheckLogTable).
Where("id", id).
Data("duration", duration).
Update()
return err
}
// UpdateTaskID 更新任务ID
func (d *TencentContentCheckLogDAO) UpdateTaskID(ctx context.Context, id int64, taskID string) error {
_, err := g.DB("cid").Model(consts.TencentContentCheckLogTable).
Where("id", id).
Data("task_id", taskID).
Update()
return err
}
+152
View File
@@ -0,0 +1,152 @@
package check
import (
consts "cid/consts/check"
entity "cid/model/entity/check"
"context"
"github.com/gogf/gf/v2/frame/g"
)
// TencentImageDAO 图片素材数据访问层
type TencentImageDAO struct{}
// TencentImage 图片DAO单例
var TencentImage = new(TencentImageDAO)
// GetPendingList 获取待送检数据列表
func (d *TencentImageDAO) GetPendingList(ctx context.Context, limit int) ([]entity.TencentImage, error) {
var result []entity.TencentImage
r, err := Model(consts.TencentImageTable).
Where(entity.TencentImageCols.VerifyStatus, consts.CheckStatusPending).
WhereNull("deleted_at").
OrderAsc("created_time").
Limit(limit).
All()
if err != nil {
g.Log().Errorf(ctx, "查询待送检图片数据失败: %v", err)
return nil, err
}
if err = r.Structs(&result); err != nil {
g.Log().Errorf(ctx, "转换待送检图片数据失败: %v", err)
return nil, err
}
return result, nil
}
// GetByImageID 根据图片ID获取数据
func (d *TencentImageDAO) GetByImageID(ctx context.Context, imageID string) (*entity.TencentImage, error) {
var result entity.TencentImage
r, err := Model(consts.TencentImageTable).
Where(entity.TencentImageCols.ImageID, imageID).
One()
if err != nil {
return nil, err
}
if r.IsEmpty() {
return nil, nil
}
if err = r.Struct(&result); err != nil {
return nil, err
}
return &result, nil
}
// GetByID 根据ID获取数据
func (d *TencentImageDAO) GetByID(ctx context.Context, id int64) (*entity.TencentImage, error) {
var result entity.TencentImage
r, err := Model(consts.TencentImageTable).
Where(entity.TencentImageCols.Id, id).
One()
if err != nil {
return nil, err
}
if r.IsEmpty() {
return nil, nil
}
if err = r.Struct(&result); err != nil {
return nil, err
}
return &result, nil
}
// CountPending 统计待送检数量
func (d *TencentImageDAO) CountPending(ctx context.Context) (int, error) {
count, err := Model(consts.TencentImageTable).
Where(entity.TencentImageCols.VerifyStatus, consts.CheckStatusPending).
WhereNull("deleted_at").
Count()
if err != nil {
g.Log().Errorf(ctx, "统计待送检图片数量失败: %v", err)
return 0, err
}
return int(count), nil
}
// CountByStatus 根据状态统计数量
func (d *TencentImageDAO) CountByStatus(ctx context.Context, status string) (int, error) {
count, err := Model(consts.TencentImageTable).
Where(entity.TencentImageCols.VerifyStatus, status).
Count()
if err != nil {
return 0, err
}
return int(count), nil
}
// GetByCondition 根据条件获取数据列表
func (d *TencentImageDAO) GetByCondition(ctx context.Context, condition map[string]interface{}, page, pageSize int) ([]entity.TencentImage, int, error) {
var result []entity.TencentImage
model := Model(consts.TencentImageTable)
for k, v := range condition {
model = model.Where(k, v)
}
total, err := model.Count()
if err != nil {
return nil, 0, err
}
r, err := model.
OrderDesc(entity.TencentImageCols.CreatedTime).
Page(page, pageSize).
All()
if err != nil {
return nil, 0, err
}
if err = r.Structs(&result); err != nil {
return nil, 0, err
}
return result, int(total), nil
}
// ClaimPending 原子地尝试将图片从 PENDING 状态转为 SUBMITTING
// 返回 true 表示成功抢到处理权,false 表示已被其他进程处理
func (d *TencentImageDAO) ClaimPending(ctx context.Context, id int64) (bool, error) {
result, err := Model(consts.TencentImageTable).
Where(entity.TencentImageCols.Id, id).
Where(entity.TencentImageCols.VerifyStatus, consts.CheckStatusPending).
Data(entity.TencentImageCols.VerifyStatus, consts.CheckStatusSubmitting).
Update()
if err != nil {
g.Log().Errorf(ctx, "原子认领图片送检失败: %v", err)
return false, err
}
affected, _ := result.RowsAffected()
return affected > 0, nil
}
// UpdateStatus 更新图片校验状态
func (d *TencentImageDAO) UpdateStatus(ctx context.Context, id int64, verifyStatus string) (int64, error) {
result, err := Model(consts.TencentImageTable).
Where(entity.TencentImageCols.Id, id).
Data(entity.TencentImageCols.VerifyStatus, verifyStatus).
Update()
if err != nil {
g.Log().Errorf(ctx, "更新图片校验状态失败: %v", err)
return 0, err
}
affected, _ := result.RowsAffected()
return affected, nil
}
+152
View File
@@ -0,0 +1,152 @@
package check
import (
consts "cid/consts/check"
entity "cid/model/entity/check"
"context"
"github.com/gogf/gf/v2/frame/g"
)
// TencentVideoDAO 视频素材数据访问层
type TencentVideoDAO struct{}
// TencentVideo 视频DAO单例
var TencentVideo = new(TencentVideoDAO)
// GetPendingList 获取待送检数据列表
func (d *TencentVideoDAO) GetPendingList(ctx context.Context, limit int) ([]entity.TencentVideo, error) {
var result []entity.TencentVideo
r, err := Model(consts.TencentVideoTable).
Where(entity.TencentVideoCols.VerifyStatus, consts.CheckStatusPending).
WhereNull("deleted_at").
OrderAsc("created_time").
Limit(limit).
All()
if err != nil {
g.Log().Errorf(ctx, "查询待送检视频数据失败: %v", err)
return nil, err
}
if err = r.Structs(&result); err != nil {
g.Log().Errorf(ctx, "转换待送检视频数据失败: %v", err)
return nil, err
}
return result, nil
}
// GetByVideoID 根据视频ID获取数据
func (d *TencentVideoDAO) GetByVideoID(ctx context.Context, videoID string) (*entity.TencentVideo, error) {
var result entity.TencentVideo
r, err := Model(consts.TencentVideoTable).
Where(entity.TencentVideoCols.VideoID, videoID).
One()
if err != nil {
return nil, err
}
if r.IsEmpty() {
return nil, nil
}
if err = r.Struct(&result); err != nil {
return nil, err
}
return &result, nil
}
// GetByID 根据ID获取数据
func (d *TencentVideoDAO) GetByID(ctx context.Context, id int64) (*entity.TencentVideo, error) {
var result entity.TencentVideo
r, err := Model(consts.TencentVideoTable).
Where(entity.TencentVideoCols.Id, id).
One()
if err != nil {
return nil, err
}
if r.IsEmpty() {
return nil, nil
}
if err = r.Struct(&result); err != nil {
return nil, err
}
return &result, nil
}
// CountPending 统计待送检数量
func (d *TencentVideoDAO) CountPending(ctx context.Context) (int, error) {
count, err := Model(consts.TencentVideoTable).
Where(entity.TencentVideoCols.VerifyStatus, consts.CheckStatusPending).
WhereNull("deleted_at").
Count()
if err != nil {
g.Log().Errorf(ctx, "统计待送检视频数量失败: %v", err)
return 0, err
}
return int(count), nil
}
// CountByStatus 根据状态统计数量
func (d *TencentVideoDAO) CountByStatus(ctx context.Context, status string) (int, error) {
count, err := Model(consts.TencentVideoTable).
Where(entity.TencentVideoCols.VerifyStatus, status).
Count()
if err != nil {
return 0, err
}
return int(count), nil
}
// GetByCondition 根据条件获取数据列表
func (d *TencentVideoDAO) GetByCondition(ctx context.Context, condition map[string]interface{}, page, pageSize int) ([]entity.TencentVideo, int, error) {
var result []entity.TencentVideo
model := Model(consts.TencentVideoTable)
for k, v := range condition {
model = model.Where(k, v)
}
total, err := model.Count()
if err != nil {
return nil, 0, err
}
r, err := model.
OrderDesc(entity.TencentVideoCols.CreatedTime).
Page(page, pageSize).
All()
if err != nil {
return nil, 0, err
}
if err = r.Structs(&result); err != nil {
return nil, 0, err
}
return result, int(total), nil
}
// ClaimPending 原子地尝试将视频从 PENDING 状态转为 SUBMITTING
// 返回 true 表示成功抢到处理权,false 表示已被其他进程处理
func (d *TencentVideoDAO) ClaimPending(ctx context.Context, id int64) (bool, error) {
result, err := Model(consts.TencentVideoTable).
Where(entity.TencentVideoCols.Id, id).
Where(entity.TencentVideoCols.VerifyStatus, consts.CheckStatusPending).
Data(entity.TencentVideoCols.VerifyStatus, consts.CheckStatusSubmitting).
Update()
if err != nil {
g.Log().Errorf(ctx, "原子认领视频送检失败: %v", err)
return false, err
}
affected, _ := result.RowsAffected()
return affected > 0, nil
}
// UpdateStatus 更新视频校验状态
func (d *TencentVideoDAO) UpdateStatus(ctx context.Context, id int64, verifyStatus string) (int64, error) {
result, err := Model(consts.TencentVideoTable).
Where(entity.TencentVideoCols.Id, id).
Data(entity.TencentVideoCols.VerifyStatus, verifyStatus).
Update()
if err != nil {
g.Log().Errorf(ctx, "更新视频校验状态失败: %v", err)
return 0, err
}
affected, _ := result.RowsAffected()
return affected, nil
}
-63
View File
@@ -1,63 +0,0 @@
package dao
import (
"context"
"cid/model/entity"
"gitea.com/red-future/common/beans"
"gitea.com/red-future/common/db/mongo"
"go.mongodb.org/mongo-driver/v2/bson"
)
var CIDRequest = &cidRequestDao{}
type cidRequestDao struct {
}
// Create 创建CID请求记录
func (d *cidRequestDao) Create(ctx context.Context, request *entity.CidRequest) (id string, err error) {
ids, err := mongo.DB().Insert(ctx, []interface{}{request}, entity.CidRequestCollection)
if err != nil {
return "", err
}
if len(ids) > 0 {
id = ids[0].(string)
}
return
}
// GetHistory 获取CID请求历史
func (d *cidRequestDao) GetHistory(ctx context.Context, userId string, page, size int) (list []*entity.CidRequest, total int64, err error) {
filter := bson.M{"userId": userId}
// 分页查询,使用common/mongo的Find方法,自动处理分页、租户等
pageBean := &beans.Page{PageNum: int64(page), PageSize: int64(size)}
total, err = mongo.DB().Find(ctx, filter, &list, entity.CidRequestCollection, pageBean, nil)
return
}
// GetStatistics 获取统计信息
func (d *cidRequestDao) GetStatistics(ctx context.Context, userId string) (stats map[string]interface{}, err error) {
stats = make(map[string]interface{})
// 总请求数
totalRequests, err := mongo.DB().Count(ctx, bson.M{"userId": userId}, entity.CidRequestCollection)
if err != nil {
return nil, err
}
stats["total_requests"] = totalRequests
// 成功请求数
successfulRequests, err := mongo.DB().Count(ctx, bson.M{"userId": userId, "status": "completed"}, entity.CidRequestCollection)
if err != nil {
return nil, err
}
stats["successful_requests"] = successfulRequests
// 平均处理时间需要单独计算,MongoDB聚合查询
// 这里简化处理,返回0
stats["average_process_time"] = 0
return
}
-88
View File
@@ -1,88 +0,0 @@
package dao
import (
"context"
"cid/model/entity"
"gitea.com/red-future/common/beans"
"gitea.com/red-future/common/db/mongo"
"go.mongodb.org/mongo-driver/v2/bson"
)
var Strategy = &strategyDao{}
type strategyDao struct {
}
// GetByName 根据名称获取策略
func (d *strategyDao) GetByName(ctx context.Context, name string) (strategy *entity.Strategy, err error) {
err = mongo.DB().FindOne(ctx, bson.M{"name": name}, &strategy, "strategies")
return
}
// GetByID 根据ID获取策略
func (d *strategyDao) GetByID(ctx context.Context, id string) (strategy *entity.Strategy, err error) {
err = mongo.DB().FindOne(ctx, bson.M{"_id": id}, &strategy, "strategies")
return
}
// GetByTenantLevel 根据租户级别获取策略
func (d *strategyDao) GetByTenantLevel(ctx context.Context, tenantLevel string) (strategy *entity.Strategy, err error) {
err = mongo.DB().FindOne(ctx, bson.M{"tenantLevel": tenantLevel, "status": "active"}, &strategy, "strategies")
return
}
// Create 创建策略
func (d *strategyDao) Create(ctx context.Context, strategy *entity.Strategy) (id string, err error) {
ids, err := mongo.DB().Insert(ctx, []interface{}{strategy}, "strategies")
if err != nil {
return "", err
}
if len(ids) > 0 {
id = ids[0].(string)
}
return
}
// Update 更新策略
func (d *strategyDao) Update(ctx context.Context, strategy *entity.Strategy) (affected int64, err error) {
result, err := mongo.DB().Update(ctx, bson.M{"_id": strategy.Id}, bson.M{"$set": strategy}, "strategies")
if err != nil {
return 0, err
}
return result, nil
}
// Delete 删除策略
func (d *strategyDao) Delete(ctx context.Context, id string) (affected int64, err error) {
count, err := mongo.DB().Delete(ctx, bson.M{"_id": id}, "strategies")
if err != nil {
return 0, err
}
return count, nil
}
// GetList 获取策略列表
func (d *strategyDao) GetList(ctx context.Context, page, size int, tenantLevel, status string) (list []*entity.Strategy, total int64, err error) {
filter := bson.M{}
// 筛选条件
if tenantLevel != "" {
filter["tenantLevel"] = tenantLevel
}
if status != "" {
filter["status"] = status
}
// 获取总数
total, err = mongo.DB().Count(ctx, filter, "strategies")
if err != nil {
return
}
// 分页查询,使用common/mongo的Find方法,自动处理分页、租户等
pageBean := &beans.Page{PageNum: int64(page), PageSize: int64(size)}
total, err = mongo.DB().Find(ctx, filter, &list, "strategies", pageBean, nil)
return
}
+58 -47
View File
@@ -1,34 +1,37 @@
module cid
go 1.25.5
go 1.26.0
require (
gitea.com/red-future/common v0.0.4
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.5
github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.5
github.com/gogf/gf/v2 v2.9.5
go.mongodb.org/mongo-driver/v2 v2.4.1
golang.org/x/net v0.47.0
gitea.redpowerfuture.com/red-future/common v0.0.29
github.com/bwmarrin/snowflake v0.3.0
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.2
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.2
github.com/gogf/gf/v2 v2.10.2
github.com/yidun/yidun-golang-sdk v1.0.38
)
//replace gitea.com/red-future/common => ../common
//replace gitea.redpowerfuture.com/red-future/common => ../common
require (
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/BurntSushi/toml v1.6.0 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/dgraph-io/badger/v4 v4.2.0 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/emirpasic/gods/v2 v2.0.0-alpha // indirect
github.com/fatih/color v1.19.0 // indirect
github.com/fsnotify/fsnotify v1.10.1 // indirect
github.com/go-ego/gse v1.0.2 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-sql-driver/mysql v1.7.1 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/goccy/go-json v0.10.6 // indirect
github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5 // indirect
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.9.5 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
@@ -37,55 +40,63 @@ require (
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/flatbuffers v1.12.1 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/flatbuffers v25.12.19+incompatible // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/grokify/html-strip-tags-go v0.1.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
github.com/hashicorp/consul/api v1.26.1 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
github.com/hashicorp/consul/api v1.33.5 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.5.0 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/go-version v1.9.0 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/serf v0.10.1 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/compress v1.18.6 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/lib/pq v1.12.3 // indirect
github.com/magiconair/properties v1.8.10 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mattn/go-colorable v0.1.15 // indirect
github.com/mattn/go-isatty v0.0.22 // indirect
github.com/mattn/go-runewidth v0.0.24 // indirect
github.com/miekg/dns v1.1.72 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/olekukonko/errors v1.1.0 // indirect
github.com/olekukonko/ll v0.0.9 // indirect
github.com/olekukonko/tablewriter v1.1.0 // indirect
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
github.com/olekukonko/errors v1.3.0 // indirect
github.com/olekukonko/ll v0.1.8 // indirect
github.com/olekukonko/tablewriter v1.1.4 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/redis/go-redis/v9 v9.12.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/r3labs/diff/v2 v2.15.1 // indirect
github.com/redis/go-redis/v9 v9.21.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/tiger1103/gfast-token v1.0.10 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
go.opencensus.io v0.23.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/vcaesar/cedar v0.30.0 // indirect
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
go.mongodb.org/mongo-driver/v2 v2.6.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel v1.44.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.opentelemetry.io/otel/metric v1.44.0 // indirect
go.opentelemetry.io/otel/sdk v1.44.0 // indirect
go.opentelemetry.io/otel/trace v1.44.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
golang.org/x/crypto v0.44.0 // indirect
golang.org/x/exp v0.0.0-20250128144449-3edf0e91c1ae // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/grpc v1.75.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
go.uber.org/atomic v1.11.0 // indirect
golang.org/x/exp v0.0.0-20260611194520-c48552f49976 // indirect
golang.org/x/net v0.56.0 // indirect
golang.org/x/sys v0.46.0 // indirect
golang.org/x/text v0.38.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+141 -122
View File
@@ -1,11 +1,9 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
gitea.com/red-future/common v0.0.2 h1:KjiIyZo0JeSN9ldXofuGkFifJ/H66kTybOU34Yew7R0=
gitea.com/red-future/common v0.0.2/go.mod h1:CUurYN0elToJTwB2pX9wSnjQqZv9D/Vxbo5ueb7i9BI=
gitea.com/red-future/common v0.0.3/go.mod h1:mq4smQZFI5nYul6gvLH7ScnC/26bAOcTvR3hP625NYY=
gitea.com/red-future/common v0.0.4/go.mod h1:UI9N5UUjilbMPF7+/lypZSnqDVHigt14300oSRrAyZg=
gitea.redpowerfuture.com/red-future/common v0.0.29 h1:5McaN5pSewvrLUHQzWMX6EaUvD+B5I5bMYoU+clHJk4=
gitea.redpowerfuture.com/red-future/common v0.0.29/go.mod h1:50U1Xi+Ie56z09S5LQbZvaken0Mxv3OeS9LgR7U/ZRY=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@@ -25,6 +23,8 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -36,6 +36,10 @@ github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -47,13 +51,11 @@ github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWa
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/emirpasic/gods/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -61,10 +63,12 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
github.com/go-ego/gse v1.0.2 h1:+27lYFPhQEhA9igtdOsJPRKYL/k3TwYsxBF5jr6KFv4=
github.com/go-ego/gse v1.0.2/go.mod h1:Fy35G+q7VV7Et1zIKO8o/sW1kkugV3znXap/lF/11zc=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
@@ -74,19 +78,21 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.5 h1:0+ZBYhi4sqwxXwL+hIBpp06a7G4m5nmjskQ3NNb8qYc=
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.5/go.mod h1:vyB7J/uJcLCrHD5lfFBzxhEEMkePIRzfhd33EcsuLa0=
github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.5 h1:Ku7p3CvGchxC7zPSgArf/tZs2w9Yb8tS/gH5ADN+p9g=
github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.5/go.mod h1:cjy18NsSLZQf5zaLAzuo7B2gr8GGjCTWDTEPY7T+6FI=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.2 h1:u8EpP24GkprogROnJ7htMov9Fc66pTP1eVYrWxiCYOs=
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.2/go.mod h1:GmvM3r8GVByVMi4RD2+MCs5+CfxVXPMeT8mVDkAaAXE=
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.2 h1:iTQegT+lEg/wDKvj2mi3W1wrdrwFarjokf88EXVVgu4=
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.2/go.mod h1:ZRw3GNz5cq4uYrW4TPSVyrYWaoqzujKdWro/AOcGBaE=
github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5 h1:eUqwJ/qNH8lJ6yssiqskazgp1ACQuNU6zXlLOZVuXTQ=
github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5/go.mod h1:sjQyMry9+0POYZCA6lHXBxO77WoNKkruJpRB4xKqk5k=
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.9.5 h1:tHUEZYB5GTqEYYVDYnlGobf1xISARKDE4KHVlgjwTec=
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.9.5/go.mod h1:cfzTn2HS9RDX8f5pUVkbGxUWcSosouqfNQ1G6cY0V88=
github.com/gogf/gf/v2 v2.9.5 h1:1scfOdHbMP854oQaiLejl+eL+c4xfuvtWmmZiDJxbKs=
github.com/gogf/gf/v2 v2.9.5/go.mod h1:VUb5eyJKpvW77O/dXsbbLNO/Kjrg0UycIiq0lRiBjjo=
github.com/gogf/gf/v2 v2.10.2 h1:46IO0Uc8e85/FqdftJFskfDejJLBL0JBnGS5qOftUu8=
github.com/gogf/gf/v2 v2.10.2/go.mod h1:Svl1N+E8G/QshU2DUbh/3J/AJauqCgUnxHurXWR4Qx0=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
@@ -102,22 +108,24 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw=
github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/flatbuffers v25.12.19+incompatible h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs=
github.com/google/flatbuffers v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -130,16 +138,16 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/grokify/html-strip-tags-go v0.1.0 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtgzltlQbOBae4=
github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
github.com/hashicorp/consul/api v1.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ/pICIM=
github.com/hashicorp/consul/api v1.26.1/go.mod h1:B4sQTeaSO16NtynqrAdwOlahJ7IUDZM9cj2420xYL8A=
github.com/hashicorp/consul/sdk v0.15.0 h1:2qK9nDrr4tiJKRoxPGhm6B7xJjLVIQqkjiab2M4aKjU=
github.com/hashicorp/consul/sdk v0.15.0/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
github.com/hashicorp/consul/api v1.33.5 h1:Nn6q87zudRU1rLBTJEgaWxz9STCNadilLCD7B8OA5aI=
github.com/hashicorp/consul/api v1.33.5/go.mod h1:pa6fJOSHKLOzNHpUVeqLDtxA5+J1D7NNzLasuk8eRXA=
github.com/hashicorp/consul/sdk v0.17.3 h1:oZMMxzQGSsiT+ToOH50y3Qcs0nc9Ud+7L5lRx+EmMU0=
github.com/hashicorp/consul/sdk v0.17.3/go.mod h1:jnOmYjiNfVRpBaujQ1DFFVs0N6g3S1y6wygSjLTzYfc=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -169,11 +177,10 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA=
github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
@@ -186,8 +193,10 @@ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -197,6 +206,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
@@ -204,41 +215,44 @@ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=
github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU=
github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI=
github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY=
github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc=
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0=
github.com/olekukonko/errors v1.3.0 h1:teJvgLGUEqMzBUms+Dj3/3szNqCG/Jdw9iDbum8fR6U=
github.com/olekukonko/errors v1.3.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
github.com/olekukonko/ll v0.1.8 h1:ysHCJRGHYKzmBSdz9w5AySztx7lG8SQY+naTGYUbsz8=
github.com/olekukonko/ll v0.1.8/go.mod h1:RPRC6UcscfFZgjo1nulkfMH5IM0QAYim0LfnMvUuozw=
github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I=
github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -260,13 +274,12 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/redis/go-redis/v9 v9.12.1 h1:k5iquqv27aBtnTm2tIkROUDp8JBXhXZIVu1InSgvovg=
github.com/redis/go-redis/v9 v9.12.1/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/r3labs/diff/v2 v2.15.1 h1:EOrVqPUzi+njlumoqJwiS/TgGgmZo83619FNDB9xQUg=
github.com/r3labs/diff/v2 v2.15.1/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc=
github.com/redis/go-redis/v9 v9.21.0 h1:FPBE4hhbAke+TLmcY3WkpbDffJEomdqPn3HYiqAtL9E=
github.com/redis/go-redis/v9 v9.21.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
@@ -274,51 +287,61 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tiger1103/gfast-token v1.0.10 h1:fNiBE/Dq5iTHvTGlCx3DmXa2o4hr0NtumFpffZ39k6s=
github.com/tiger1103/gfast-token v1.0.10/go.mod h1:a/21mxmj7zFeNvjhZSC0XpEAFHfb1aT2k6DXnufFU1s=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
github.com/vcaesar/cedar v0.30.0 h1:9fSDpM7FTjjUdPiBUUa0MWYMRGSEcqgFXvppZcZ4d7Y=
github.com/vcaesar/cedar v0.30.0/go.mod h1:lyuGvALuZZDPNXwpzv/9LyxW+8Y6faN7zauFezNsnik=
github.com/vcaesar/tt v0.20.1 h1:D/jUeeVCNbq3ad8M7hhtB3J9x5RZ6I1n1eZ0BJp7M+4=
github.com/vcaesar/tt v0.20.1/go.mod h1:cH2+AwGAJm19Wa6xvEa+0r+sXDJBT0QgNQey6mwqLeU=
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/yidun/yidun-golang-sdk v1.0.38 h1:4NjQdt2GGMgLToB2+zTA0L4YRpqY3ZQjVpl2ot1gwfk=
github.com/yidun/yidun-golang-sdk v1.0.38/go.mod h1:+JGdWbkUvLi9uKTtHI+nrxajulfZKA7BXDPlzt1RCsU=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver/v2 v2.4.1 h1:hGDMngUao03OVQ6sgV5csk+RWOIkF+CuLsTPobNMGNI=
go.mongodb.org/mongo-driver/v2 v2.4.1/go.mod h1:jHeEDJHJq7tm6ZF45Issun9dbogjfnPySb1vXA7EeAI=
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
go.mongodb.org/mongo-driver/v2 v2.6.0 h1:b9sJOYrkmt4l8bY43ZenFBcPlhYIjaOfYHLtbB/5qi8=
go.mongodb.org/mongo-driver/v2 v2.6.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -326,36 +349,35 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ=
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8=
golang.org/x/exp v0.0.0-20250128144449-3edf0e91c1ae/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/exp v0.0.0-20260611194520-c48552f49976 h1:X8Hz2ImujgbmetVuW+w2YkyZChE3cBpZi2P158rTG9M=
golang.org/x/exp v0.0.0-20260611194520-c48552f49976/go.mod h1:vnf4pv9iKZXY58sQE1L86zmNWJ4159e1RkcWiLCkeEY=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -364,9 +386,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -385,27 +406,20 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -415,7 +429,8 @@ golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.46.0 h1:7jTurBkPZu4moS/Uy4OQT1M+QBlsj3wejyZwsT8Z7rk=
golang.org/x/tools v0.46.0/go.mod h1:FrD85F8l+NWL+9XWBSyVSHO6Ne4jutsfIFba7AWQ5Ys=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -424,20 +439,24 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY=
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc=
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0=
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -447,8 +466,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+122 -15
View File
@@ -1,30 +1,137 @@
package main
import (
"cid/controller"
"cid/controller/check"
serviceYidun "cid/service/check"
"context"
"fmt"
"os"
"path/filepath"
"time"
_ "gitea.com/red-future/common/db/mongo"
"gitea.com/red-future/common/http"
"gitea.com/red-future/common/jaeger"
_ "gitea.com/red-future/common/ragflow" // RAGFlow 客户端自动初始化
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
_ "gitea.redpowerfuture.com/red-future/common/consul"
"gitea.redpowerfuture.com/red-future/common/http"
"gitea.redpowerfuture.com/red-future/common/jaeger"
_ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
_ "github.com/gogf/gf/contrib/nosql/redis/v2"
"golang.org/x/net/context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
func main() {
ctx := context.Background()
defer jaeger.ShutDown(ctx)
// 设置时区为东八区
loc, err := time.LoadLocation("Asia/Shanghai")
if err == nil {
time.Local = loc
}
// 关键:设置 PGTZ 环境变量,lib/pq 驱动在连接 pg 时会自动设置 session timezone
// 确保从数据库读取 TIMESTAMPTZ 时返回的是东八区时间,gtime.Time 序列化输出北京时间
os.Setenv("PGTZ", "Asia/Shanghai")
// 初始化易盾客户端
if err := serviceYidun.InitYidunClients(ctx); err != nil {
panic(fmt.Sprintf("初始化易盾客户端失败: %v", err))
}
g.Log().Info(ctx, "易盾客户端初始化成功")
// 启动内容送检定时任务
startContentCheckService(ctx)
// 获取前端目录
frontendDir := getFrontendDir()
// 注册前端静态文件路由(在使用 http.Httpserver 之前)
registerFrontendRoutes(frontendDir)
// 注册 API 路由并启动服务器
http.RouteRegister([]interface{}{
controller.AdSource,
controller.CID,
controller.Strategy,
controller.Advertisement,
controller.Advertiser,
controller.AdPosition,
controller.RateLimit,
controller.Application,
check.YidunController,
check.YidunCallback,
check.ContentCheck,
check.MaterialVerify,
})
// 打印前端访问地址
port := g.Cfg().MustGet(ctx, "server.address", ":3001").String()
g.Log().Info(ctx, "============================================")
g.Log().Infof(ctx, "🌐 前端访问地址: http://localhost%s", port)
g.Log().Info(ctx, "============================================")
select {}
}
// getFrontendDir 获取前端目录路径
func getFrontendDir() string {
execPath, _ := os.Executable()
execDir := filepath.Dir(execPath)
frontendDir := filepath.Join(execDir, "resource", "frontend")
if _, err := os.Stat(frontendDir); os.IsNotExist(err) {
cwd, _ := os.Getwd()
frontendDir = filepath.Join(cwd, "resource", "frontend")
}
return frontendDir
}
// registerFrontendRoutes 注册前端静态文件路由
func registerFrontendRoutes(frontendDir string) {
if _, err := os.Stat(frontendDir); os.IsNotExist(err) {
g.Log().Warningf(context.Background(), "前端目录不存在: %s", frontendDir)
return
}
s := http.Httpserver
// 静态资源路由
s.BindHandler("/frontend/{file}", func(r *ghttp.Request) {
file := r.Get("file").String()
filePath := filepath.Join(frontendDir, file)
if _, err := os.Stat(filePath); err == nil {
r.Response.ServeFile(filePath)
} else {
r.Response.WriteStatus(404)
}
})
// 首页/主入口
s.BindHandler("/", func(r *ghttp.Request) {
indexFile := filepath.Join(frontendDir, "material-verify.html")
if _, err := os.Stat(indexFile); err == nil {
r.Response.ServeFile(indexFile)
} else {
r.Response.Write("<html><body><h1>CID Backend Service</h1><p>前端页面未找到</p></body></html>")
}
})
}
// startContentCheckService 启动内容送检服务
func startContentCheckService(ctx context.Context) {
// 检查是否启用定时送检任务
schedulerEnabled := g.Cfg().MustGet(ctx, "content_check.scheduler_enabled", true).Bool()
if !schedulerEnabled {
g.Log().Info(ctx, "定时送检任务已禁用(scheduler_enabled=false),仅支持API手动送检")
return
}
// 配置送检服务参数
config := serviceYidun.ContentCheckConfig{
BatchSize: g.Cfg().MustGet(ctx, "content_check.batch_size", 10).Int(),
ImageEnabled: g.Cfg().MustGet(ctx, "content_check.image_enabled", true).Bool(),
VideoEnabled: g.Cfg().MustGet(ctx, "content_check.video_enabled", true).Bool(),
IntervalSeconds: g.Cfg().MustGet(ctx, "content_check.interval_seconds", 30).Int(),
PollInterval: g.Cfg().MustGet(ctx, "content_check.poll_interval", 60).Int(),
}
serviceYidun.TencentContentCheck.SetConfig(config)
// 启动服务
if err := serviceYidun.TencentContentCheck.Start(ctx); err != nil {
g.Log().Errorf(ctx, "启动内容送检服务失败: %v", err)
} else {
g.Log().Info(ctx, "内容送检服务启动成功")
}
}
-231
View File
@@ -1,231 +0,0 @@
package config
import (
"cid/consts"
"errors"
)
// BaseConfig 基础配置结构
type BaseConfig struct {
// 优先级和权重
Priority int `bson:"priority" json:"priority"` // 优先级
Weight float64 `bson:"weight" json:"weight"` // 权重
Order int `bson:"order" json:"order"` // 排序顺序
// 标签和分类
Tags []string `bson:"tags" json:"tags"` // 标签
Category string `bson:"category" json:"category"` // 分类
Industry string `bson:"industry" json:"industry"` // 行业
// 配置信息
Config string `bson:"config" json:"config"` // 配置信息(JSON格式)
Extra map[string]interface{} `bson:"extra" json:"extra"` // 扩展字段
Remark string `bson:"remark" json:"remark"` // 备注
}
// Validate 基础配置验证
func (c *BaseConfig) Validate() error {
if c.Priority < 0 {
return errors.New(consts.ErrInvalidConfiguration)
}
if c.Weight < 0 || c.Weight > 1 {
return errors.New(consts.ErrInvalidConfiguration)
}
return nil
}
// BiddingConfig 竞价配置
type BiddingConfig struct {
// 竞价类型
BiddingType string `bson:"biddingType" json:"biddingType"` // 竞价类型:cpm、cpc、cpa、rtb
BiddingStrategy string `bson:"biddingStrategy" json:"biddingStrategy"` // 出价策略:manual、auto、target_cpa、target_roas等
// 出价范围
MinBidAmount int64 `bson:"minBidAmount" json:"minBidAmount"` // 最小出价(分)
MaxBidAmount int64 `bson:"maxBidAmount" json:"maxBidAmount"` // 最大出价(分)
DefaultBidAmount int64 `bson:"defaultBidAmount" json:"defaultBidAmount"` // 默认出价(分)
BidIncrement int64 `bson:"bidIncrement" json:"bidIncrement"` // 出价增量(分)
// 自动优化
AutoOptimization bool `bson:"autoOptimization" json:"autoOptimization"` // 是否自动优化
TargetCPA int64 `bson:"targetCPA" json:"targetCPA"` // 目标CPA(分)
TargetROAS float64 `bson:"targetROAS" json:"targetROAS"` // 目标ROAS
OptimizationGoal string `bson:"optimizationGoal" json:"optimizationGoal"` // 优化目标:impressions、clicks、conversions、revenue等
}
// Validate 竞价配置验证
func (c *BiddingConfig) Validate() error {
if c.MinBidAmount < 0 || c.MaxBidAmount < 0 || c.DefaultBidAmount < 0 {
return errors.New(consts.ErrInvalidConfiguration)
}
if c.MinBidAmount > c.MaxBidAmount {
return errors.New(consts.ErrInvalidConfiguration)
}
if c.TargetROAS < 0 {
return errors.New(consts.ErrInvalidConfiguration)
}
return nil
}
// BudgetConfig 预算配置
type BudgetConfig struct {
// 预算设置
TotalBudget int64 `bson:"totalBudget" json:"totalBudget"` // 总预算(分)
DailyBudget int64 `bson:"dailyBudget" json:"dailyBudget"` // 日预算(分)
// 投放节奏
PaceType string `bson:"paceType" json:"paceType"` // 投放节奏:even、accelerated、standard
IsBudgetPacing bool `bson:"isBudgetPacing" json:"isBudgetPacing"` // 是否预算匀速投放
// 时间配置
StartDate int64 `bson:"startDate" json:"startDate"` // 开始投放时间
EndDate int64 `bson:"endDate" json:"endDate"` // 结束投放时间
Timezone string `bson:"timezone" json:"timezone"` // 时区
}
// Validate 预算配置验证
func (c *BudgetConfig) Validate() error {
if c.TotalBudget < 0 || c.DailyBudget < 0 {
return errors.New(consts.ErrInvalidConfiguration)
}
if c.StartDate > c.EndDate {
return errors.New(consts.ErrInvalidConfiguration)
}
return nil
}
// APIConfig API配置
type APIConfig struct {
// 基础配置
Endpoint string `bson:"endpoint" json:"endpoint"` // API端点
Version string `bson:"version" json:"version"` // API版本
Timeout int `bson:"timeout" json:"timeout"` // 超时时间(毫秒)
RetryCount int `bson:"retryCount" json:"retryCount"` // 重试次数
// 认证配置
AuthType string `bson:"authType" json:"authType"` // 认证类型:api_key、oauth、basic
AuthConfig string `bson:"authConfig" json:"authConfig"` // 认证配置(JSON字符串)
// 请求配置
Headers string `bson:"headers" json:"headers"` // 请求头配置(JSON字符串)
// 限流配置
RateLimit int64 `bson:"rateLimit" json:"rateLimit"` // 速率限制
}
// Validate API配置验证
func (c *APIConfig) Validate() error {
if c.Timeout <= 0 {
return errors.New(consts.ErrInvalidConfiguration)
}
if c.RetryCount < 0 {
return errors.New(consts.ErrInvalidConfiguration)
}
if c.RateLimit <= 0 {
return errors.New(consts.ErrInvalidConfiguration)
}
return nil
}
// CreativeConfig 创意配置
type CreativeConfig struct {
// 轮播设置
CreativeRotation string `bson:"creativeRotation" json:"creativeRotation"` // 创意轮播方式:optimize、even、random
SelectedCreatives []string `bson:"selectedCreatives" json:"selectedCreatives"` // 选中的创意列表
ExcludedCreatives []string `bson:"excludedCreatives" json:"excludedCreatives"` // 排除的创意列表
// 技术要求
MaxFileSize int64 `bson:"maxFileSize" json:"maxFileSize"` // 最大文件大小(bytes)
MaxDuration int64 `bson:"maxDuration" json:"maxDuration"` // 最大时长(秒)
// 支持的格式
SupportedFormats []string `bson:"supportedFormats" json:"supportedFormats"` // 支持的格式
SupportedSizes []string `bson:"supportedSizes" json:"supportedSizes"` // 支持的尺寸
}
// Validate 创意配置验证
func (c *CreativeConfig) Validate() error {
if c.MaxFileSize <= 0 {
return errors.New(consts.ErrInvalidConfiguration)
}
if c.MaxDuration <= 0 {
return errors.New(consts.ErrInvalidConfiguration)
}
return nil
}
// PaymentConfig 支付配置
type PaymentConfig struct {
// 计费模式
BillingModel string `bson:"billingModel" json:"billingModel"` // 计费模式:CPC、CPM、CPA等
CommissionRate float64 `bson:"commissionRate" json:"commissionRate"` // 佣金比例
MinimumBudget int64 `bson:"minimumBudget" json:"minimumBudget"` // 最低预算(分)
// 结算配置
SettlementCycle string `bson:"settlementCycle" json:"settlementCycle"` // 结算周期:daily、weekly、monthly
PaymentTerms string `bson:"paymentTerms" json:"paymentTerms"` // 支付条款
Currency string `bson:"currency" json:"currency"` // 货币单位
// 收入分成
RevShareRate float64 `bson:"revShareRate" json:"revShareRate"` // 收入分成比例(0-1)
MinPayment int64 `bson:"minPayment" json:"minPayment"` // 最小支付金额(分)
TaxInclusive bool `bson:"taxInclusive" json:"taxInclusive"` // 是否含税
}
// Validate 支付配置验证
func (c *PaymentConfig) Validate() error {
if c.CommissionRate < 0 || c.CommissionRate > 1 {
return errors.New(consts.ErrInvalidConfiguration)
}
if c.RevShareRate < 0 || c.RevShareRate > 1 {
return errors.New(consts.ErrInvalidConfiguration)
}
if c.MinimumBudget < 0 {
return errors.New(consts.ErrInvalidConfiguration)
}
if c.MinPayment < 0 {
return errors.New(consts.ErrInvalidConfiguration)
}
return nil
}
// FrequencyCapConfig 频次控制配置
type FrequencyCapConfig struct {
// 频次限制
Impressions int `bson:"impressions" json:"impressions"` // 展示次数
TimeWindow int `bson:"timeWindow" json:"timeWindow"` // 时间窗口(小时)
PerUser int `bson:"perUser" json:"perUser"` // 每用户频次
PerHour int `bson:"perHour" json:"perHour"` // 每小时频次
PerDay int `bson:"perDay" json:"perDay"` // 每日频次
// 频次控制规则
CapType string `bson:"capType" json:"capType"` // 频次类型:lifetime、daily、hourly
CapScope string `bson:"capScope" json:"capScope"` // 频次范围:user、device、ip
ResetRule string `bson:"resetRule" json:"resetRule"` // 重置规则:daily、weekly、monthly
}
// RestrictionConfig 限制配置
type RestrictionConfig struct {
// 年龄限制
AgeRestriction bool `bson:"ageRestriction" json:"ageRestriction"` // 年龄限制
MinAge int `bson:"minAge" json:"minAge"` // 最小年龄
MaxAge int `bson:"maxAge" json:"maxAge"` // 最大年龄
// 地域限制
GeoRestrictions []string `bson:"geoRestrictions" json:"geoRestrictions"` // 地域限制
// 设备限制
DeviceRestrictions []string `bson:"deviceRestrictions" json:"deviceRestrictions"` // 设备限制
// 分类限制
CategoryRestrictions []string `bson:"categoryRestrictions" json:"categoryRestrictions"` // 分类限制
// 内容限制
ContentRestrictions []string `bson:"contentRestrictions" json:"contentRestrictions"` // 内容限制
// 品牌安全
BrandSafety bool `bson:"brandSafety" json:"brandSafety"` // 品牌安全
BlockedCategories []string `bson:"blockedCategories" json:"blockedCategories"` // 阻止的分类
AllowedCategories []string `bson:"allowedCategories" json:"allowedCategories"` // 允许的分类
ExcludedKeywords []string `bson:"excludedKeywords" json:"excludedKeywords"` // 排除的关键词
}
-149
View File
@@ -1,149 +0,0 @@
package dto
import (
"cid/model/entity"
"gitea.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
"go.mongodb.org/mongo-driver/v2/bson"
)
// AddAdPositionReq 添加广告位请求
type AddAdPositionReq struct {
g.Meta `path:"/add" method:"post" tags:"广告位管理" summary:"添加广告位" dc:"添加新的广告位"`
// 基本信息
Name string `json:"name" v:"required"` // 广告位名称
Description string `json:"description"` // 广告位描述
PositionCode string `json:"positionCode" v:"required"` // 广告位编码,用于标识
AdFormat string `json:"adFormat" v:"required"` // 支持的广告格式
// 尺寸信息
Width int `json:"width" v:"required"` // 宽度(px)
Height int `json:"height" v:"required"` // 高度(px)
// 位置信息
Page string `json:"page" v:"required"` // 所属页面
Section string `json:"section" v:"required"` // 页面区域
Location string `json:"location" v:"required"` // 具体位置
// 展示设置
MaxAds int `json:"maxAds"` // 最大广告数量
RefreshInterval int `json:"refreshInterval"` // 刷新间隔(秒)
IsLazyLoad bool `json:"isLazyLoad"` // 是否懒加载
// 定价设置
PricingModel string `json:"pricingModel" v:"required"` // 计费模型:CPC、CPM、CPA等
BasePrice int64 `json:"basePrice" v:"required"` // 基础价格(分)
FloorPrice int64 `json:"floorPrice" v:"required"` // 底价(分)
PriceUnit string `json:"priceUnit" v:"required"` // 价格单位:千次展示、单次点击、单次转化等
// 展示规则
DisplayRules *entity.DisplayRules `json:"displayRules"` // 展示规则
// 状态信息
Status string `json:"status" v:"required"` // 广告位状态:启用、禁用、测试
IsExclusive bool `json:"isExclusive"` // 是否独占广告位
}
type AddAdPositionRes struct {
Id *bson.ObjectID `json:"id"`
}
// UpdateAdPositionReq 更新广告位请求
type UpdateAdPositionReq struct {
g.Meta `path:"/update" method:"put" tags:"广告位管理" summary:"更新广告位" dc:"更新广告位信息"`
Id string `json:"id" v:"required"` // ID
// 基本信息
Name string `json:"name"` // 广告位名称
Description string `json:"description"` // 广告位描述
PositionCode string `json:"positionCode"` // 广告位编码,用于标识
AdFormat string `json:"adFormat"` // 支持的广告格式
// 尺寸信息
Width *int `json:"width"` // 宽度(px)
Height *int `json:"height"` // 高度(px)
// 位置信息
Page string `json:"page"` // 所属页面
Section string `json:"section"` // 页面区域
Location string `json:"location"` // 具体位置
// 展示设置
MaxAds *int `json:"maxAds"` // 最大广告数量
RefreshInterval *int `json:"refreshInterval"` // 刷新间隔(秒)
IsLazyLoad *bool `json:"isLazyLoad"` // 是否懒加载
// 定价设置
PricingModel string `json:"pricingModel"` // 计费模型:CPC、CPM、CPA等
BasePrice *int64 `json:"basePrice"` // 基础价格(分)
FloorPrice *int64 `json:"floorPrice"` // 底价(分)
PriceUnit string `json:"priceUnit"` // 价格单位:千次展示、单次点击、单次转化等
// 展示规则
DisplayRules *entity.DisplayRules `json:"displayRules"` // 展示规则
// 状态信息
Status *string `json:"status"` // 广告位状态:启用、禁用、测试
IsExclusive *bool `json:"isExclusive"` // 是否独占广告位
}
// GetAdPositionReq 获取广告位详情请求
type GetAdPositionReq struct {
g.Meta `path:"/one" method:"get" tags:"广告位管理" summary:"获取广告位详情" dc:"根据ID获取单个广告位详情"`
Id string `json:"id" v:"required"` // ID
}
type GetAdPositionRes struct {
*entity.AdPosition
}
// ListAdPositionReq 获取广告位列表请求
type ListAdPositionReq struct {
g.Meta `path:"/list" method:"get" tags:"广告位管理" summary:"获取广告位列表" dc:"分页查询广告位列表,支持多条件筛选"`
*beans.Page
Name string `json:"name"` // 广告位名称模糊查询
PositionCode string `json:"positionCode"` // 广告位编码
PageName string `json:"pageName"` // 所属页面
Section string `json:"section"` // 页面区域
Status string `json:"status"` // 广告位状态
AdFormat string `json:"adFormat"` // 广告格式
DateRange []string `json:"dateRange"` // 创建时间范围 [start, end]
}
type ListAdPositionRes struct {
List []*entity.AdPosition `json:"list"`
Total int `json:"total"`
}
// UpdateAdPositionStatusReq 更新广告位状态请求
type UpdateAdPositionStatusReq struct {
g.Meta `path:"/updateStatus" method:"patch" tags:"广告位管理" summary:"更新广告位状态" dc:"更新广告位状态"`
Id string `json:"id" v:"required"` // 广告位ID
Status string `json:"status" v:"required"` // 广告位状态:启用、禁用、测试
}
// GetAvailableAdPositionsReq 获取可用广告位请求
type GetAvailableAdPositionsReq struct {
g.Meta `path:"/getAvailableAdPositions" method:"get" tags:"广告位管理" summary:"获取可用广告位列表" dc:"获取所有启用的广告位列表"`
}
type GetAvailableAdPositionsRes struct {
List []*entity.AdPosition `json:"list"`
}
// MatchAdReq 匹配广告请求
type MatchAdReq struct {
g.Meta `path:"/matchAd" method:"post" tags:"广告位管理" summary:"匹配广告" dc:"根据广告位编码和用户信息匹配适合的广告"`
PositionCode string `json:"positionCode" v:"required"` // 广告位编码
UserInfo map[string]interface{} `json:"userInfo"` // 用户信息
}
type MatchAdRes struct {
*entity.Advertisement `json:"advertisement"`
}
-185
View File
@@ -1,185 +0,0 @@
package dto
import (
"cid/model/entity"
"gitea.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
)
// CreateAdSourceReq 创建广告源请求
type CreateAdSourceReq struct {
g.Meta `path:"/create" method:"post" tags:"广告源管理" summary:"创建广告源" dc:"创建新的广告源配置"`
// 基本信息
Name string `json:"name" v:"required"` // 广告源名称
Code string `json:"code" v:"required"` // 广告源编码,唯一标识
Provider string `json:"provider" v:"required"` // 提供商:google、facebook、baidu、tencent、self等
Type string `json:"type" v:"required|in:self,third_party,exchange"` // 类型
Description string `json:"description"` // 描述
// 连接配置
APIEndpoint string `json:"apiEndpoint" v:"required"` // API端点
APIVersion string `json:"apiVersion"` // API版本
AuthType string `json:"authType" v:"required|in:api_key,oauth,basic"` // 认证类型
AuthConfig map[string]interface{} `json:"authConfig" v:"required"` // 认证配置
Headers map[string]string `json:"headers"` // 请求头配置
Timeout int `json:"timeout"` // 超时时间(毫秒)
RetryCount int `json:"retryCount"` // 重试次数
// 基础配置
SupportedFormats []string `json:"supportedFormats" v:"required"` // 支持的广告格式
SupportedSizes []string `json:"supportedSizes"` // 支持的尺寸
SupportedDevices []string `json:"supportedDevices"` // 支持的设备类型
SupportedOS []string `json:"supportedOS"` // 支持的操作系统
SupportedCountries []string `json:"supportedCountries"` // 支持的国家/地区
// 竞价配置
BiddingType string `json:"biddingType" v:"required|in:cpm,cpc,cpa,rtb"` // 竞价类型
MinBidAmount int64 `json:"minBidAmount"` // 最小出价(分)
MaxBidAmount int64 `json:"maxBidAmount"` // 最大出价(分)
BidIncrement int64 `json:"bidIncrement"` // 出价增量(分)
DefaultBidAmount int64 `json:"defaultBidAmount"` // 默认出价(分)
AutoOptimization bool `json:"autoOptimization"` // 是否自动优化
// 最大广告数
MaxAdsPerRequest int `json:"maxAdsPerRequest"` // 单次请求最大广告数量
// 其他配置
BrandSafety bool `json:"brandSafety"` // 品牌安全
Viewability bool `json:"viewability"` // 可见性支持
RealTimeBidding bool `json:"realTimeBidding"` // 实时竞价
HeaderBidding bool `json:"headerBidding"` // 标题竞价
// 财务设置
BillingModel string `json:"billingModel" v:"required|in:cpm,cpc,cpa,rev_share"` // 计费模式
PaymentTerms string `json:"paymentTerms" v:"in:net_30,net_60,net_90"` // 支付条款
RevShareRate float64 `json:"revShareRate" v:"between:0,1"` // 收入分成比例
MinPayment int64 `json:"minPayment"` // 最小支付金额(分)
Currency string `json:"currency"` // 货币单位
TaxInclusive bool `json:"taxInclusive"` // 是否含税
// 系统信息
Priority int `json:"priority"` // 优先级
}
type CreateAdSourceRes struct {
Id string `json:"id"` // 广告源ID
}
// GetAdSourceReq 获取广告源详情请求
type GetAdSourceReq struct {
g.Meta `path:"/getByID" method:"get" tags:"广告源管理" summary:"获取广告源详情" dc:"根据ID获取单个广告源详情"`
Id string `json:"id" v:"required"` // 广告源ID
}
type GetAdSourceRes struct {
*entity.AdSource
}
// ListAdSourceReq 获取广告源列表请求
type ListAdSourceReq struct {
g.Meta `path:"/getList" method:"get" tags:"广告源管理" summary:"获取广告源列表" dc:"分页查询广告源列表,支持多条件筛选"`
*beans.Page
Name string `json:"name"` // 广告源名称模糊查询
Code string `json:"code"` // 广告源编码
Provider string `json:"provider"` // 提供商
Type string `json:"type"` // 类型
Status string `json:"status"` // 状态
Health string `json:"health"` // 健康状态
}
type ListAdSourceRes struct {
List []*entity.AdSource `json:"list"`
Total int `json:"total"`
}
// UpdateAdSourceReq 更新广告源请求
type UpdateAdSourceReq struct {
g.Meta `path:"/update" method:"put" tags:"广告源管理" summary:"更新广告源" dc:"更新广告源信息"`
Id string `json:"id" v:"required"` // 广告源ID
// 基本信息
Name string `json:"name"` // 广告源名称
Description string `json:"description"` // 描述
// 连接配置
APIEndpoint string `json:"apiEndpoint"` // API端点
APIVersion string `json:"apiVersion"` // API版本
AuthType string `json:"authType"` // 认证类型
AuthConfig map[string]interface{} `json:"authConfig"` // 认证配置
Headers map[string]string `json:"headers"` // 请求头配置
Timeout int `json:"timeout"` // 超时时间(毫秒)
RetryCount int `json:"retryCount"` // 重试次数
// 基础配置
SupportedFormats []string `json:"supportedFormats"` // 支持的广告格式
SupportedSizes []string `json:"supportedSizes"` // 支持的尺寸
SupportedDevices []string `json:"supportedDevices"` // 支持的设备类型
SupportedOS []string `json:"supportedOS"` // 支持的操作系统
SupportedCountries []string `json:"supportedCountries"` // 支持的国家/地区
// 竞价配置
BiddingType string `json:"biddingType"` // 竞价类型
MinBidAmount int64 `json:"minBidAmount"` // 最小出价(分)
MaxBidAmount int64 `json:"maxBidAmount"` // 最大出价(分)
BidIncrement int64 `json:"bidIncrement"` // 出价增量(分)
DefaultBidAmount int64 `json:"defaultBidAmount"` // 默认出价(分)
AutoOptimization bool `json:"autoOptimization"` // 是否自动优化
// 最大广告数
MaxAdsPerRequest int `json:"maxAdsPerRequest"` // 单次请求最大广告数量
// 其他配置
BrandSafety bool `json:"brandSafety"` // 品牌安全
Viewability bool `json:"viewability"` // 可见性支持
RealTimeBidding bool `json:"realTimeBidding"` // 实时竞价
HeaderBidding bool `json:"headerBidding"` // 标题竞价
// 财务设置
BillingModel string `json:"billingModel"` // 计费模式
PaymentTerms string `json:"paymentTerms"` // 支付条款
RevShareRate float64 `json:"revShareRate"` // 收入分成比例
MinPayment int64 `json:"minPayment"` // 最小支付金额(分)
Currency string `json:"currency"` // 货币单位
TaxInclusive bool `json:"taxInclusive"` // 是否含税
// 系统信息
Priority int `json:"priority"` // 优先级
}
// UpdateAdSourceStatusReq 更新广告源状态请求
type UpdateAdSourceStatusReq struct {
// g.Meta `path:"/adsource" method:"patch" tags:"广告源管理" summary:"更新广告源状态" dc:"更新广告源状态"` // 暂时注释,缺少对应的controller方法
Id string `json:"id" v:"required"` // 广告源ID
Status string `json:"status" v:"required"` // 广告源状态:active、inactive、maintenance
}
// DeleteAdSourceReq 删除广告源请求
type DeleteAdSourceReq struct {
g.Meta `path:"/delete" method:"delete" tags:"广告源管理" summary:"删除广告源" dc:"删除指定的广告源"`
Id string `json:"id" v:"required"` // 广告源ID
}
// TestAdSourceReq 测试广告源连接请求
type TestAdSourceReq struct {
// g.Meta `path:"/adsource-test" method:"post" tags:"广告源管理" summary:"测试广告源连接" dc:"测试广告源的连接性和可用性"` // 暂时注释,缺少对应的controller方法
Id string `json:"id" v:"required"` // 广告源ID
}
type TestAdSourceRes struct {
Success bool `json:"success"` // 测试是否成功
ResponseTime int64 `json:"responseTime"` // 响应时间(毫秒)
ErrorMessage string `json:"errorMessage"` // 错误信息
SupportedFormats []string `json:"supportedFormats"` // 支持的广告格式
}
// DeleteAdSourceRes 删除广告源响应
type DeleteAdSourceRes struct {
Success bool `json:"success"` // 删除是否成功
}
-117
View File
@@ -1,117 +0,0 @@
package dto
import "github.com/gogf/gf/v2/frame/g"
// GetAdSourceStatisticsReq 获取广告源统计数据请求
type GetAdSourceStatisticsReq struct {
g.Meta `path:"/adsource-statistics" method:"get" tags:"广告源管理" summary:"获取广告源统计数据" dc:"获取广告源的详细统计数据"`
Id string `json:"id" v:"required"` // 广告源ID
StartDate int64 `json:"startDate" v:"required"` // 开始日期
EndDate int64 `json:"endDate" v:"required"` // 结束日期
Dimension string `json:"dimension"` // 统计维度:day、week、month
}
type GetAdSourceStatisticsRes struct {
// 概览数据
Overview AdSourceOverviewStats `json:"overview"`
// 趋势数据
Trends []AdSourceTrendData `json:"trends"`
// 性能数据
Performance AdSourcePerformanceStats `json:"performance"`
// 错误统计
Errors []AdSourceErrorStats `json:"errors"`
}
// AdSourceOverviewStats 广告源概览统计
type AdSourceOverviewStats struct {
TotalRequests int64 `json:"totalRequests"` // 总请求数
SuccessfulRequests int64 `json:"successfulRequests"` // 成功请求数
FailedRequests int64 `json:"failedRequests"` // 失败请求数
TotalImpressions int64 `json:"totalImpressions"` // 总展示次数
TotalClicks int64 `json:"totalClicks"` // 总点击次数
TotalConversions int64 `json:"totalConversions"` // 总转化次数
TotalRevenue int64 `json:"totalRevenue"` // 总收入(分)
SuccessRate float64 `json:"successRate"` // 成功率
ErrorRate float64 `json:"errorRate"` // 错误率
CTR float64 `json:"ctr"` // 点击率
CVR float64 `json:"cvr"` // 转化率
FillRate float64 `json:"fillRate"` // 填充率
ECPM int64 `json:"ecpm"` // 有效千次展示成本(分)
ECPC int64 `json:"ecpc"` // 有效点击成本(分)
AverageResponseTime float64 `json:"averageResponseTime"` // 平均响应时间(毫秒)
Uptime float64 `json:"uptime"` // 可用性(百分比)
}
// AdSourceTrendData 广告源趋势数据
type AdSourceTrendData struct {
Date int64 `json:"date"` // 日期
Requests int64 `json:"requests"` // 请求数
Impressions int64 `json:"impressions"` // 展示次数
Clicks int64 `json:"clicks"` // 点击次数
Conversions int64 `json:"conversions"` // 转化次数
Revenue int64 `json:"revenue"` // 收入(分)
SuccessRate float64 `json:"successRate"` // 成功率
ErrorRate float64 `json:"errorRate"` // 错误率
CTR float64 `json:"ctr"` // 点击率
CVR float64 `json:"cvr"` // 转化率
FillRate float64 `json:"fillRate"` // 填充率
ECPM int64 `json:"ecpm"` // 有效千次展示成本(分)
ResponseTime float64 `json:"responseTime"` // 平均响应时间(毫秒)
}
// AdSourcePerformanceStats 广告源性能统计
type AdSourcePerformanceStats struct {
// 响应时间分布
ResponseTimeDistribution map[string]int64 `json:"responseTimeDistribution"` // 响应时间分布
// 错误类型统计
ErrorTypes map[string]int64 `json:"errorTypes"` // 错误类型统计
// 地区性能
RegionalPerformance map[string]AdSourceRegionalStats `json:"regionalPerformance"` // 地区性能
// 设备性能
DevicePerformance map[string]AdSourceDeviceStats `json:"devicePerformance"` // 设备性能
}
// AdSourceErrorStats 广告源错误统计
type AdSourceErrorStats struct {
ErrorType string `json:"errorType"` // 错误类型
Count int64 `json:"count"` // 错误次数
Percentage float64 `json:"percentage"` // 占比
LastOccurred int64 `json:"lastOccurred"` // 最后发生时间
}
// AdSourceRegionalStats 广告源地区统计
type AdSourceRegionalStats struct {
Region string `json:"region"` // 地区
Requests int64 `json:"requests"` // 请求数
Impressions int64 `json:"impressions"` // 展示次数
Clicks int64 `json:"clicks"` // 点击次数
Revenue int64 `json:"revenue"` // 收入(分)
CTR float64 `json:"ctr"` // 点击率
ResponseTime float64 `json:"responseTime"` // 平均响应时间(毫秒)
}
// AdSourceDeviceStats 广告源设备统计
type AdSourceDeviceStats struct {
Device string `json:"device"` // 设备类型
Requests int64 `json:"requests"` // 请求数
Impressions int64 `json:"impressions"` // 展示次数
Clicks int64 `json:"clicks"` // 点击次数
Revenue int64 `json:"revenue"` // 收入(分)
CTR float64 `json:"ctr"` // 点击率
ResponseTime float64 `json:"responseTime"` // 平均响应时间(毫秒)
}
// AdSourceTestMetrics 广告源测试质量指标
type AdSourceTestMetrics struct {
SuccessRate float64 `json:"successRate"` // 成功率
AverageResponseTime float64 `json:"averageResponseTime"` // 平均响应时间(毫秒)
FillRate float64 `json:"fillRate"` // 填充率
CTR float64 `json:"ctr"` // 点击率
}
-117
View File
@@ -1,117 +0,0 @@
package dto
import (
"cid/model/entity"
"gitea.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
)
// AddAdvertisementReq 添加广告请求
type AddAdvertisementReq struct {
g.Meta `path:"/add" method:"post" tags:"广告管理" summary:"添加广告" dc:"添加新的广告"`
// 广告基本信息
Title string `json:"title" v:"required"` // 广告标题
Description string `json:"description"` // 广告描述
AdvertiserId string `json:"advertiserId" v:"required"` // 广告主ID
AdPositionId string `json:"adPositionId" v:"required"` // 广告位ID
AdType string `json:"adType" v:"required"` // 广告类型:图片、视频、文字等
AdFormat string `json:"adFormat" v:"required"` // 广告格式
MaterialUrl string `json:"materialUrl" v:"required"` // 广告素材URL
TargetUrl string `json:"targetUrl" v:"required"` // 目标链接
// 投放设置
StartDate int64 `json:"startDate" v:"required"` // 开始投放时间
EndDate int64 `json:"endDate" v:"required"` // 结束投放时间
Budget int64 `json:"budget" v:"required"` // 预算(分)
DailyBudget int64 `json:"dailyBudget"` // 日预算(分)
BidAmount int64 `json:"bidAmount" v:"required"` // 出价(分)
BillingType string `json:"billingType" v:"required"` // 计费类型:CPC、CPM、CPA等
// 投放条件
Targeting *entity.UnifiedTargeting `json:"targeting"` // 定向条件
}
type AddAdvertisementRes struct {
Id string `json:"id"`
}
// UpdateAdvertisementReq 更新广告请求
type UpdateAdvertisementReq struct {
g.Meta `path:"/update" method:"put" tags:"广告管理" summary:"更新广告" dc:"更新广告信息"`
Id string `json:"id" v:"required"` // ID
// 广告基本信息
Title string `json:"title"` // 广告标题
Description string `json:"description"` // 广告描述
AdvertiserId string `json:"advertiserId"` // 广告主ID
AdPositionId string `json:"adPositionId"` // 广告位ID
AdType string `json:"adType"` // 广告类型:图片、视频、文字等
AdFormat string `json:"adFormat"` // 广告格式
MaterialUrl string `json:"materialUrl"` // 广告素材URL
TargetUrl string `json:"targetUrl"` // 目标链接
// 投放设置
StartDate *int64 `json:"startDate"` // 开始投放时间
EndDate *int64 `json:"endDate"` // 结束投放时间
Budget *int64 `json:"budget"` // 预算(分)
DailyBudget *int64 `json:"dailyBudget"` // 日预算(分)
BidAmount *int64 `json:"bidAmount"` // 出价(分)
BillingType string `json:"billingType"` // 计费类型:CPC、CPM、CPA等
// 投放条件
Targeting *entity.UnifiedTargeting `json:"targeting"` // 定向条件
// 状态信息
Status *string `json:"status"` // 广告状态:待审核、审核中、已通过、已拒绝、投放中、已暂停、已结束
AuditStatus *string `json:"auditStatus"` // 审核状态:通过、拒绝
AuditReason *string `json:"auditReason"` // 审核不通过原因
}
// GetAdvertisementReq 获取广告详情请求
type GetAdvertisementReq struct {
g.Meta `path:"/getOne" method:"get" tags:"广告管理" summary:"获取广告详情" dc:"根据ID获取单个广告详情"`
Id string `json:"id" v:"required"` // ID
}
type GetAdvertisementRes struct {
*entity.Advertisement
}
// ListAdvertisementReq 获取广告列表请求
type ListAdvertisementReq struct {
g.Meta `path:"/list" method:"get" tags:"广告管理" summary:"获取广告列表" dc:"分页查询广告列表,支持多条件筛选"`
*beans.Page
AdvertiserId string `json:"advertiserId"` // 广告主ID
AdPositionId string `json:"adPositionId"` // 广告位ID
AdType string `json:"adType"` // 广告类型
Status string `json:"status"` // 广告状态
AuditStatus string `json:"auditStatus"` // 审核状态
Title string `json:"title"` // 广告标题模糊查询
DateRange []string `json:"dateRange"` // 创建时间范围 [start, end]
}
type ListAdvertisementRes struct {
List []*entity.Advertisement `json:"list"`
Total int `json:"total"`
}
// AuditAdvertisementReq 审核广告请求
type AuditAdvertisementReq struct {
g.Meta `path:"/audit" method:"post" tags:"广告管理" summary:"审核广告" dc:"审核广告,通过或拒绝"`
Id string `json:"id" v:"required"` // 广告ID
AuditStatus string `json:"auditStatus" v:"required"` // 审核状态:通过、拒绝
AuditReason string `json:"auditReason"` // 审核不通过原因
}
// UpdateAdStatusReq 更新广告状态请求
type UpdateAdStatusReq struct {
g.Meta `path:"/updateStatus" method:"patch" tags:"广告管理" summary:"更新广告状态" dc:"更新广告状态"`
Id string `json:"id" v:"required"` // 广告ID
Status string `json:"status" v:"required"` // 广告状态:启用、禁用
}
-167
View File
@@ -1,167 +0,0 @@
package dto
import (
"cid/model/entity"
"gitea.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
)
// AddAdvertiserReq 添加广告主请求
type AddAdvertiserReq struct {
g.Meta `path:"/add" method:"post" tags:"广告主管理" summary:"添加广告主" dc:"添加新的广告主"`
// 基本信息
Name string `json:"name" v:"required"` // 广告主名称
ContactName string `json:"contactName" v:"required"` // 联系人姓名
ContactPhone string `json:"contactPhone" v:"required"` // 联系电话
ContactEmail string `json:"contactEmail" v:"required"` // 联系邮箱
Company string `json:"company" v:"required"` // 公司名称
Industry string `json:"industry" v:"required"` // 所属行业
Scale string `json:"scale"` // 公司规模
// 证件信息
BusinessLicenseUrl string `json:"businessLicenseUrl" v:"required"` // 营业执照URL
ICPLicenseUrl string `json:"icpLicenseUrl"` // ICP备案截图URL
OtherLicenseUrls []string `json:"otherLicenseUrls"` // 其他证件URL
// 财务信息
BankName string `json:"bankName" v:"required"` // 开户银行
BankAccount string `json:"bankAccount" v:"required"` // 银行账号
AccountName string `json:"accountName" v:"required"` // 账户名称
// 合同信息
ContractId string `json:"contractId"` // 合同编号
ContractType string `json:"contractType"` // 合同类型
ContractUrl string `json:"contractUrl"` // 合同文件URL
SignDate int64 `json:"signDate"` // 签约日期
ExpireDate int64 `json:"expireDate"` // 到期日期
// 系统信息
AccountBalance int64 `json:"accountBalance"` // 账户余额(分)
CreditLimit int64 `json:"creditLimit"` // 授信额度(分)
Remark string `json:"remark"` // 备注
}
type AddAdvertiserRes struct {
Id string `json:"id"`
}
// UpdateAdvertiserReq 更新广告主请求
type UpdateAdvertiserReq struct {
g.Meta `path:"/update" method:"put" tags:"广告主管理" summary:"更新广告主" dc:"更新广告主信息"`
Id string `json:"id" v:"required"` // ID
// 基本信息
Name string `json:"name"` // 广告主名称
ContactName string `json:"contactName"` // 联系人姓名
ContactPhone string `json:"contactPhone"` // 联系电话
ContactEmail string `json:"contactEmail"` // 联系邮箱
Company string `json:"company"` // 公司名称
Industry string `json:"industry"` // 所属行业
Scale string `json:"scale"` // 公司规模
// 证件信息
BusinessLicenseUrl string `json:"businessLicenseUrl"` // 营业执照URL
ICPLicenseUrl string `json:"icpLicenseUrl"` // ICP备案截图URL
OtherLicenseUrls []string `json:"otherLicenseUrls"` // 其他证件URL
// 财务信息
BankName string `json:"bankName"` // 开户银行
BankAccount string `json:"bankAccount"` // 银行账号
AccountName string `json:"accountName"` // 账户名称
// 合同信息
ContractId string `json:"contractId"` // 合同编号
ContractType string `json:"contractType"` // 合同类型
ContractUrl string `json:"contractUrl"` // 合同文件URL
SignDate *int64 `json:"signDate"` // 签约日期
ExpireDate *int64 `json:"expireDate"` // 到期日期
// 系统信息
AccountBalance *int64 `json:"accountBalance"` // 账户余额(分)
CreditLimit *int64 `json:"creditLimit"` // 授信额度(分)
Remark string `json:"remark"` // 备注
// 状态信息
Status *string `json:"status"` // 广告主状态:待审核、已审核、已拒绝、已冻结
AuditStatus *string `json:"auditStatus"` // 审核状态
AuditReason *string `json:"auditReason"` // 审核不通过原因
}
// GetAdvertiserReq 获取广告主详情请求
type GetAdvertiserReq struct {
g.Meta `path:"/getOne" method:"get" tags:"广告主管理" summary:"获取广告主详情" dc:"根据ID获取单个广告主详情"`
Id string `json:"id" v:"required"` // ID
}
type GetAdvertiserRes struct {
*entity.Advertiser
}
// ListAdvertiserReq 获取广告主列表请求
type ListAdvertiserReq struct {
g.Meta `path:"/list" method:"get" tags:"广告主管理" summary:"获取广告主列表" dc:"分页查询广告主列表,支持多条件筛选"`
*beans.Page
Name string `json:"name"` // 广告主名称模糊查询
ContactName string `json:"contactName"` // 联系人模糊查询
Company string `json:"company"` // 公司名称模糊查询
Industry string `json:"industry"` // 所属行业
Status string `json:"status"` // 广告主状态
AuditStatus string `json:"auditStatus"` // 审核状态
DateRange []string `json:"dateRange"` // 创建时间范围 [start, end]
}
type ListAdvertiserRes struct {
List []*entity.Advertiser `json:"list"`
Total int `json:"total"`
}
// AuditAdvertiserReq 审核广告主请求
type AuditAdvertiserReq struct {
g.Meta `path:"/audit" method:"post" tags:"广告主管理" summary:"审核广告主" dc:"审核广告主,通过或拒绝"`
Id string `json:"id" v:"required"` // 广告主ID
AuditStatus string `json:"auditStatus" v:"required"` // 审核状态:通过、拒绝
AuditReason string `json:"auditReason"` // 审核不通过原因
}
// UpdateAdvertiserStatusReq 更新广告主状态请求
type UpdateAdvertiserStatusReq struct {
g.Meta `path:"/updateStatus" method:"patch" tags:"广告主管理" summary:"更新广告主状态" dc:"更新广告主状态"`
Id string `json:"id" v:"required"` // 广告主ID
Status string `json:"status" v:"required"` // 广告主状态:启用、禁用、冻结
}
// RechargeAdvertiserReq 广告主充值请求
type RechargeAdvertiserReq struct {
g.Meta `path:"/recharge" method:"post" tags:"广告主管理" summary:"广告主充值" dc:"为广告主账户充值"`
Id string `json:"id" v:"required"` // 广告主ID
Amount int64 `json:"amount" v:"required"` // 充值金额(分)
Remark string `json:"remark"` // 充值备注
}
// UpdateCreditLimitReq 更新授信额度请求
type UpdateCreditLimitReq struct {
g.Meta `path:"/updateCreditLimit" method:"post" tags:"广告主管理" summary:"更新授信额度" dc:"更新广告主的授信额度"`
Id string `json:"id" v:"required"` // 广告主ID
CreditLimit int64 `json:"creditLimit" v:"required"` // 授信额度(分)
Remark string `json:"remark"` // 备注说明
}
// GetAdvertiserBalanceReq 获取广告主余额请求
type GetAdvertiserBalanceReq struct {
g.Meta `path:"/getBalance" method:"get" tags:"广告主管理" summary:"获取广告主余额" dc:"根据ID获取广告主账户余额和授信额度"`
Id string `json:"id" v:"required"` // 广告主ID
}
// GetAdvertiserBalanceRes 获取广告主余额响应
type GetAdvertiserBalanceRes struct {
Balance int64 `json:"balance"` // 账户余额(分)
CreditLimit int64 `json:"creditLimit"` // 授信额度(分)
}
-157
View File
@@ -1,157 +0,0 @@
package dto
import (
"github.com/gogf/gf/v2/frame/g"
)
// CreateApplicationReq 创建应用请求
type CreateApplicationReq struct {
g.Meta `path:"/createApplication" method:"post" summary:"创建应用"`
TenantID interface{} `json:"tenantId" v:"required#租户ID不能为空"`
Name string `json:"name" v:"required#应用名称不能为空"`
Code string `json:"code" v:"required#应用编码不能为空"`
Description string `json:"description"`
Platform string `json:"platform" v:"required#平台不能为空|in:web,h5,android,ios#平台类型错误"`
PackageName string `json:"packageName"`
AppStoreURL string `json:"appStoreUrl"`
Categories []string `json:"categories"`
Tags []string `json:"tags"`
AdTypes []string `json:"adTypes"`
CallbackURL string `json:"callbackUrl"`
}
// CreateApplicationRes 创建应用响应
type CreateApplicationRes struct {
ID int64 `json:"id"`
AppKey string `json:"appKey"`
AppSecret string `json:"appSecret"`
}
// UpdateApplicationReq 更新应用请求
type UpdateApplicationReq struct {
g.Meta `path:"/updateApplication" method:"put" summary:"更新应用"`
ID int64 `json:"id" v:"required#应用ID不能为空"`
Name string `json:"name"`
Description string `json:"description"`
Platform string `json:"platform" v:"in:web,h5,android,ios#平台类型错误"`
PackageName string `json:"packageName"`
AppStoreURL string `json:"appStoreUrl"`
Categories []string `json:"categories"`
Tags []string `json:"tags"`
AdTypes []string `json:"adTypes"`
CallbackURL string `json:"callbackUrl"`
}
// UpdateApplicationRes 更新应用响应
type UpdateApplicationRes struct {
Success bool `json:"success"`
}
// GetApplicationReq 获取应用请求
type GetApplicationReq struct {
g.Meta `path:"/getApplication" method:"get" summary:"获取应用信息"`
ID int64 `json:"id" v:"required#应用ID不能为空"`
}
// GetApplicationRes 获取应用响应
type GetApplicationRes struct {
ID int64 `json:"id"`
TenantID interface{} `json:"tenantId"`
Name string `json:"name"`
Code string `json:"code"`
Description string `json:"description"`
Platform string `json:"platform"`
PackageName string `json:"packageName"`
AppStoreURL string `json:"appStoreUrl"`
Categories []string `json:"categories"`
Tags []string `json:"tags"`
AdTypes []string `json:"adTypes"`
Status string `json:"status"`
AppKey string `json:"appKey"`
CallbackURL string `json:"callbackUrl"`
CreatedAt int64 `json:"createdAt"`
UpdatedAt int64 `json:"updatedAt"`
}
// ListApplicationsReq 获取应用列表请求
type ListApplicationsReq struct {
g.Meta `path:"/listApplications" method:"get" summary:"获取应用列表"`
TenantID int64 `json:"tenantId" v:"required#租户ID不能为空"`
Platform string `json:"platform"`
Status string `json:"status"`
Page int `json:"page" d:"1"`
Size int `json:"size" d:"20"`
}
// ListApplicationsRes 获取应用列表响应
type ListApplicationsRes struct {
List []ApplicationItem `json:"list"`
Total int64 `json:"total"`
Page int `json:"page"`
Size int `json:"size"`
}
// ApplicationItem 应用列表项
type ApplicationItem struct {
ID int64 `json:"id"`
Name string `json:"name"`
Code string `json:"code"`
Description string `json:"description"`
Platform string `json:"platform"`
PackageName string `json:"packageName"`
Categories []string `json:"categories"`
Tags []string `json:"tags"`
AdTypes []string `json:"adTypes"`
Status string `json:"status"`
DailyRequests int64 `json:"dailyRequests"`
MonthlyRequests int64 `json:"monthlyRequests"`
CreatedAt int64 `json:"createdAt"`
}
// ResetAPIKeysReq 重置API密钥请求
type ResetAPIKeysReq struct {
g.Meta `path:"/resetAPIKeys" method:"post" summary:"重置API密钥"`
ID int64 `json:"id" v:"required#应用ID不能为空"`
}
// ResetAPIKeysRes 重置API密钥响应
type ResetAPIKeysRes struct {
AppKey string `json:"appKey"`
AppSecret string `json:"appSecret"`
}
// ValidateApplicationReq 验证应用请求
type ValidateApplicationReq struct {
g.Meta `path:"/validateApplication" method:"post" summary:"验证应用权限"`
AppKey string `json:"appKey" v:"required#应用密钥不能为空"`
AppSecret string `json:"appSecret" v:"required#应用密钥不能为空"`
}
// ValidateApplicationRes 验证应用响应
type ValidateApplicationRes struct {
Valid bool `json:"valid"`
AppID int64 `json:"appId"`
AppName string `json:"appName"`
TenantID int64 `json:"tenantId"`
TenantName string `json:"tenantName"`
Platform string `json:"platform"`
AdTypes []string `json:"adTypes"`
}
// DeleteApplicationReq 删除应用请求
type DeleteApplicationReq struct {
g.Meta `path:"/deleteApplication" method:"delete" summary:"删除应用"`
ID int64 `json:"id" v:"required#应用ID不能为空"`
}
// DeleteApplicationRes 删除应用响应
type DeleteApplicationRes struct {
Success bool `json:"success"`
}
+70
View File
@@ -0,0 +1,70 @@
package check
// ContentCheckConfig 送检配置
type ContentCheckConfig struct {
BatchSize int `json:"batch_size"`
ImageEnabled bool `json:"image_enabled"`
VideoEnabled bool `json:"video_enabled"`
IntervalSeconds int `json:"interval_seconds"`
}
// StartCheckReq 启动送检服务请求
type StartCheckReq struct {
BatchSize int `json:"batch_size"`
IntervalSeconds int `json:"interval_seconds"`
ImageEnabled bool `json:"image_enabled"`
VideoEnabled bool `json:"video_enabled"`
}
// EmptyReq 空请求
type EmptyReq struct{}
// ProcessImageCallbackReq 处理图片回调请求
type ProcessImageCallbackReq struct {
CallbackData string `json:"callbackData"`
}
// ProcessVideoCallbackReq 处理视频回调请求
type ProcessVideoCallbackReq struct {
CallbackData string `json:"callbackData"`
}
// ProcessImageResultReq 查询图片检测结果请求
type ProcessImageResultReq struct {
TaskID string `json:"taskId"`
}
// ProcessVideoResultReq 查询视频检测结果请求
type ProcessVideoResultReq struct {
TaskID string `json:"taskId"`
}
// ManualSubmitImageByIDReq 手动提交图片送检请求
type ManualSubmitImageByIDReq struct {
ImageID string `json:"image_id" v:"required#图片ID不能为空"`
}
// ManualSubmitVideoByIDReq 手动提交视频送检请求
type ManualSubmitVideoByIDReq struct {
VideoID string `json:"video_id" v:"required#视频ID不能为空"`
}
// ManualSubmitRes 手动提交响应
type ManualSubmitRes struct {
TaskID string `json:"taskId"`
}
// GetImageCheckLogsReq 获取图片送检日志请求
type GetImageCheckLogsReq struct {
ImageID string `json:"image_id" v:"required#图片ID不能为空"`
}
// GetVideoCheckLogsReq 获取视频送检日志请求
type GetVideoCheckLogsReq struct {
VideoID string `json:"video_id" v:"required#视频ID不能为空"`
}
// GetCheckLogsRes 获取送检日志响应
type GetCheckLogsRes struct {
List interface{} `json:"list"`
}
-70
View File
@@ -1,70 +0,0 @@
package dto
import (
"github.com/gogf/gf/v2/frame/g"
)
// GenerateCIDReq 生成CID请求
type GenerateCIDReq struct {
g.Meta `path:"/generateCID" method:"post" tags:"CID服务" summary:"生成CID广告" dc:"为当前用户生成CID广告"`
UserId int64 `json:"user_id"` // 用户ID(可选,如果不提供则从token获取)
RequestType string `json:"request_type"` // 请求类型
Parameters map[string]interface{} `json:"parameters"` // 请求参数
Position string `json:"position"` // 广告位置
Count int `json:"count"` // 广告数量
}
// AdInfo 广告信息
type AdInfo struct {
Id int64 `json:"id"` // 广告ID
Title string `json:"title"` // 广告标题
Description string `json:"description"` // 广告描述
ImageUrl string `json:"image_url"` // 广告图片URL
TargetUrl string `json:"target_url"` // 目标链接
ConversionRate float64 `json:"conversion_rate"` // 转化率
Source string `json:"source"` // 广告源
Bid int `json:"bid"` // 出价(分)
}
// GenerateCIDRes 生成CID响应
type GenerateCIDRes struct {
CID string `json:"cid"` // 唯一CID
Ads []*AdInfo `json:"ads"` // 广告列表
TotalAds int `json:"total_ads"` // 总广告数
TenantId interface{} `json:"tenant_id"` // 租户ID
TenantName string `json:"tenant_name"` // 租户名称
GeneratedAt string `json:"generated_at"` // 生成时间
}
// CIDRequestHistory CID请求历史记录
type CIDRequestHistory struct {
Id int64 `json:"id"` // 记录ID
TenantId interface{} `json:"tenant_id"` // 租户ID
UserId int64 `json:"user_id"` // 用户ID
RequestType string `json:"request_type"` // 请求类型
Status string `json:"status"` // 状态
ProcessTime int `json:"process_time"` // 处理时间(ms)
CreatedAt string `json:"created_at"` // 创建时间
}
// GetCIDHistoryReq 获取CID历史请求
type GetCIDHistoryReq struct {
g.Meta `path:"/getCidHistory" method:"get" tags:"CID服务" summary:"获取CID历史记录" dc:"分页获取用户的CID请求历史"`
Page int `json:"page" v:"required|min:1"` // 页码
Size int `json:"size" v:"required|min:1|max:100"` // 每页数量
}
// GetCIDHistoryRes 获取CID历史响应
type GetCIDHistoryRes struct {
List []*CIDRequestHistory `json:"list"` // 历史记录列表
Total int64 `json:"total"` // 总数
Page int `json:"page"` // 当前页
Size int `json:"size"` // 每页数量
}
// TenantInfo 租户信息
type TenantInfo struct {
Id string `json:"id"` // 租户ID
Name string `json:"name"` // 租户名称
Level string `json:"level"` // 租户级别
}
-35
View File
@@ -1,35 +0,0 @@
package dto
import (
"github.com/gogf/gf/v2/frame/g"
)
// SetTenantRateLimitReq 设置租户限流配置请求
type SetTenantRateLimitReq struct {
g.Meta `path:"/setTenantRateLimit" method:"post" tags:"租户限流" summary:"设置租户限流配置" dc:"设置指定租户的请求次数限制配置(实际使用全局配置)"`
TenantID int64 `json:"tenant_id" v:"required"` // 租户ID(仅用于记录,实际使用全局配置)
RequestsPerSecond float64 `json:"requests_per_second" v:"required"` // 每秒请求数
Burst int `json:"burst" v:"required"` // 突发请求数
WindowSeconds int `json:"window_seconds" v:"required"` // 时间窗口(秒)
}
// SetTenantRateLimitRes 设置租户限流配置响应
type SetTenantRateLimitRes struct {
Success bool `json:"success"` // 是否成功
}
// GetTenantRateLimitUsageReq 获取租户限流使用情况请求
type GetTenantRateLimitUsageReq struct {
g.Meta `path:"/getTenantRateLimitUsage" method:"get" tags:"租户限流" summary:"获取租户限流使用情况" dc:"获取指定租户的请求次数使用情况"`
TenantID int64 `json:"tenant_id" v:"required"` // 租户ID
}
// GetTenantRateLimitUsageRes 获取租户限流使用情况响应
type GetTenantRateLimitUsageRes struct {
TenantID int64 `json:"tenant_id"` // 租户ID
CurrentUsed int64 `json:"current_used"` // 当前已使用请求数
MaxAllowed int64 `json:"max_allowed"` // 最大允许请求数(基于全局配置)
UsagePercent float64 `json:"usage_percent"` // 使用率百分比
}
-89
View File
@@ -1,89 +0,0 @@
package dto
import (
"github.com/gogf/gf/v2/frame/g"
)
// CreateStrategyReq 创建策略请求
type CreateStrategyReq struct {
g.Meta `path:"/create" method:"post" tags:"策略管理" summary:"创建匹配策略" dc:"创建新的广告匹配策略"`
Name string `json:"name" v:"required|length:3,50"` // 策略名称
Description string `json:"description" v:"max:500"` // 描述
TenantLevel string `json:"tenant_level" v:"required|in:basic,standard,premium"` // 租户级别
MinConversion float64 `json:"min_conversion" v:"required|min:0|max:1"` // 最低转化率
MaxConversion float64 `json:"max_conversion" v:"required|min:0|max:1"` // 最高转化率
SourceWeights map[string]int `json:"source_weights" v:"required"` // 广告源权重
MaxAdsPerReq int `json:"max_ads_per_req" v:"required|min:1|max:50"` // 每次请求最大广告数
MaxReqPerHour int `json:"max_req_per_hour" v:"required|min:1"` // 每小时最大请求次数
Priority int `json:"priority" v:"required|min:0|max:100"` // 优先级
Status string `json:"status" v:"required|in:active,inactive"` // 状态
}
// UpdateStrategyReq 更新策略请求
type UpdateStrategyReq struct {
g.Meta `path:"/update" method:"put" tags:"策略管理" summary:"更新匹配策略" dc:"更新现有的广告匹配策略"`
Id int64 `json:"id" v:"required"` // 策略ID
Name string `json:"name" v:"required|length:3,50"` // 策略名称
Description string `json:"description" v:"max:500"` // 描述
TenantLevel string `json:"tenant_level" v:"required|in:basic,standard,premium"` // 租户级别
MinConversion float64 `json:"min_conversion" v:"required|min:0|max:1"` // 最低转化率
MaxConversion float64 `json:"max_conversion" v:"required|min:0|max:1"` // 最高转化率
SourceWeights map[string]int `json:"source_weights" v:"required"` // 广告源权重
MaxAdsPerReq int `json:"max_ads_per_req" v:"required|min:1|max:50"` // 每次请求最大广告数
MaxReqPerHour int `json:"max_req_per_hour" v:"required|min:1"` // 每小时最大请求次数
Priority int `json:"priority" v:"required|min:0|max:100"` // 优先级
Status string `json:"status" v:"required|in:active,inactive"` // 状态
}
// DeleteStrategyReq 删除策略请求
type DeleteStrategyReq struct {
g.Meta `path:"/delete" method:"delete" tags:"策略管理" summary:"删除匹配策略" dc:"删除指定的广告匹配策略"`
Id int64 `json:"id" v:"required"` // 策略ID
}
// GetStrategyReq 获取策略请求
type GetStrategyReq struct {
g.Meta `path:"/getByID" method:"get" tags:"策略管理" summary:"获取策略详情" dc:"获取指定策略的详细信息"`
Id int64 `json:"id" v:"required"` // 策略ID
}
// GetStrategyListReq 获取策略列表请求
type GetStrategyListReq struct {
g.Meta `path:"/getList" method:"get" tags:"策略管理" summary:"获取策略列表" dc:"分页获取策略列表"`
Page int `json:"page" v:"required|min:1"` // 页码
Size int `json:"size" v:"required|min:1|max:100"` // 每页数量
TenantLevel string `json:"tenant_level"` // 租户级别筛选
Status string `json:"status"` // 状态筛选
}
// StrategyRes 策略响应
type StrategyRes struct {
Id int64 `json:"id"` // ID
Name string `json:"name"` // 策略名称
Description string `json:"description"` // 描述
TenantLevel string `json:"tenant_level"` // 租户级别
MinConversion float64 `json:"min_conversion"` // 最低转化率
MaxConversion float64 `json:"max_conversion"` // 最高转化率
SourceWeights map[string]int `json:"source_weights"` // 广告源权重
MaxAdsPerReq int `json:"max_ads_per_req"` // 每次请求最大广告数
MaxReqPerHour int `json:"max_req_per_hour"` // 每小时最大请求次数
Priority int `json:"priority"` // 优先级
Status string `json:"status"` // 状态
CreatedAt string `json:"created_at"` // 创建时间
UpdatedAt string `json:"updated_at"` // 更新时间
CreatedBy int64 `json:"created_by"` // 创建人
UpdatedBy int64 `json:"updated_by"` // 更新人
}
// GetStrategyListRes 获取策略列表响应
type GetStrategyListRes struct {
List []*StrategyRes `json:"list"` // 策略列表
Total int64 `json:"total"` // 总数
Page int `json:"page"` // 当前页
Size int `json:"size"` // 每页数量
}
// DeleteStrategyRes 删除策略响应
type DeleteStrategyRes struct {
Success bool `json:"success"` // 是否成功
}
-68
View File
@@ -1,68 +0,0 @@
package entity
import (
"cid/model/config"
"gitea.com/red-future/common/beans"
)
const AdCreativeCollection = "ad_creative"
// AdCreative 广告创意素材实体
type AdCreative struct {
beans.MongoBaseDO `bson:",inline" json:",inline"`
AdvertiserId string `bson:"advertiserId" json:"advertiserId"` // 广告主ID
// 基本信息
Name string `bson:"name" json:"name"` // 创意名称
Title string `bson:"title" json:"title"` // 广告标题
Description string `bson:"description" json:"description"` // 广告描述
AdType string `bson:"adType" json:"adType"` // 广告类型:image、video、native、interstitial等
Format string `bson:"format" json:"format"` // 创意格式:jpg、png、mp4、html等
// 素材信息
MaterialURL string `bson:"materialUrl" json:"materialUrl"` // 素材URL
ThumbnailURL string `bson:"thumbnailUrl" json:"thumbnailUrl"` // 缩略图URL
LandingPageURL string `bson:"landingPageUrl" json:"landingPageUrl"` // 落地页URL
DisplayURL string `bson:"displayUrl" json:"displayUrl"` // 显示URL
// 尺寸和文件信息
Width int64 `bson:"width" json:"width"` // 宽度(px)
Height int64 `bson:"height" json:"height"` // 高度(px)
Size int64 `bson:"size" json:"size"` // 文件大小(bytes)
Duration int64 `bson:"duration" json:"duration"` // 时长(秒)
HasAudio bool `bson:"hasAudio" json:"hasAudio"` // 是否有音频
AspectRatio string `bson:"aspectRatio" json:"aspectRatio"` // 宽高比
// 技术信息
MimeType string `bson:"mimeType" json:"mimeType"` // MIME类型
Source string `bson:"source" json:"source"` // 来源:upload、sync、generate
BackupURL string `bson:"backupUrl" json:"backupUrl"` // 备份URL
CDNURL string `bson:"cdnUrl" json:"cdnUrl"` // CDN加速URL
CompressInfo string `bson:"compressInfo" json:"compressInfo"` // 压缩信息(JSON格式)
// 平台兼容性
SupportedPlatforms []string `bson:"supportedPlatforms" json:"supportedPlatforms"` // 支持的平台
PlatformSpecific string `bson:"platformSpecific" json:"platformSpecific"` // 平台特定配置(JSON格式)
// 外部平台信息
ExternalCreativeId string `bson:"externalCreativeId" json:"externalCreativeId"` // 外部创意ID
PlatformId string `bson:"platformId" json:"platformId"` // 平台ID
SyncStatus string `bson:"syncStatus" json:"syncStatus"` // 同步状态
LastSyncTime int64 `bson:"lastSyncTime" json:"lastSyncTime"` // 最后同步时间
// 基础配置
config.BaseConfig `bson:",inline" json:",inline"` // 内联基础配置
// 限制配置
config.RestrictionConfig `bson:",inline" json:",inline"` // 内联限制配置
// 其他信息
Status string `bson:"status" json:"status"` // 状态:active、inactive、archived
ExpireTime int64 `bson:"expireTime" json:"expireTime"` // 过期时间
}
// GetCollectionName 获取集合名称
func (a *AdCreative) GetCollectionName() string {
return AdCreativeCollection
}
-57
View File
@@ -1,57 +0,0 @@
package entity
import (
"cid/model/config"
"gitea.com/red-future/common/beans"
)
const AdPlatformCollection = "ad_platform"
// AdPlatform 广告平台实体
type AdPlatform struct {
beans.MongoBaseDO `bson:",inline" json:",inline"`
Status string `bson:"status" json:"status"` // 状态:active、inactive、maintenance等
// 平台基本信息
Name string `bson:"name" json:"name"` // 平台名称:小红书、抖音、快手、京东、淘宝、百度等
Code string `bson:"code" json:"code"` // 平台编码,唯一标识
DisplayName string `bson:"displayName" json:"displayName"` // 显示名称
Logo string `bson:"logo" json:"logo"` // 平台Logo
Description string `bson:"description" json:"description"` // 平台描述
Category string `bson:"category" json:"category"` // 平台分类:social、ecommerce、search、short_video等
// 支持的广告类型
SupportedAdTypes []string `bson:"supportedAdTypes" json:"supportedAdTypes"` // 支持的广告类型
SupportedFormats []string `bson:"supportedFormats" json:"supportedFormats"` // 支持的广告格式
// 技术能力
RealTimeBidding bool `bson:"realTimeBidding" json:"realTimeBidding"` // 是否支持实时竞价
ProgrammaticGuaranteed bool `bson:"programmaticGuaranteed" json:"programmaticGuaranteed"` // 是否支持程序化保障
HeaderBidding bool `bson:"headerBidding" json:"headerBidding"` // 是否支持Header Bidding
// API配置
config.APIConfig `bson:",inline" json:",inline"` // 内联API配置
// 竞价配置
config.BiddingConfig `bson:",inline" json:",inline"` // 内联竞价配置
// 支付配置
config.PaymentConfig `bson:",inline" json:",inline"` // 内联支付配置
// 限流配置
RateLimit int64 `bson:"rateLimit" json:"rateLimit"` // 速率限制
MaxBudgetPerDay int64 `bson:"maxBudgetPerDay" json:"maxBudgetPerDay"` // 每日最大预算
LastSyncTime int64 `bson:"lastSyncTime" json:"lastSyncTime"` // 最后同步时间
// 联系信息
SupportContact string `bson:"supportContact" json:"supportContact"` // 技术支持联系方式
AccountManager string `bson:"accountManager" json:"accountManager"` // 客户经理
TechDocumentation string `bson:"techDocumentation" json:"techDocumentation"` // 技术文档链接
}
// GetCollectionName 获取集合名称
func (a *AdPlatform) GetCollectionName() string {
return AdPlatformCollection
}
-85
View File
@@ -1,85 +0,0 @@
package entity
import (
"cid/model/config"
"gitea.com/red-future/common/beans"
)
const AdPositionCollection = "ad_position"
// AdPosition 广告位实体
type AdPosition struct {
beans.MongoBaseDO `bson:",inline" json:",inline"`
Status string `bson:"status" json:"status"` // 状态:active、inactive、maintenance等
// 基本信息
Name string `bson:"name" json:"name"` // 广告位名称
Description string `bson:"description" json:"description"` // 广告位描述
PositionCode string `bson:"positionCode" json:"positionCode"` // 广告位编码,用于标识
AdFormat string `bson:"adFormat" json:"adFormat"` // 支持的广告格式
// 尺寸信息
Width int64 `bson:"width" json:"width"` // 宽度(px)
Height int64 `bson:"height" json:"height"` // 高度(px)
// 位置信息
Page string `bson:"page" json:"page"` // 所属页面
Section string `bson:"section" json:"section"` // 页面区域
Location string `bson:"location" json:"location"` // 具体位置
// 展示设置
MaxAds int `bson:"maxAds" json:"maxAds"` // 最大广告数量
RefreshInterval int `bson:"refreshInterval" json:"refreshInterval"` // 刷新间隔(秒)
IsLazyLoad bool `bson:"isLazyLoad" json:"isLazyLoad"` // 是否懒加载
// 定价设置
PricingModel string `bson:"pricingModel" json:"pricingModel"` // 计费模型:CPC、CPM、CPA等
BasePrice int64 `bson:"basePrice" json:"basePrice"` // 基础价格(分)
FloorPrice int64 `bson:"floorPrice" json:"floorPrice"` // 底价(分)
PriceUnit string `bson:"priceUnit" json:"priceUnit"` // 价格单位:千次展示、单次点击、单次转化等
// 展示规则
DisplayRules *DisplayRules `bson:"displayRules" json:"displayRules"` // 展示规则
// 限制配置
config.RestrictionConfig `bson:",inline" json:",inline"` // 内联限制配置
// 其他状态
IsExclusive bool `bson:"isExclusive" json:"isExclusive"` // 是否独占广告位
}
// DisplayRules 广告位展示规则
type DisplayRules struct {
// 频次控制
FrequencyCap *FrequencyCap `bson:"frequencyCap" json:"frequencyCap"` // 频次控制
// 展示条件
DisplayConditions []DisplayCondition `bson:"displayConditions" json:"displayConditions"` // 展示条件
// 排除条件
ExcludeConditions []ExcludeCondition `bson:"excludeConditions" json:"excludeConditions"` // 排除条件
}
// FrequencyCap 频次控制
type FrequencyCap struct {
Impressions int `bson:"impressions" json:"impressions"` // 展示次数
TimeWindow int `bson:"timeWindow" json:"timeWindow"` // 时间窗口(小时)
}
// DisplayCondition 展示条件
type DisplayCondition struct {
Type string `bson:"type" json:"type"` // 条件类型
Value interface{} `bson:"value" json:"value"` // 条件值
}
// ExcludeCondition 排除条件
type ExcludeCondition struct {
Type string `bson:"type" json:"type"` // 条件类型
Value interface{} `bson:"value" json:"value"` // 条件值
}
// GetCollectionName 获取集合名称
func (a *AdPosition) GetCollectionName() string {
return AdPositionCollection
}
-74
View File
@@ -1,74 +0,0 @@
package entity
import (
"cid/model/config"
"gitea.com/red-future/common/beans"
)
const AdSourceCollection = "ad_source"
// AdSource 广告源实体
type AdSource struct {
beans.MongoBaseDO `bson:",inline" json:",inline"`
Status string `bson:"status" json:"status"` // 状态:active、inactive、maintenance等
// 基本信息
Name string `bson:"name" json:"name"` // 广告源名称
Code string `bson:"code" json:"code"` // 广告源编码,唯一标识
Provider string `bson:"provider" json:"provider"` // 提供商:self(自营)、chuanshanjia(穿山甲)、gdt(腾讯广点通)、baidu(百度)、byteance(字节跳动)等
Type string `bson:"type" json:"type"` // 类型:self(自营)、third_party(第三方)、exchange(广告交易平台)、platform_ad_source(平台广告源)
Category string `bson:"category" json:"category"` // 分类:network、ssp、dsp、rtb等
// 连接配置
Config string `bson:"config" json:"config"` // 广告源配置(JSON字符串)
// API配置
config.APIConfig `bson:",inline" json:",inline"` // 内联API配置
// 创意配置
config.CreativeConfig `bson:",inline" json:",inline"` // 内联创意配置
// 广告源能力
Capabilities *AdSourceCapabilities `bson:"capabilities" json:"capabilities"` // 广告源能力
// 支付配置
config.PaymentConfig `bson:",inline" json:",inline"` // 内联支付配置
}
// AdSourceCapabilities 广告源能力
type AdSourceCapabilities struct {
// 广告格式
SupportedFormats []AdFormat `bson:"supportedFormats" json:"supportedFormats"` // 支持的广告格式
// 功能特性
RealTimeBidding bool `bson:"realTimeBidding" json:"realTimeBidding"` // 实时竞价
HeaderBidding bool `bson:"headerBidding" json:"headerBidding"` // 标题竞价
ProgrammaticDirect bool `bson:"programmaticDirect" json:"programmaticDirect"` // 程序化直购
PrivateMarketplace bool `bson:"privateMarketplace" json:"privateMarketplace"` // 私有交易市场
// 质量控制
FraudDetection bool `bson:"fraudDetection" json:"fraudDetection"` // 反欺诈检测
BrandSafety bool `bson:"brandSafety" json:"brandSafety"` // 品牌安全
Viewability bool `bson:"viewability" json:"viewability"` // 可见度验证
CreativeApproval bool `bson:"creativeApproval" json:"creativeApproval"` // 创意审核
// 数据能力
AudienceTargeting bool `bson:"audienceTargeting" json:"audienceTargeting"` // 受众定向
ContextualTargeting bool `bson:"contextualTargeting" json:"contextualTargeting"` // 上下文定向
CrossDeviceTargeting bool `bson:"crossDeviceTargeting" json:"crossDeviceTargeting"` // 跨设备定向
}
// AdFormat 广告格式
type AdFormat struct {
Type string `bson:"type" json:"type"` // 格式类型:banner、video、native、interstitial等
Name string `bson:"name" json:"name"` // 格式名称
Width int `bson:"width" json:"width"` // 宽度
Height int `bson:"height" json:"height"` // 高度
MimeType string `bson:"mimeType" json:"mimeType"` // MIME类型
}
// GetCollectionName 获取集合名称
func (a *AdSource) GetCollectionName() string {
return AdSourceCollection
}
-44
View File
@@ -1,44 +0,0 @@
package entity
import (
"gitea.com/red-future/common/beans"
)
const AdTypeCollection = "ad_type"
// AdType 广告类型实体
type AdType struct {
beans.MongoBaseDO `bson:",inline"`
// 广告类型信息
Name string `bson:"name" json:"name"` // 广告类型名称
Code string `bson:"code" json:"code"` // 广告类型编码
Description string `bson:"description" json:"description"` // 广告类型描述
Icon string `bson:"icon" json:"icon"` // 广告类型图标
// 类型配置
Category string `bson:"category" json:"category"` // 分类:display, video, native, interstitial
Platforms []string `bson:"platforms" json:"platforms"` // 支持的平台
Formats []string `bson:"formats" json:"formats"` // 支持格式
Dimensions []string `bson:"dimensions" json:"dimensions"` // 尺寸规格
// 技术要求
MaxFileSize int64 `bson:"maxFileSize" json:"maxFileSize"` // 最大文件大小(bytes)
MaxDuration int64 `bson:"maxDuration" json:"maxDuration"` // 最大时长(秒)
SupportedMimeTypes []string `bson:"supportedMimeTypes" json:"supportedMimeTypes"` // 支持的MIME类型
// 业务配置
BidType string `bson:"bidType" json:"bidType"` // 竞价类型:CPM, CPC, CPA
MinBidPrice int64 `bson:"minBidPrice" json:"minBidPrice"` // 最低出价(分)
MaxBidPrice int64 `bson:"maxBidPrice" json:"maxBidPrice"` // 最高出价(分)
// 状态信息
Status string `bson:"status" json:"status"` // 状态:active, inactive
SortOrder int `bson:"sortOrder" json:"sortOrder"` // 排序顺序
// 统计信息
DailyImpression int64 `bson:"dailyImpression" json:"dailyImpression"` // 日展示量
DailyClick int64 `bson:"dailyClick" json:"dailyClick"` // 日点击量
Remark string `bson:"remark" json:"remark"` // 备注
}
-55
View File
@@ -1,55 +0,0 @@
package entity
import (
"cid/model/config"
"gitea.com/red-future/common/beans"
)
const AdvertisementCollection = "advertisement"
// Advertisement 广告实体
type Advertisement struct {
beans.MongoBaseDO `bson:",inline" json:",inline"`
AdvertiserId string `bson:"advertiserId" json:"advertiserId"` // 广告主ID
// 广告基本信息
Title string `bson:"title" json:"title"` // 广告标题
Description string `bson:"description" json:"description"` // 广告描述
AdPositionId string `bson:"adPositionId" json:"adPositionId"` // 广告位ID
AdType string `bson:"adType" json:"adType"` // 广告类型:图片、视频、文字等
AdFormat string `bson:"adFormat" json:"adFormat"` // 广告格式
MaterialUrl string `bson:"materialUrl" json:"materialUrl"` // 广告素材URL
TargetUrl string `bson:"targetUrl" json:"targetUrl"` // 目标链接(点击跳转或落地页)
// 平台和广告源信息
AdSourceId string `bson:"adSourceId" json:"adSourceId"` // 广告源ID
AdPlatformId string `bson:"adPlatformId" json:"adPlatformId"` // 广告平台ID(当广告来自第三方平台时)
ExternalAdId string `bson:"externalAdId" json:"externalAdId"` // 外部广告ID(第三方平台的广告ID)
AdProvider string `bson:"adProvider" json:"adProvider"` // 广告提供者:self、chuanshanjia、xiaohongshu、douyin等
// 投放配置
config.BudgetConfig `bson:",inline" json:",inline"` // 内联预算配置
BidAmount int64 `bson:"bidAmount" json:"bidAmount"` // 出价(分)
BillingType string `bson:"billingType" json:"billingType"` // 计费类型:CPC、CPM、CPA等
// 定向条件
Targeting *UnifiedTargeting `bson:"targeting" json:"targeting"` // 统一定向条件
// 审核状态
AuditStatus string `bson:"auditStatus" json:"auditStatus"` // 广告状态:待审核、审核中、已通过、已拒绝、投放中、已暂停、已结束
AuditReason string `bson:"auditReason" json:"auditReason"` // 审核不通过原因
AuditTime int64 `bson:"auditTime" json:"auditTime"` // 审核时间
AuditBy string `bson:"auditBy" json:"auditBy"` // 审核人
// 限制配置
config.RestrictionConfig `bson:",inline" json:",inline"` // 内联限制配置
// 其他状态信息
Status string `bson:"status" json:"status"` // 业务状态:active、inactive、archived
}
// GetCollectionName 获取集合名称
func (a *Advertisement) GetCollectionName() string {
return AdvertisementCollection
}
-53
View File
@@ -1,53 +0,0 @@
package entity
import (
"gitea.com/red-future/common/beans"
)
const AdvertiserCollection = "advertiser"
// Advertiser 广告主实体
type Advertiser struct {
beans.MongoBaseDO `bson:",inline" json:",inline"`
Status string `bson:"status" json:"status"` // 状态:active、inactive、maintenance等
// 基本信息
Name string `bson:"name" json:"name"` // 广告主名称
ContactName string `bson:"contactName" json:"contactName"` // 联系人姓名
ContactPhone string `bson:"contactPhone" json:"contactPhone"` // 联系电话
ContactEmail string `bson:"contactEmail" json:"contactEmail"` // 联系邮箱
Company string `bson:"company" json:"company"` // 公司名称
Scale string `bson:"scale" json:"scale"` // 公司规模
// 证件信息
BusinessLicenseUrl string `bson:"businessLicenseUrl" json:"businessLicenseUrl"` // 营业执照URL
ICPLicenseUrl string `bson:"icpLicenseUrl" json:"icpLicenseUrl"` // ICP备案截图URL
OtherLicenseUrls []string `bson:"otherLicenseUrls" json:"otherLicenseUrls"` // 其他证件URL
// 财务信息
BankName string `bson:"bankName" json:"bankName"` // 开户银行
BankAccount string `bson:"bankAccount" json:"bankAccount"` // 银行账号
AccountName string `bson:"accountName" json:"accountName"` // 账户名称
// 合同信息
ContractId string `bson:"contractId" json:"contractId"` // 合同编号
ContractType string `bson:"contractType" json:"contractType"` // 合同类型
ContractUrl string `bson:"contractUrl" json:"contractUrl"` // 合同文件URL
SignDate int64 `bson:"signDate" json:"signDate"` // 签约日期
ExpireDate int64 `bson:"expireDate" json:"expireDate"` // 到期日期
// 审核状态
AuditStatus string `bson:"auditStatus" json:"auditStatus"` // 广告主状态:待审核、审核中、已通过、已拒绝、已冻结
AuditReason string `bson:"auditReason" json:"auditReason"` // 审核不通过原因
AuditTime int64 `bson:"auditTime" json:"auditTime"` // 审核时间
AuditBy string `bson:"auditBy" json:"auditBy"` // 审核人
// 系统信息
AccountBalance int64 `bson:"accountBalance" json:"accountBalance"` // 账户余额(分)
CreditLimit int64 `bson:"creditLimit" json:"creditLimit"` // 授信额度(分)
}
// GetCollectionName 获取集合名称
func (a *Advertiser) GetCollectionName() string {
return AdvertiserCollection
}
-32
View File
@@ -1,32 +0,0 @@
package entity
import (
"gitea.com/red-future/common/beans"
)
const AppPlatformConfigCollection = "app_platform_config"
// AppPlatformConfig 应用平台配置实体
type AppPlatformConfig struct {
beans.MongoBaseDO `bson:",inline" json:",inline"`
Status string `bson:"status" json:"status"` // 状态:active、inactive、maintenance等
// 关联信息
AppID string `bson:"appId" json:"appId"` // 应用ID
PlatformID string `bson:"platformId" json:"platformId"` // 平台ID
// 配置信息
Config string `bson:"config" json:"config"` // 配置信息(JSON字符串)
MaxAdsPerReq int `bson:"maxAdsPerReq" json:"maxAdsPerReq"` // 每次请求最大广告数
// 定向配置
TargetingRules string `bson:"targetingRules" json:"targetingRules"` // 定向规则(JSON字符串)
// 过滤配置
FilterRules string `bson:"filterRules" json:"filterRules"` // 过滤规则(JSON字符串)
}
// GetCollectionName 获取集合名称
func (a *AppPlatformConfig) GetCollectionName() string {
return AppPlatformConfigCollection
}
-54
View File
@@ -1,54 +0,0 @@
package entity
import (
"gitea.com/red-future/common/beans"
)
const ApplicationCollection = "application"
// Application 应用实体
type Application struct {
beans.MongoBaseDO `bson:",inline" json:",inline"`
Status string `bson:"status" json:"status"` // 状态:active、inactive、maintenance等
// 应用基本信息
Name string `bson:"name" json:"name"` // 应用名称
Code string `bson:"code" json:"code"` // 应用编码
Description string `bson:"description" json:"description"` // 应用描述
AppKey string `bson:"appKey" json:"appKey"` // 应用密钥
AppSecret string `bson:"appSecret" json:"appSecret"` // 应用秘钥
Platform string `bson:"platform" json:"platform"` // 平台:web、ios、android、h5
Version string `bson:"version" json:"version"` // 版本号
PackageName string `bson:"packageName" json:"packageName"` // 包名(移动应用)
BundleID string `bson:"bundleId" json:"bundleId"` // Bundle IDiOS应用)
AppStoreURL string `bson:"appStoreUrl" json:"appStoreUrl"` // 应用商店URL
// 应用配置
Config string `bson:"config" json:"config"` // 应用配置(JSON字符串)
Permissions string `bson:"permissions" json:"permissions"` // 权限配置(JSON字符串)
// 应用分类和标签
Categories []string `bson:"categories" json:"categories"` // 应用分类
Tags []string `bson:"tags" json:"tags"` // 标签
AdTypes []string `bson:"adTypes" json:"adTypes"` // 支持的广告类型
// 回调配置
CallbackURL string `bson:"callbackUrl" json:"callbackUrl"` // 回调URL
// 应用特定统计
DailyActiveUsers int64 `bson:"dailyActiveUsers" json:"dailyActiveUsers"` // 日活用户数
MonthlyActiveUsers int64 `bson:"monthlyActiveUsers" json:"monthlyActiveUsers"` // 月活用户数
TotalRequests int64 `bson:"totalRequests" json:"totalRequests"` // 总请求数
DailyRequests int64 `bson:"dailyRequests" json:"dailyRequests"` // 日请求数
MonthlyRequests int64 `bson:"monthlyRequests" json:"monthlyRequests"` // 月请求数
// 联系信息
ContactName string `bson:"contactName" json:"contactName"` // 联系人姓名
ContactEmail string `bson:"contactEmail" json:"contactEmail"` // 联系邮箱
ContactPhone string `bson:"contactPhone" json:"contactPhone"` // 联系电话
}
// GetCollectionName 获取集合名称
func (a *Application) GetCollectionName() string {
return ApplicationCollection
}
+83
View File
@@ -0,0 +1,83 @@
package attribution
import (
"github.com/gogf/gf/v2/os/gtime"
"gitea.redpowerfuture.com/red-future/common/beans"
)
// ClickLog 点击日志实体(CID核心表,按 day 分区)
type ClickLog struct {
beans.SQLBaseDO `orm:",inherit"`
// 业务字段
ClickID string `orm:"click_id" json:"clickId" description:"对外CID"`
RequestID string `orm:"request_id" json:"requestId" description:"广告平台请求号(去重键)"`
Channel string `orm:"channel" json:"channel" description:"投放通道 external外投/self_dsp自有"`
AdPlatform string `orm:"ad_platform" json:"adPlatform" description:"广告平台 douyin/xiaohongshu/kuaishou/self_dsp"`
AccountID string `orm:"account_id" json:"accountId" description:"广告账户ID"`
CampaignID string `orm:"campaign_id" json:"campaignId" description:"平台侧计划ID(透传)"`
LocalCampaignID int64 `orm:"local_campaign_id" json:"localCampaignId" description:"本地计划ID(回传路由)"`
LocalAdID int64 `orm:"local_ad_id" json:"localAdId" description:"本地广告ID(DSP必填,外投按映射回填)"`
LocalCreativeID int64 `orm:"local_creative_id" json:"localCreativeId" description:"本地创意ID(DSP必填,外投按映射回填)"`
SlotID int64 `orm:"slot_id" json:"slotId" description:"自有DSP广告位ID"`
AdGroupID string `orm:"ad_group_id" json:"adGroupId" description:"广告组ID"`
AdID string `orm:"ad_id" json:"adId" description:"广告ID"`
CreativeID string `orm:"creative_id" json:"creativeId" description:"创意ID"`
DeviceID string `orm:"device_id" json:"deviceId" description:"设备ID oaid/imei/idfa"`
IP string `orm:"ip" json:"ip" description:"点击IP"`
UA string `orm:"ua" json:"ua" description:"用户代理"`
LandingURL string `orm:"landing_url" json:"landingUrl" description:"原始落地页URL"`
RedirectURL string `orm:"redirect_url" json:"redirectUrl" description:"跳转下游URL(带click_id)"`
Extra string `orm:"extra" json:"extra" description:"平台透传参数(JSON)"`
Day *gtime.Time `orm:"day" json:"day" description:"分区日期"`
}
// ClickLogCol 点击日志表字段定义
type ClickLogCol struct {
beans.SQLBaseCol
ClickID string
RequestID string
Channel string
AdPlatform string
AccountID string
CampaignID string
LocalCampaignID string
LocalAdID string
LocalCreativeID string
SlotID string
AdGroupID string
AdID string
CreativeID string
DeviceID string
IP string
UA string
LandingURL string
RedirectURL string
Extra string
Day string
}
// ClickLogCols 点击日志表字段常量
var ClickLogCols = ClickLogCol{
SQLBaseCol: beans.DefSQLBaseCol,
ClickID: "click_id",
RequestID: "request_id",
Channel: "channel",
AdPlatform: "ad_platform",
AccountID: "account_id",
CampaignID: "campaign_id",
LocalCampaignID: "local_campaign_id",
LocalAdID: "local_ad_id",
LocalCreativeID: "local_creative_id",
SlotID: "slot_id",
AdGroupID: "ad_group_id",
AdID: "ad_id",
CreativeID: "creative_id",
DeviceID: "device_id",
IP: "ip",
UA: "ua",
LandingURL: "landing_url",
RedirectURL: "redirect_url",
Extra: "extra",
Day: "day",
}
@@ -0,0 +1,78 @@
package attribution
import (
"gitea.redpowerfuture.com/red-future/common/beans"
)
// ConversionOrder 转化订单实体(电商平台回传的订单/转化)
type ConversionOrder struct {
beans.SQLBaseDO `orm:",inherit"`
// 业务字段
ConversionID string `orm:"conversion_id" json:"conversionId" description:"转化事件ID(幂等键)"`
OrderID string `orm:"order_id" json:"orderId" description:"电商平台订单号"`
CommercePlatform string `orm:"commerce_platform" json:"commercePlatform" description:"电商平台 jd/taobao"`
ClickID string `orm:"click_id" json:"clickId" description:"关联点击CID"`
LocalCampaignID int64 `orm:"local_campaign_id" json:"localCampaignId" description:"本地计划ID(回传路由)"`
AdAccountID int64 `orm:"ad_account_id" json:"adAccountId" description:"广告账户ID(回传凭据)"`
DeviceID string `orm:"device_id" json:"deviceId" description:"设备ID(无click_id时兜底归因)"`
ItemID string `orm:"item_id" json:"itemId" description:"商品ID"`
ItemName string `orm:"item_name" json:"itemName" description:"商品名称"`
SkuID string `orm:"sku_id" json:"skuId" description:"SKU ID"`
ConversionType string `orm:"conversion_type" json:"conversionType" description:"转化类型 order/pay/refund/cart"`
OrderAmount int64 `orm:"order_amount" json:"orderAmount" description:"订单金额(分)"`
PayAmount int64 `orm:"pay_amount" json:"payAmount" description:"实付金额(分)"`
Quantity int `orm:"quantity" json:"quantity" description:"商品数量"`
OrderStatus string `orm:"order_status" json:"orderStatus" description:"订单状态 paid/refunded/canceled"`
AttributedBy string `orm:"attributed_by" json:"attributedBy" description:"归因方式 click_id/device/manual"`
ReportStatus int `orm:"report_status" json:"reportStatus" description:"回传状态 0待回传 1已回传 2失败"`
ReportCount int `orm:"report_count" json:"reportCount" description:"回传次数"`
FirstClickTime int64 `orm:"first_click_time" json:"firstClickTime" description:"首次点击时间戳"`
}
// ConversionOrderCol 转化订单表字段定义
type ConversionOrderCol struct {
beans.SQLBaseCol
ConversionID string
OrderID string
CommercePlatform string
ClickID string
LocalCampaignID string
AdAccountID string
DeviceID string
ItemID string
ItemName string
SkuID string
ConversionType string
OrderAmount string
PayAmount string
Quantity string
OrderStatus string
AttributedBy string
ReportStatus string
ReportCount string
FirstClickTime string
}
// ConversionOrderCols 转化订单表字段常量
var ConversionOrderCols = ConversionOrderCol{
SQLBaseCol: beans.DefSQLBaseCol,
ConversionID: "conversion_id",
OrderID: "order_id",
CommercePlatform: "commerce_platform",
ClickID: "click_id",
LocalCampaignID: "local_campaign_id",
AdAccountID: "ad_account_id",
DeviceID: "device_id",
ItemID: "item_id",
ItemName: "item_name",
SkuID: "sku_id",
ConversionType: "conversion_type",
OrderAmount: "order_amount",
PayAmount: "pay_amount",
Quantity: "quantity",
OrderStatus: "order_status",
AttributedBy: "attributed_by",
ReportStatus: "report_status",
ReportCount: "report_count",
FirstClickTime: "first_click_time",
}
+57
View File
@@ -0,0 +1,57 @@
package attribution
import (
"gitea.redpowerfuture.com/red-future/common/beans"
)
// ReportTask 转化回传任务实体(回传广告平台的任务队列,失败退避重试)
type ReportTask struct {
beans.SQLBaseDO `orm:",inherit"`
// 业务字段
ConversionID string `orm:"conversion_id" json:"conversionId" description:"关联转化事件ID"`
ClickID string `orm:"click_id" json:"clickId" description:"点击CID"`
AdPlatform string `orm:"ad_platform" json:"adPlatform" description:"回传目标广告平台"`
AdAccountID int64 `orm:"ad_account_id" json:"adAccountId" description:"广告账户ID(回传凭据)"`
CampaignID int64 `orm:"campaign_id" json:"campaignId" description:"回传目标本地计划ID"`
TaskType string `orm:"task_type" json:"taskType" description:"回传类型 conversion/refund"`
Status int `orm:"status" json:"status" description:"状态 0待回传 1回传中 2成功 3失败"`
RetryCount int `orm:"retry_count" json:"retryCount" description:"重试次数"`
NextRetryAt int64 `orm:"next_retry_at" json:"nextRetryAt" description:"下次重试时间戳"`
RequestBody string `orm:"request_body" json:"requestBody" description:"回传请求体(JSON)"`
ResponseBody string `orm:"response_body" json:"responseBody" description:"回传响应体(JSON)"`
TraceID string `orm:"trace_id" json:"traceId" description:"链路追踪ID"`
}
// ReportTaskCol 回传任务表字段定义
type ReportTaskCol struct {
beans.SQLBaseCol
ConversionID string
ClickID string
AdPlatform string
AdAccountID string
CampaignID string
TaskType string
Status string
RetryCount string
NextRetryAt string
RequestBody string
ResponseBody string
TraceID string
}
// ReportTaskCols 回传任务表字段常量
var ReportTaskCols = ReportTaskCol{
SQLBaseCol: beans.DefSQLBaseCol,
ConversionID: "conversion_id",
ClickID: "click_id",
AdPlatform: "ad_platform",
AdAccountID: "ad_account_id",
CampaignID: "campaign_id",
TaskType: "task_type",
Status: "status",
RetryCount: "retry_count",
NextRetryAt: "next_retry_at",
RequestBody: "request_body",
ResponseBody: "response_body",
TraceID: "trace_id",
}
+89
View File
@@ -0,0 +1,89 @@
package check
import (
"gitea.redpowerfuture.com/red-future/common/beans"
)
// MaterialVerifyLog 素材校验日志实体
type MaterialVerifyLog struct {
beans.SQLBaseDO `orm:",inherit"`
// 业务字段
TenantID int64 `orm:"tenant_id" json:"tenantId" description:"租户ID"`
MaterialType string `orm:"material_type" json:"materialType" description:"素材类型 IMAGE/VIDEO"`
MaterialID string `orm:"material_id" json:"materialId" description:"素材ID"`
SourceTable string `orm:"source_table" json:"sourceTable" description:"来源表"`
SourceID int64 `orm:"source_id" json:"sourceId" description:"原表主键ID"`
AccountID int64 `orm:"account_id" json:"accountId" description:"账户ID"`
TaskID string `orm:"task_id" json:"taskId" description:"易盾任务ID"`
RequestParams string `orm:"request_params" json:"requestParams" description:"请求入参"`
ResponseResult string `orm:"response_result" json:"responseResult" description:"响应出参"`
VerifyStatus string `orm:"verify_status" json:"verifyStatus" description:"校验状态"`
Suggestion int `orm:"suggestion" json:"suggestion" description:"处置建议"`
Label int `orm:"label" json:"label" description:"垃圾类型"`
ResultType int `orm:"result_type" json:"resultType" description:"结果类型"`
ErrorMsg string `orm:"error_msg" json:"errorMsg" description:"错误信息"`
CheckTime int64 `orm:"check_time" json:"checkTime" description:"审核时间戳"`
DurationMs int64 `orm:"duration_ms" json:"durationMs" description:"处理耗时(毫秒)"`
RiskDescription string `orm:"risk_description" json:"riskDescription" description:"风险描述(易盾返回)"`
// 扩展字段(用于展示)
PreviewURL string `orm:"-" json:"previewUrl" description:"预览URL"`
}
// MaterialVerifyLogCol 日志表字段定义
type MaterialVerifyLogCol struct {
beans.SQLBaseCol
TenantID string
MaterialType string
MaterialID string
SourceTable string
SourceID string
AccountID string
TaskID string
RequestParams string
ResponseResult string
VerifyStatus string
Suggestion string
Label string
ResultType string
ErrorMsg string
CheckTime string
DurationMs string
RiskDescription string
}
// MaterialVerifyLogCols 日志表字段常量
var MaterialVerifyLogCols = MaterialVerifyLogCol{
SQLBaseCol: beans.DefSQLBaseCol,
TenantID: "tenant_id",
MaterialType: "material_type",
MaterialID: "material_id",
SourceTable: "source_table",
SourceID: "source_id",
AccountID: "account_id",
TaskID: "task_id",
RequestParams: "request_params",
ResponseResult: "response_result",
VerifyStatus: "verify_status",
Suggestion: "suggestion",
Label: "label",
ResultType: "result_type",
ErrorMsg: "error_msg",
CheckTime: "check_time",
DurationMs: "duration_ms",
RiskDescription: "risk_description",
}
// 素材类型常量
const (
MaterialTypeImage = "IMAGE"
MaterialTypeVideo = "VIDEO"
)
// 校验状态常量
const (
VerifyStatusPending = "PENDING" // 待校验
VerifyStatusReview = "REVIEW" // 嫌疑,需人工复核
VerifyStatusVerified = "VERIFIED" // 校验通过
VerifyStatusRejected = "REJECTED" // 校验不通过
)
@@ -0,0 +1,27 @@
package check
import (
"gitea.redpowerfuture.com/red-future/common/beans"
)
// TencentAccountRelation 腾讯广告账户关系实体(来源:data-engine.tencent_account_relation
type TencentAccountRelation struct {
beans.SQLBaseDO `orm:",inherit"`
// 业务字段
AccountID int64 `orm:"account_id" json:"accountId" description:"账户ID"`
CorporationName string `orm:"corporation_name" json:"corporationName" description:"公司名称"`
}
// TencentAccountRelationCol 账户关系表字段定义
type TencentAccountRelationCol struct {
beans.SQLBaseCol
AccountID string
CorporationName string
}
// TencentAccountRelationCols 账户关系表字段常量
var TencentAccountRelationCols = TencentAccountRelationCol{
SQLBaseCol: beans.DefSQLBaseCol,
AccountID: "account_id",
CorporationName: "corporation_name",
}
@@ -0,0 +1,62 @@
package check
import (
"gitea.redpowerfuture.com/red-future/common/beans"
)
// TencentContentCheckLog 送检日志实体(来源:data-engine.tencent_content_check_log
type TencentContentCheckLog struct {
beans.SQLBaseDO `orm:",inherit"`
// 来源标识
SourceTable string `orm:"source_table" json:"sourceTable" description:"来源表标识:tencent_image/tencent_video"`
SourceID int64 `orm:"source_id" json:"sourceId" description:"原数据ID(关联业务表数据)"`
// 送检信息
RequestURL string `orm:"request_url" json:"requestUrl" description:"送检请求路径(接口地址)"`
RequestParam string `orm:"request_param" json:"requestParam" description:"送检入参(完整请求参数,JSON格式)"`
ResponseData string `orm:"response_data" json:"responseData" description:"送检出参(完整接口返回结果,JSON格式)"`
Status string `orm:"status" json:"status" description:"送检状态:pending-待送检, submitting-送检中, success-送检成功, failed-送检失败"`
CheckTime int64 `orm:"check_time" json:"checkTime" description:"送检时间(时间戳,毫秒)"`
FailReason string `orm:"fail_reason" json:"failReason" description:"失败原因(可选,记录接口报错信息)"`
TaskID string `orm:"task_id" json:"taskId" description:"易盾返回的任务ID"`
// 检测结果
Suggestion int `orm:"suggestion" json:"suggestion" description:"检测结果建议:0-通过,1-嫌疑,2-不通过"`
Label int `orm:"label" json:"label" description:"检测标签"`
ResultType int `orm:"result_type" json:"resultType" description:"结果类型:1-机器结果,2-人审结果"`
Duration int64 `orm:"duration" json:"duration" description:"送检耗时(毫秒)"`
}
// TencentContentCheckLogCol 送检日志表字段定义
type TencentContentCheckLogCol struct {
beans.SQLBaseCol
SourceTable string
SourceID string
RequestURL string
RequestParam string
ResponseData string
Status string
CheckTime string
FailReason string
TaskID string
Suggestion string
Label string
ResultType string
Duration string
}
// TencentContentCheckLogCols 送检日志表字段常量
var TencentContentCheckLogCols = TencentContentCheckLogCol{
SQLBaseCol: beans.DefSQLBaseCol,
SourceTable: "source_table",
SourceID: "source_id",
RequestURL: "request_url",
RequestParam: "request_param",
ResponseData: "response_data",
Status: "status",
CheckTime: "check_time",
FailReason: "fail_reason",
TaskID: "task_id",
Suggestion: "suggestion",
Label: "label",
ResultType: "result_type",
Duration: "duration",
}
+120
View File
@@ -0,0 +1,120 @@
package check
import (
"gitea.redpowerfuture.com/red-future/common/beans"
)
// TencentImage 图片素材实体(来源:data-engine.tencent_image
type TencentImage struct {
beans.SQLBaseDO `orm:",inherit"`
// 业务字段 - 匹配现有表结构
ImageID string `orm:"image_id" json:"imageId" description:"图片ID"`
AccountID int64 `orm:"account_id" json:"accountId" description:"账户ID"`
Width int `orm:"width" json:"width" description:"宽度"`
Height int `orm:"height" json:"height" description:"高度"`
FileSize int64 `orm:"file_size" json:"fileSize" description:"文件大小"`
Type string `orm:"type" json:"type" description:"图片类型"`
Signature string `orm:"signature" json:"signature" description:"签名"`
Description string `orm:"description" json:"description" description:"描述"`
SourceSignature string `orm:"source_signature" json:"sourceSignature" description:"源签名"`
PreviewURL string `orm:"preview_url" json:"previewUrl" description:"预览URL"`
ThumbPreviewURL string `orm:"thumb_preview_url" json:"thumbPreviewUrl" description:"缩略图URL"`
SourceType string `orm:"source_type" json:"sourceType" description:"来源类型"`
ImageUsage string `orm:"image_usage" json:"imageUsage" description:"图片用途"`
CreatedTime int64 `orm:"created_time" json:"createdTime" description:"创建时间戳"`
LastModifiedTime int64 `orm:"last_modified_time" json:"lastModifiedTime" description:"最后修改时间戳"`
ProductCatalogID int64 `orm:"product_catalog_id" json:"productCatalogId" description:"产品目录ID"`
ProductOuterID string `orm:"product_outer_id" json:"productOuterId" description:"产品外部ID"`
SourceReferenceID string `orm:"source_reference_id" json:"sourceReferenceId" description:"源引用ID"`
OwnerAccountID string `orm:"owner_account_id" json:"ownerAccountId" description:"所有者账户ID"`
VerifyStatus string `orm:"verify_status" json:"verifyStatus" description:"审核状态"`
SampleAspectRatio string `orm:"sample_aspect_ratio" json:"sampleAspectRatio" description:"示例宽高比"`
SourceMaterialID string `orm:"source_material_id" json:"sourceMaterialId" description:"源素材ID"`
NewSourceType string `orm:"new_source_type" json:"newSourceType" description:"新来源类型"`
FirstPublicationStatus string `orm:"first_publication_status" json:"firstPublicationStatus" description:"首次发布状态"`
QualityStatus string `orm:"quality_status" json:"qualityStatus" description:"质量状态"`
SimilarityStatus string `orm:"similarity_status" json:"similarityStatus" description:"相似度状态"`
UserAigcStatus string `orm:"user_aigc_status" json:"userAigcStatus" description:"用户AIGC状态"`
SystemAigcStatus string `orm:"system_aigc_status" json:"systemAigcStatus" description:"系统AIGC状态"`
AigcSource string `orm:"aigc_source" json:"aigcSource" description:"AIGC来源"`
AigcFlag string `orm:"aigc_flag" json:"aigcFlag" description:"AIGC标志"`
MuseAigcVersion int `orm:"muse_aigc_version" json:"museAigcVersion" description:"Muse AIGC版本"`
AigcType int `orm:"aigc_type" json:"aigcType" description:"AIGC类型"`
// 内容检测相关字段(扩展字段,用于存储检测结果)
// 注意:如果表中没有这些字段,需要通过 content_check_log 表来存储检测结果
}
// TencentImageCol 图片素材表字段定义
type TencentImageCol struct {
beans.SQLBaseCol
ImageID string
AccountID string
Width string
Height string
FileSize string
Type string
Signature string
Description string
SourceSignature string
PreviewURL string
ThumbPreviewURL string
SourceType string
ImageUsage string
CreatedTime string
LastModifiedTime string
ProductCatalogID string
ProductOuterID string
SourceReferenceID string
OwnerAccountID string
VerifyStatus string
SampleAspectRatio string
SourceMaterialID string
NewSourceType string
FirstPublicationStatus string
QualityStatus string
SimilarityStatus string
UserAigcStatus string
SystemAigcStatus string
AigcSource string
AigcFlag string
MuseAigcVersion string
AigcType string
}
// TencentImageCols 图片素材表字段常量
var TencentImageCols = TencentImageCol{
SQLBaseCol: beans.DefSQLBaseCol,
ImageID: "image_id",
AccountID: "account_id",
Width: "width",
Height: "height",
FileSize: "file_size",
Type: "type",
Signature: "signature",
Description: "description",
SourceSignature: "source_signature",
PreviewURL: "preview_url",
ThumbPreviewURL: "thumb_preview_url",
SourceType: "source_type",
ImageUsage: "image_usage",
CreatedTime: "created_time",
LastModifiedTime: "last_modified_time",
ProductCatalogID: "product_catalog_id",
ProductOuterID: "product_outer_id",
SourceReferenceID: "source_reference_id",
OwnerAccountID: "owner_account_id",
VerifyStatus: "verify_status",
SampleAspectRatio: "sample_aspect_ratio",
SourceMaterialID: "source_material_id",
NewSourceType: "new_source_type",
FirstPublicationStatus: "first_publication_status",
QualityStatus: "quality_status",
SimilarityStatus: "similarity_status",
UserAigcStatus: "user_aigc_status",
SystemAigcStatus: "system_aigc_status",
AigcSource: "aigc_source",
AigcFlag: "aigc_flag",
MuseAigcVersion: "muse_aigc_version",
AigcType: "aigc_type",
}
+159
View File
@@ -0,0 +1,159 @@
package check
import (
"gitea.redpowerfuture.com/red-future/common/beans"
)
// TencentVideo 视频素材实体(来源:data-engine.tencent_video
type TencentVideo struct {
beans.SQLBaseDO `orm:",inherit"`
// 业务字段 - 匹配现有表结构
VideoID string `orm:"video_id" json:"videoId" description:"视频ID"`
AccountID int64 `orm:"account_id" json:"accountId" description:"账户ID"`
Width int `orm:"width" json:"width" description:"宽度"`
Height int `orm:"height" json:"height" description:"高度"`
VideoFrames int `orm:"video_frames" json:"videoFrames" description:"视频帧数"`
VideoFps int `orm:"video_fps" json:"videoFps" description:"帧率"`
VideoCodec string `orm:"video_codec" json:"videoCodec" description:"视频编码"`
VideoBitRate int64 `orm:"video_bit_rate" json:"videoBitRate" description:"视频码率"`
AudioCodec string `orm:"audio_codec" json:"audioCodec" description:"音频编码"`
AudioBitRate int64 `orm:"audio_bit_rate" json:"audioBitRate" description:"音频码率"`
FileSize int64 `orm:"file_size" json:"fileSize" description:"文件大小"`
Type string `orm:"type" json:"type" description:"媒体类型"`
Signature string `orm:"signature" json:"signature" description:"签名"`
SystemStatus string `orm:"system_status" json:"systemStatus" description:"系统状态"`
Description string `orm:"description" json:"description" description:"描述"`
PreviewURL string `orm:"preview_url" json:"previewUrl" description:"预览URL"`
KeyFrameImageURL string `orm:"key_frame_image_url" json:"keyFrameImageUrl" description:"关键帧图片URL"`
CreatedTime int64 `orm:"created_time" json:"createdTime" description:"创建时间戳"`
LastModifiedTime int64 `orm:"last_modified_time" json:"lastModifiedTime" description:"最后修改时间戳"`
VideoProfileName string `orm:"video_profile_name" json:"videoProfileName" description:"视频配置名称"`
AudioSampleRate int `orm:"audio_sample_rate" json:"audioSampleRate" description:"音频采样率"`
MaxKeyframeInterval int `orm:"max_keyframe_interval" json:"maxKeyframeInterval" description:"最大关键帧间隔"`
MinKeyframeInterval int `orm:"min_keyframe_interval" json:"minKeyframeInterval" description:"最小关键帧间隔"`
SampleAspectRatio string `orm:"sample_aspect_ratio" json:"sampleAspectRatio" description:"示例宽高比"`
AudioProfileName string `orm:"audio_profile_name" json:"audioProfileName" description:"音频配置名称"`
ScanType string `orm:"scan_type" json:"scanType" description:"扫描类型"`
ImageDurationMs int64 `orm:"image_duration_millisecond" json:"imageDurationMs" description:"图片时长(毫秒)"`
AudioDurationMs int64 `orm:"audio_duration_millisecond" json:"audioDurationMs" description:"音频时长(毫秒)"`
SourceType string `orm:"source_type" json:"sourceType" description:"来源类型"`
ProductCatalogID string `orm:"product_catalog_id" json:"productCatalogId" description:"产品目录ID"`
ProductOuterID string `orm:"product_outer_id" json:"productOuterId" description:"产品外部ID"`
SourceReferenceID string `orm:"source_reference_id" json:"sourceReferenceId" description:"源引用ID"`
OwnerAccountID string `orm:"owner_account_id" json:"ownerAccountId" description:"所有者账户ID"`
VerifyStatus string `orm:"verify_status" json:"verifyStatus" description:"审核状态"`
SourceMaterialID string `orm:"source_material_id" json:"sourceMaterialId" description:"源素材ID"`
NewSourceType string `orm:"new_source_type" json:"newSourceType" description:"新来源类型"`
AigcType int `orm:"aigc_type" json:"aigcType" description:"AIGC类型"`
FirstPublicationStatus string `orm:"first_publication_status" json:"firstPublicationStatus" description:"首次发布状态"`
QualityStatus string `orm:"quality_status" json:"qualityStatus" description:"质量状态"`
CoverID string `orm:"cover_id" json:"coverId" description:"封面ID"`
SimilarityStatus string `orm:"similarity_status" json:"similarityStatus" description:"相似度状态"`
UserAigcStatus string `orm:"user_aigc_status" json:"userAigcStatus" description:"用户AIGC状态"`
SystemAigcStatus string `orm:"system_aigc_status" json:"systemAigcStatus" description:"系统AIGC状态"`
AigcSource string `orm:"aigc_source" json:"aigcSource" description:"AIGC来源"`
AigcFlag string `orm:"aigc_flag" json:"aigcFlag" description:"AIGC标志"`
MuseAigcVersion int `orm:"muse_aigc_version" json:"museAigcVersion" description:"Muse AIGC版本"`
}
// TencentVideoCol 视频素材表字段定义
type TencentVideoCol struct {
beans.SQLBaseCol
VideoID string
AccountID string
Width string
Height string
VideoFrames string
VideoFps string
VideoCodec string
VideoBitRate string
AudioCodec string
AudioBitRate string
FileSize string
Type string
Signature string
SystemStatus string
Description string
PreviewURL string
KeyFrameImageURL string
CreatedTime string
LastModifiedTime string
VideoProfileName string
AudioSampleRate string
MaxKeyframeInterval string
MinKeyframeInterval string
SampleAspectRatio string
AudioProfileName string
ScanType string
ImageDurationMs string
AudioDurationMs string
SourceType string
ProductCatalogID string
ProductOuterID string
SourceReferenceID string
OwnerAccountID string
VerifyStatus string
SourceMaterialID string
NewSourceType string
AigcType string
FirstPublicationStatus string
QualityStatus string
CoverID string
SimilarityStatus string
UserAigcStatus string
SystemAigcStatus string
AigcSource string
AigcFlag string
MuseAigcVersion string
}
// TencentVideoCols 视频素材表字段常量
var TencentVideoCols = TencentVideoCol{
SQLBaseCol: beans.DefSQLBaseCol,
VideoID: "video_id",
AccountID: "account_id",
Width: "width",
Height: "height",
VideoFrames: "video_frames",
VideoFps: "video_fps",
VideoCodec: "video_codec",
VideoBitRate: "video_bit_rate",
AudioCodec: "audio_codec",
AudioBitRate: "audio_bit_rate",
FileSize: "file_size",
Type: "type",
Signature: "signature",
SystemStatus: "system_status",
Description: "description",
PreviewURL: "preview_url",
KeyFrameImageURL: "key_frame_image_url",
CreatedTime: "created_time",
LastModifiedTime: "last_modified_time",
VideoProfileName: "video_profile_name",
AudioSampleRate: "audio_sample_rate",
MaxKeyframeInterval: "max_keyframe_interval",
MinKeyframeInterval: "min_keyframe_interval",
SampleAspectRatio: "sample_aspect_ratio",
AudioProfileName: "audio_profile_name",
ScanType: "scan_type",
ImageDurationMs: "image_duration_millisecond",
AudioDurationMs: "audio_duration_millisecond",
SourceType: "source_type",
ProductCatalogID: "product_catalog_id",
ProductOuterID: "product_outer_id",
SourceReferenceID: "source_reference_id",
OwnerAccountID: "owner_account_id",
VerifyStatus: "verify_status",
SourceMaterialID: "source_material_id",
NewSourceType: "new_source_type",
AigcType: "aigc_type",
FirstPublicationStatus: "first_publication_status",
QualityStatus: "quality_status",
CoverID: "cover_id",
SimilarityStatus: "similarity_status",
UserAigcStatus: "user_aigc_status",
SystemAigcStatus: "system_aigc_status",
AigcSource: "aigc_source",
AigcFlag: "aigc_flag",
MuseAigcVersion: "muse_aigc_version",
}
-251
View File
@@ -1,251 +0,0 @@
package entity
import (
"gitea.com/red-future/common/beans"
)
const CidRequestCollection = "cid_request"
// CidRequest CID请求实体(合并后的统一版本)
type CidRequest struct {
beans.MongoBaseDO `bson:",inline" json:",inline"` // 嵌入基础字段:Id, Creator, CreatedAt, Updater, UpdatedAt, IsDeleted
// 请求基础信息
RequestID string `bson:"requestId" json:"requestId"` // 请求唯一ID
SessionID string `bson:"sessionId" json:"sessionId"` // 会话ID
UserID string `bson:"userId" json:"userId"` // 用户ID
// 网络信息
IPAddress string `bson:"ipAddress" json:"ipAddress"` // IP地址
UserAgent string `bson:"userAgent" json:"userAgent"` // 用户代理
Referer string `bson:"referer" json:"referer"` // 来源页面
// 广告位信息(使用内联结构)
PositionCode string `bson:"positionCode" json:"positionCode"` // 广告位编码
PositionSize string `bson:"positionSize" json:"positionSize"` // 广告位尺寸
PositionFormat string `bson:"positionFormat" json:"positionFormat"` // 广告位格式
PositionType string `bson:"positionType" json:"positionType"` // 广告位类型
// 页面信息
PageURL string `bson:"pageUrl" json:"pageUrl"` // 页面URL
PageTitle string `bson:"pageTitle" json:"pageTitle"` // 页面标题
PageCategory string `bson:"pageCategory" json:"pageCategory"` // 页面分类
PageKeywords []string `bson:"pageKeywords" json:"pageKeywords"` // 页面关键词
PageTags map[string]string `bson:"pageTags" json:"pageTags"` // 页面标签
// 用户上下文信息(使用统一版本)
UserContext *UnifiedUserContext `bson:"userContext" json:"userContext"` // 用户上下文
DeviceInfo *DeviceInfo `bson:"deviceInfo" json:"deviceInfo"` // 设备信息
LocationInfo *UnifiedLocationInfo `bson:"locationInfo" json:"locationInfo"` // 位置信息
TemporalInfo *UnifiedTemporalInfo `bson:"temporalInfo" json:"temporalInfo"` // 时间信息
// 请求参数(使用合并版本)
RequestParams *RequestParams `bson:"requestParams" json:"requestParams"` // 请求参数
// 定向规则(使用统一的定向结构)
TargetingRules *UnifiedTargeting `bson:"targetingRules" json:"targetingRules"` // 定向规则
// 策略配置
StrategyConfig *StrategyConfig `bson:"strategyConfig" json:"strategyConfig"` // 策略配置
// 响应信息
Response *CidResponse `bson:"response" json:"response"` // 响应结果
ProcessingTime int64 `bson:"processingTime" json:"processingTime"` // 处理时间(毫秒)
ResponseTime int64 `bson:"responseTime" json:"responseTime"` // 响应时间(毫秒)
// 状态信息
Status string `bson:"status" json:"status"` // 请求状态:pending、processing、completed、failed、timeout
ErrorMessage string `bson:"errorMessage" json:"errorMessage"` // 错误信息
ErrorCode string `bson:"errorCode" json:"errorCode"` // 错误代码
// 广告源信息
RequestedAdSources []string `bson:"requestedAdSources" json:"requestedAdSources"` // 请求的广告源列表
RespondedAdSources []string `bson:"respondedAdSources" json:"respondedAdSources"` // 响应的广告源列表
AdSourceResponses map[string]*AdSourceResponse `bson:"adSourceResponses" json:"adSourceResponses"` // 各广告源响应
// 统计信息
TotalAdsReturned int `bson:"totalAdsReturned" json:"totalAdsReturned"` // 返回的广告总数
ValidAdsReturned int `bson:"validAdsReturned" json:"validAdsReturned"` // 有效广告数
FilteredAds int `bson:"filteredAds" json:"filteredAds"` // 过滤的广告数
DuplicateAds int `bson:"duplicateAds" json:"duplicateAds"` // 重复广告数
// 系统信息
ServerInstance string `bson:"serverInstance" json:"serverInstance"` // 服务实例ID
Region string `bson:"region" json:"region"` // 服务区域
Version string `bson:"version" json:"version"` // 系统版本
}
// GetCollectionName 获取集合名称
func (c *CidRequest) GetCollectionName() string {
return CidRequestCollection
}
// UnifiedUserContext 统一的用户上下文
type UnifiedUserContext struct {
UserID string `bson:"userId" json:"userId"` // 用户ID
SessionID string `bson:"sessionId" json:"sessionId"` // 会话ID
CookieID string `bson:"cookieId" json:"cookieId"` // Cookie ID
IP string `bson:"ip" json:"ip"` // IP地址
UserAgent string `bson:"userAgent" json:"userAgent"` // 用户代理
Language string `bson:"language" json:"language"` // 语言
Timezone string `bson:"timezone" json:"timezone"` // 时区
CustomData map[string]interface{} `bson:"customData" json:"customData"` // 自定义数据
}
// UnifiedLocationInfo 统一的位置信息
type UnifiedLocationInfo struct {
Country string `bson:"country" json:"country"` // 国家
Region string `bson:"region" json:"region"` // 地区/省份
City string `bson:"city" json:"city"` // 城市
PostalCode string `bson:"postalCode" json:"postalCode"` // 邮政编码
Latitude float64 `bson:"latitude" json:"latitude"` // 纬度
Longitude float64 `bson:"longitude" json:"longitude"` // 经度
Timezone string `bson:"timezone" json:"timezone"` // 时区
Metro string `bson:"metro" json:"metro"` // 都市区
Area string `bson:"area" json:"area"` // 区域
Network string `bson:"network" json:"network"` // 网络运营商
ConnectionType string `bson:"connectionType" json:"connectionType"` // 连接类型
ISP string `bson:"isp" json:"isp"` // 互联网服务提供商
}
// UnifiedTemporalInfo 统一的时间信息
type UnifiedTemporalInfo struct {
Timestamp int64 `bson:"timestamp" json:"timestamp"` // 时间戳(秒)
Milliseconds int64 `bson:"milliseconds" json:"milliseconds"` // 毫秒数
Timezone string `bson:"timezone" json:"timezone"` // 时区
DayOfWeek int `bson:"dayOfWeek" json:"dayOfWeek"` // 星期几(0-6)
HourOfDay int `bson:"hourOfDay" json:"hourOfDay"` // 小时(0-23)
DayOfMonth int `bson:"dayOfMonth" json:"dayOfMonth"` // 月份中的天数
Month int `bson:"month" json:"month"` // 月份(1-12)
Year int `bson:"year" json:"year"` // 年份
IsWeekend bool `bson:"isWeekend" json:"isWeekend"` // 是否周末
IsBusinessHours bool `bson:"isBusinessHours" json:"isBusinessHours"` // 是否营业时间
Season string `bson:"season" json:"season"` // 季节
Holiday string `bson:"holiday" json:"holiday"` // 节假日
}
// DeviceInfo 设备信息
type DeviceInfo struct {
Type string `bson:"type" json:"type"` // 设备类型:desktop、mobile、tablet
Brand string `bson:"brand" json:"brand"` // 设备品牌
Model string `bson:"model" json:"model"` // 设备型号
OS string `bson:"os" json:"os"` // 操作系统
OSVersion string `bson:"osVersion" json:"osVersion"` // 操作系统版本
Browser string `bson:"browser" json:"browser"` // 浏览器
BrowserVersion string `bson:"browserVersion" json:"browserVersion"` // 浏览器版本
ScreenWidth int `bson:"screenWidth" json:"screenWidth"` // 屏幕宽度
ScreenHeight int `bson:"screenHeight" json:"screenHeight"` // 屏幕高度
ViewportWidth int `bson:"viewportWidth" json:"viewportWidth"` // 视口宽度
ViewportHeight int `bson:"viewportHeight" json:"viewportHeight"` // 视口高度
DPI int `bson:"dpi" json:"dpi"` // 设备DPI
IsJavaScript bool `bson:"isJavaScript" json:"isJavaScript"` // 是否支持JavaScript
IsCookie bool `bson:"isCookie" json:"isCookie"` // 是否支持Cookie
IsFlash bool `bson:"isFlash" json:"isFlash"` // 是否支持Flash
IsHTTPS bool `bson:"isHTTPS" json:"isHTTPS"` // 是否HTTPS连接
}
// RequestParams 请求参数(合并版本)
type RequestParams struct {
AdCount int `bson:"adCount" json:"adCount"` // 请求的广告数量
AdTypes []string `bson:"adTypes" json:"adTypes"` // 广告类型
AdSizes []string `bson:"adSizes" json:"adSizes"` // 广告尺寸
ExcludedAdSources []string `bson:"excludedAdSources" json:"excludedAdSources"` // 排除的广告源
RequiredAdSources []string `bson:"requiredAdSources" json:"requiredAdSources"` // 必需的广告源
MinBidAmount int64 `bson:"minBidAmount" json:"minBidAmount"` // 最小出价(分)
MaxBidAmount int64 `bson:"maxBidAmount" json:"maxBidAmount"` // 最大出价(分)
AllowDuplicates bool `bson:"allowDuplicates" json:"allowDuplicates"` // 是否允许重复广告
FloorPrice int64 `bson:"floorPrice" json:"floorPrice"` // 底价(分)
CeilingPrice int64 `bson:"ceilingPrice" json:"ceilingPrice"` // 封顶价(分)
CustomParams map[string]interface{} `bson:"customParams" json:"customParams"` // 自定义参数
}
// StrategyConfig 策略配置(合并版本)
type StrategyConfig struct {
StrategyType string `bson:"strategyType" json:"strategyType"` // 策略类型
Priority int `bson:"priority" json:"priority"` // 优先级
Weight float64 `bson:"weight" json:"weight"` // 权重
MinAds int `bson:"minAds" json:"minAds"` // 最小广告数
MaxAds int `bson:"maxAds" json:"maxAds"` // 最大广告数
AllowDuplicates bool `bson:"allowDuplicates" json:"allowDuplicates"` // 是否允许重复
Timeout int64 `bson:"timeout" json:"timeout"` // 超时时间(毫秒)
RetryCount int `bson:"retryCount" json:"retryCount"` // 重试次数
CustomSettings map[string]interface{} `bson:"customSettings" json:"customSettings"` // 自定义设置
}
// CidResponse CID响应(合并版本)
type CidResponse struct {
Ads []Ad `bson:"ads" json:"ads"` // 广告列表
TrackingInfo *TrackingInfo `bson:"trackingInfo" json:"trackingInfo"` // 跟踪信息
Metadata *ResponseMetadata `bson:"metadata" json:"metadata"` // 响应元数据
}
// Ad 广告结构(合并版本)
type Ad struct {
ID string `bson:"id" json:"id"` // 广告ID
AdSource string `bson:"adSource" json:"adSource"` // 广告源
Advertiser string `bson:"advertiser" json:"advertiser"` // 广告主
Title string `bson:"title" json:"title"` // 广告标题
Description string `bson:"description" json:"description"` // 广告描述
CreativeURL string `bson:"creativeUrl" json:"creativeUrl"` // 创意URL
LandingURL string `bson:"landingUrl" json:"landingUrl"` // 落地页URL
DisplayURL string `bson:"displayUrl" json:"displayUrl"` // 显示URL
AdType string `bson:"adType" json:"adType"` // 广告类型
Format string `bson:"format" json:"format"` // 广告格式
Width int `bson:"width" json:"width"` // 宽度
Height int `bson:"height" json:"height"` // 高度
MimeType string `bson:"mimeType" json:"mimeType"` // MIME类型
BidAmount int64 `bson:"bidAmount" json:"bidAmount"` // 出价(分)
Revenue int64 `bson:"revenue" json:"revenue"` // 预估收入(分)
CTR float64 `bson:"ctr" json:"ctr"` // 点击率
CVR float64 `bson:"cvr" json:"cvr"` // 转化率
Targeting map[string]interface{} `bson:"targeting" json:"targeting"` // 定向条件
Restrictions map[string]interface{} `bson:"restrictions" json:"restrictions"` // 限制条件
TrackingPixels []string `bson:"trackingPixels" json:"trackingPixels"` // 跟踪像素
CustomData map[string]interface{} `bson:"customData" json:"customData"` // 自定义数据
ExpiresAt int64 `bson:"expiresAt" json:"expiresAt"` // 过期时间
Priority int `bson:"priority" json:"priority"` // 优先级
Score float64 `bson:"score" json:"score"` // 评分
}
// TrackingInfo 跟踪信息(合并版本)
type TrackingInfo struct {
ImpressionURLs []string `bson:"impressionUrls" json:"impressionUrls"` // 展示跟踪URL
ClickURLs []string `bson:"clickUrls" json:"clickUrls"` // 点击跟踪URL
ConversionURLs []string `bson:"conversionUrls" json:"conversionUrls"` // 转化跟踪URL
ViewThroughURLs []string `bson:"viewThroughUrls" json:"viewThroughUrls"` // 查看跟踪URL
EventURLs map[string][]string `bson:"eventUrls" json:"eventUrls"` // 事件跟踪URL
BeaconURLs []string `bson:"beaconUrls" json:"beaconUrls"` // 信标URL
}
// ResponseMetadata 响应元数据(合并版本)
type ResponseMetadata struct {
TotalAvailableAds int `bson:"totalAvailableAds" json:"totalAvailableAds"` // 总可用广告数
SelectedAds int `bson:"selectedAds" json:"selectedAds"` // 选择的广告数
FilteredAds int `bson:"filteredAds" json:"filteredAds"` // 过滤的广告数
DuplicateAds int `bson:"duplicateAds" json:"duplicateAds"` // 重复的广告数
AverageBidAmount int64 `bson:"averageBidAmount" json:"averageBidAmount"` // 平均出价
HighestBidAmount int64 `bson:"highestBidAmount" json:"highestBidAmount"` // 最高出价
LowestBidAmount int64 `bson:"lowestBidAmount" json:"lowestBidAmount"` // 最低出价
AverageCTR float64 `bson:"averageCTR" json:"averageCTR"` // 平均点击率
AverageCVR float64 `bson:"averageCVR" json:"averageCVR"` // 平均转化率
ResponseTime int64 `bson:"responseTime" json:"responseTime"` // 响应时间(毫秒)
CacheHit bool `bson:"cacheHit" json:"cacheHit"` // 是否命中缓存
StrategyUsed string `bson:"strategyUsed" json:"strategyUsed"` // 使用的策略
AdSourcesUsed []string `bson:"adSourcesUsed" json:"adSourcesUsed"` // 使用的广告源
}
// AdSourceResponse 广告源响应(合并版本)
type AdSourceResponse struct {
AdSource string `bson:"adSource" json:"adSource"` // 广告源名称
Status string `bson:"status" json:"status"` // 响应状态:success、timeout、error
ResponseTime int64 `bson:"responseTime" json:"responseTime"` // 响应时间(毫秒)
AdsReturned int `bson:"adsReturned" json:"adsReturned"` // 返回的广告数
AdsAccepted int `bson:"adsAccepted" json:"adsAccepted"` // 接受的广告数
AdsFiltered int `bson:"adsFiltered" json:"adsFiltered"` // 过滤的广告数
ErrorMessage string `bson:"errorMessage" json:"errorMessage"` // 错误信息
ErrorCode string `bson:"errorCode" json:"errorCode"` // 错误代码
RetryCount int `bson:"retryCount" json:"retryCount"` // 重试次数
CacheHit bool `bson:"cacheHit" json:"cacheHit"` // 是否命中缓存
TotalRevenue int64 `bson:"totalRevenue" json:"totalRevenue"` // 总收入(分)
AverageBidAmount int64 `bson:"averageBidAmount" json:"averageBidAmount"` // 平均出价(分)
}
+42
View File
@@ -0,0 +1,42 @@
package delivery
import (
"gitea.redpowerfuture.com/red-future/common/beans"
)
// Ad 广告实体(计划树第三层)
type Ad struct {
beans.SQLBaseDO `orm:",inherit"`
// 业务字段
AdGroupID int64 `orm:"ad_group_id" json:"adGroupId" description:"所属单元ID"`
Name string `orm:"name" json:"name" description:"广告名称"`
PlatformAdID string `orm:"platform_ad_id" json:"platformAdId" description:"平台侧广告ID"`
Status string `orm:"status" json:"status" description:"本地状态 draft/active/paused/ended/rejected"`
PlatformStatus string `orm:"platform_status" json:"platformStatus" description:"平台侧状态快照"`
SyncStatus string `orm:"sync_status" json:"syncStatus" description:"同步状态 none/pending/synced/failed"`
Extra string `orm:"extra" json:"extra" description:"平台特有扩展(JSON)"`
}
// AdCol 广告表字段定义
type AdCol struct {
beans.SQLBaseCol
AdGroupID string
Name string
PlatformAdID string
Status string
PlatformStatus string
SyncStatus string
Extra string
}
// AdCols 广告表字段常量
var AdCols = AdCol{
SQLBaseCol: beans.DefSQLBaseCol,
AdGroupID: "ad_group_id",
Name: "name",
PlatformAdID: "platform_ad_id",
Status: "status",
PlatformStatus: "platform_status",
SyncStatus: "sync_status",
Extra: "extra",
}
+51
View File
@@ -0,0 +1,51 @@
package delivery
import (
"gitea.redpowerfuture.com/red-future/common/beans"
)
// AdGroup 广告单元实体(计划树第二层,定向+出价)
type AdGroup struct {
beans.SQLBaseDO `orm:",inherit"`
// 业务字段
CampaignID int64 `orm:"campaign_id" json:"campaignId" description:"所属计划ID"`
Name string `orm:"name" json:"name" description:"单元名称"`
PlatformAdGroupID string `orm:"platform_ad_group_id" json:"platformAdGroupId" description:"平台侧单元ID"`
Targeting string `orm:"targeting" json:"targeting" description:"定向(JSON) 地域/人群/时段"`
BidType string `orm:"bid_type" json:"bidType" description:"出价方式 cpm/ocpm/cpc/ocpc"`
BidAmount int64 `orm:"bid_amount" json:"bidAmount" description:"出价(分,cpm按千次展示)"`
Status string `orm:"status" json:"status" description:"本地状态 draft/active/paused/ended/rejected"`
PlatformStatus string `orm:"platform_status" json:"platformStatus" description:"平台侧状态快照"`
SyncStatus string `orm:"sync_status" json:"syncStatus" description:"同步状态 none/pending/synced/failed"`
Extra string `orm:"extra" json:"extra" description:"平台特有扩展(JSON)"`
}
// AdGroupCol 广告单元表字段定义
type AdGroupCol struct {
beans.SQLBaseCol
CampaignID string
Name string
PlatformAdGroupID string
Targeting string
BidType string
BidAmount string
Status string
PlatformStatus string
SyncStatus string
Extra string
}
// AdGroupCols 广告单元表字段常量
var AdGroupCols = AdGroupCol{
SQLBaseCol: beans.DefSQLBaseCol,
CampaignID: "campaign_id",
Name: "name",
PlatformAdGroupID: "platform_ad_group_id",
Targeting: "targeting",
BidType: "bid_type",
BidAmount: "bid_amount",
Status: "status",
PlatformStatus: "platform_status",
SyncStatus: "sync_status",
Extra: "extra",
}
+68
View File
@@ -0,0 +1,68 @@
package delivery
import (
"github.com/gogf/gf/v2/os/gtime"
"gitea.redpowerfuture.com/red-future/common/beans"
)
// Campaign 广告计划实体(计划树第一层,外投/自有DSP共用,预算在计划层)
type Campaign struct {
beans.SQLBaseDO `orm:",inherit"`
// 业务字段
AdAccountID int64 `orm:"ad_account_id" json:"adAccountId" description:"本地广告账户ID"`
Name string `orm:"name" json:"name" description:"计划名称"`
Channel string `orm:"channel" json:"channel" description:"投放通道 external外投/self_dsp自有"`
Platform string `orm:"platform" json:"platform" description:"广告平台 douyin/xiaohongshu/kuaishou"`
PlatformCampaignID string `orm:"platform_campaign_id" json:"platformCampaignId" description:"平台侧计划ID"`
Status string `orm:"status" json:"status" description:"本地状态 draft/active/paused/ended/rejected"`
PlatformStatus string `orm:"platform_status" json:"platformStatus" description:"平台侧状态快照"`
SyncStatus string `orm:"sync_status" json:"syncStatus" description:"同步状态 none/pending/synced/failed"`
BudgetMode string `orm:"budget_mode" json:"budgetMode" description:"预算方式 free不限/daily日预算"`
DailyBudget *int64 `orm:"daily_budget" json:"dailyBudget" description:"日预算(分)budget_mode=daily时必填,否则NULL"`
TotalBudget *int64 `orm:"total_budget" json:"totalBudget" description:"总预算(分)NULL=不限"`
OptimizeGoal string `orm:"optimize_goal" json:"optimizeGoal" description:"优化目标 conversion/click/exposure"`
StartTime *gtime.Time `orm:"start_time" json:"startTime" description:"投放开始时间"`
EndTime *gtime.Time `orm:"end_time" json:"endTime" description:"投放结束时间"`
Extra string `orm:"extra" json:"extra" description:"平台特有扩展(JSON)"`
}
// CampaignCol 广告计划表字段定义
type CampaignCol struct {
beans.SQLBaseCol
AdAccountID string
Name string
Channel string
Platform string
PlatformCampaignID string
Status string
PlatformStatus string
SyncStatus string
BudgetMode string
DailyBudget string
TotalBudget string
OptimizeGoal string
StartTime string
EndTime string
Extra string
}
// CampaignCols 广告计划表字段常量
var CampaignCols = CampaignCol{
SQLBaseCol: beans.DefSQLBaseCol,
AdAccountID: "ad_account_id",
Name: "name",
Channel: "channel",
Platform: "platform",
PlatformCampaignID: "platform_campaign_id",
Status: "status",
PlatformStatus: "platform_status",
SyncStatus: "sync_status",
BudgetMode: "budget_mode",
DailyBudget: "daily_budget",
TotalBudget: "total_budget",
OptimizeGoal: "optimize_goal",
StartTime: "start_time",
EndTime: "end_time",
Extra: "extra",
}
+63
View File
@@ -0,0 +1,63 @@
package delivery
import (
"gitea.redpowerfuture.com/red-future/common/beans"
)
// Creative 创意实体(计划树第四层,引用素材库,落地页带click_id模板)
type Creative struct {
beans.SQLBaseDO `orm:",inherit"`
// 业务字段
AdID int64 `orm:"ad_id" json:"adId" description:"所属广告ID(部分平台创意先于广告,可空)"`
CampaignID int64 `orm:"campaign_id" json:"campaignId" description:"所属计划ID(冗余,ad_id为空时链路不断)"`
Name string `orm:"name" json:"name" description:"创意名称"`
MaterialID int64 `orm:"material_id" json:"materialId" description:"素材ID(material.id)"`
Title string `orm:"title" json:"title" description:"标题"`
Description string `orm:"description" json:"description" description:"描述"`
ClickURL string `orm:"click_url" json:"clickUrl" description:"落地页URL(含click_id模板)"`
PlatformCreativeID string `orm:"platform_creative_id" json:"platformCreativeId" description:"平台侧创意ID"`
PlatformMaterialID string `orm:"platform_material_id" json:"platformMaterialId" description:"平台侧素材ID"`
AuditStatus string `orm:"audit_status" json:"auditStatus" description:"审核状态 pending/pass/reject"`
Status string `orm:"status" json:"status" description:"本地状态 draft/active/paused/ended/rejected"`
PlatformStatus string `orm:"platform_status" json:"platformStatus" description:"平台侧状态快照"`
SyncStatus string `orm:"sync_status" json:"syncStatus" description:"同步状态 none/pending/synced/failed"`
Extra string `orm:"extra" json:"extra" description:"平台特有扩展(JSON)"`
}
// CreativeCol 创意表字段定义
type CreativeCol struct {
beans.SQLBaseCol
AdID string
CampaignID string
Name string
MaterialID string
Title string
Description string
ClickURL string
PlatformCreativeID string
PlatformMaterialID string
AuditStatus string
Status string
PlatformStatus string
SyncStatus string
Extra string
}
// CreativeCols 创意表字段常量
var CreativeCols = CreativeCol{
SQLBaseCol: beans.DefSQLBaseCol,
AdID: "ad_id",
CampaignID: "campaign_id",
Name: "name",
MaterialID: "material_id",
Title: "title",
Description: "description",
ClickURL: "click_url",
PlatformCreativeID: "platform_creative_id",
PlatformMaterialID: "platform_material_id",
AuditStatus: "audit_status",
Status: "status",
PlatformStatus: "platform_status",
SyncStatus: "sync_status",
Extra: "extra",
}
+45
View File
@@ -0,0 +1,45 @@
package dsp
import (
"gitea.redpowerfuture.com/red-future/common/beans"
)
// AdSlot 广告位实体(自有媒体广告位,售卖给计划)
type AdSlot struct {
beans.SQLBaseDO `orm:",inherit"`
// 业务字段
MediaID int64 `orm:"media_id" json:"mediaId" description:"媒体ID"`
Name string `orm:"name" json:"name" description:"广告位名称"`
SlotType string `orm:"slot_type" json:"slotType" description:"广告位类型 banner/feed/video/interstitial/reward"`
Size string `orm:"size" json:"size" description:"尺寸"`
Unit string `orm:"unit" json:"unit" description:"计费方式 cpm/cpc/cpa"`
BasePrice int64 `orm:"base_price" json:"basePrice" description:"底价(分,cpm按千次)"`
Status int `orm:"status" json:"status" description:"状态 1可售 0停售"`
Extra string `orm:"extra" json:"extra" description:"扩展(JSON) 定向能力声明等"`
}
// AdSlotCol 广告位表字段定义
type AdSlotCol struct {
beans.SQLBaseCol
MediaID string
Name string
SlotType string
Size string
Unit string
BasePrice string
Status string
Extra string
}
// AdSlotCols 广告位表字段常量
var AdSlotCols = AdSlotCol{
SQLBaseCol: beans.DefSQLBaseCol,
MediaID: "media_id",
Name: "name",
SlotType: "slot_type",
Size: "size",
Unit: "unit",
BasePrice: "base_price",
Status: "status",
Extra: "extra",
}
+53
View File
@@ -0,0 +1,53 @@
package dsp
import (
"github.com/gogf/gf/v2/os/gtime"
"gitea.redpowerfuture.com/red-future/common/beans"
)
// AuctionLog 展示计费日志实体(PD与RTB统一入口,按day分区)
type AuctionLog struct {
beans.SQLBaseDO `orm:",inherit"`
// 业务字段
RequestID string `orm:"request_id" json:"requestId" description:"请求ID(RTB)/流水号(PD)"`
CampaignID int64 `orm:"campaign_id" json:"campaignId" description:"本地计划ID"`
AdID int64 `orm:"ad_id" json:"adId" description:"广告ID"`
SlotID int64 `orm:"slot_id" json:"slotId" description:"广告位ID"`
Event string `orm:"event" json:"event" description:"事件 show/click"`
Price int64 `orm:"price" json:"price" description:"成交价(分,cpm按千次)"`
Cost int64 `orm:"cost" json:"cost" description:"本次事件应计费用(分,写入时算好)"`
DeviceID string `orm:"device_id" json:"deviceId" description:"设备ID"`
IP string `orm:"ip" json:"ip" description:"请求IP"`
Day *gtime.Time `orm:"day" json:"day" description:"分区日期"`
}
// AuctionLogCol 展示计费日志表字段定义
type AuctionLogCol struct {
beans.SQLBaseCol
RequestID string
CampaignID string
AdID string
SlotID string
Event string
Price string
Cost string
DeviceID string
IP string
Day string
}
// AuctionLogCols 展示计费日志表字段常量
var AuctionLogCols = AuctionLogCol{
SQLBaseCol: beans.DefSQLBaseCol,
RequestID: "request_id",
CampaignID: "campaign_id",
AdID: "ad_id",
SlotID: "slot_id",
Event: "event",
Price: "price",
Cost: "cost",
DeviceID: "device_id",
IP: "ip",
Day: "day",
}
+44
View File
@@ -0,0 +1,44 @@
package dsp
import (
"github.com/gogf/gf/v2/os/gtime"
"gitea.redpowerfuture.com/red-future/common/beans"
)
// BidRequestLog RTB请求日志实体(高并发,按day分区,可裁剪)
type BidRequestLog struct {
beans.SQLBaseDO `orm:",inherit"`
// 业务字段
RequestID string `orm:"request_id" json:"requestId" description:"请求ID(去重键)"`
SlotID int64 `orm:"slot_id" json:"slotId" description:"广告位ID"`
DeviceID string `orm:"device_id" json:"deviceId" description:"设备ID"`
IP string `orm:"ip" json:"ip" description:"请求IP"`
UA string `orm:"ua" json:"ua" description:"用户代理"`
Extra string `orm:"extra" json:"extra" description:"上下文/定向特征(JSON)"`
Day *gtime.Time `orm:"day" json:"day" description:"分区日期"`
}
// BidRequestLogCol RTB请求日志表字段定义
type BidRequestLogCol struct {
beans.SQLBaseCol
RequestID string
SlotID string
DeviceID string
IP string
UA string
Extra string
Day string
}
// BidRequestLogCols RTB请求日志表字段常量
var BidRequestLogCols = BidRequestLogCol{
SQLBaseCol: beans.DefSQLBaseCol,
RequestID: "request_id",
SlotID: "slot_id",
DeviceID: "device_id",
IP: "ip",
UA: "ua",
Extra: "extra",
Day: "day",
}
+33
View File
@@ -0,0 +1,33 @@
package dsp
import (
"gitea.redpowerfuture.com/red-future/common/beans"
)
// Media 媒体实体(自有媒体,公司流量侧)
type Media struct {
beans.SQLBaseDO `orm:",inherit"`
// 业务字段
Name string `orm:"name" json:"name" description:"媒体名称"`
Type string `orm:"type" json:"type" description:"媒体类型 app/web"`
Domain string `orm:"domain" json:"domain" description:"域名"`
Status int `orm:"status" json:"status" description:"状态 1启用 0停用"`
}
// MediaCol 媒体表字段定义
type MediaCol struct {
beans.SQLBaseCol
Name string
Type string
Domain string
Status string
}
// MediaCols 媒体表字段常量
var MediaCols = MediaCol{
SQLBaseCol: beans.DefSQLBaseCol,
Name: "name",
Type: "type",
Domain: "domain",
Status: "status",
}
+47
View File
@@ -0,0 +1,47 @@
package dsp
import (
"github.com/gogf/gf/v2/os/gtime"
"gitea.redpowerfuture.com/red-future/common/beans"
)
// Placement 预订单实体(PD直投/程序化直购:计划×广告位预订)
type Placement struct {
beans.SQLBaseDO `orm:",inherit"`
// 业务字段
AdSlotID int64 `orm:"ad_slot_id" json:"adSlotId" description:"广告位ID"`
CampaignID int64 `orm:"campaign_id" json:"campaignId" description:"本地计划ID"`
Quota int `orm:"quota" json:"quota" description:"预订量(展示次数)"`
Price int64 `orm:"price" json:"price" description:"协议价(分,按unit口径)"`
BookStart *gtime.Time `orm:"book_start" json:"bookStart" description:"预订开始时间"`
BookEnd *gtime.Time `orm:"book_end" json:"bookEnd" description:"预订结束时间"`
Status string `orm:"status" json:"status" description:"状态 pending/running/finished/ended"`
DeliveredCount int `orm:"delivered_count" json:"deliveredCount" description:"已投展示数"`
}
// PlacementCol 预订单表字段定义
type PlacementCol struct {
beans.SQLBaseCol
AdSlotID string
CampaignID string
Quota string
Price string
BookStart string
BookEnd string
Status string
DeliveredCount string
}
// PlacementCols 预订单表字段常量
var PlacementCols = PlacementCol{
SQLBaseCol: beans.DefSQLBaseCol,
AdSlotID: "ad_slot_id",
CampaignID: "campaign_id",
Quota: "quota",
Price: "price",
BookStart: "book_start",
BookEnd: "book_end",
Status: "status",
DeliveredCount: "delivered_count",
}
+57
View File
@@ -0,0 +1,57 @@
package material
import (
"gitea.redpowerfuture.com/red-future/common/beans"
)
// Material 素材实体(素材文件库,md5去重)
type Material struct {
beans.SQLBaseDO `orm:",inherit"`
// 业务字段
Type string `orm:"type" json:"type" description:"素材类型 image/video/text"`
Title string `orm:"title" json:"title" description:"素材标题"`
FilePath string `orm:"file_path" json:"filePath" description:"本地存储路径"`
FileSize int64 `orm:"file_size" json:"fileSize" description:"文件大小(字节)"`
MD5 string `orm:"md5" json:"md5" description:"文件MD5(去重键)"`
Width int `orm:"width" json:"width" description:"宽度(像素)"`
Height int `orm:"height" json:"height" description:"高度(像素)"`
Duration int `orm:"duration" json:"duration" description:"视频时长(秒)"`
Format string `orm:"format" json:"format" description:"文件格式"`
AuditStatus string `orm:"audit_status" json:"auditStatus" description:"审核状态 pending/auditing/pass/reject/disabled"`
Status int `orm:"status" json:"status" description:"状态 1可用 0禁用"`
Extra string `orm:"extra" json:"extra" description:"扩展(JSON) 如平台素材ID映射"`
}
// MaterialCol 素材表字段定义
type MaterialCol struct {
beans.SQLBaseCol
Type string
Title string
FilePath string
FileSize string
MD5 string
Width string
Height string
Duration string
Format string
AuditStatus string
Status string
Extra string
}
// MaterialCols 素材表字段常量
var MaterialCols = MaterialCol{
SQLBaseCol: beans.DefSQLBaseCol,
Type: "type",
Title: "title",
FilePath: "file_path",
FileSize: "file_size",
MD5: "md5",
Width: "width",
Height: "height",
Duration: "duration",
Format: "format",
AuditStatus: "audit_status",
Status: "status",
Extra: "extra",
}
+44
View File
@@ -0,0 +1,44 @@
package material
import (
"github.com/gogf/gf/v2/os/gtime"
"gitea.redpowerfuture.com/red-future/common/beans"
)
// MaterialAudit 素材审核记录实体(审核状态机全量日志,多通道)
type MaterialAudit struct {
beans.SQLBaseDO `orm:",inherit"`
// 业务字段
MaterialID int64 `orm:"material_id" json:"materialId" description:"素材ID"`
AuditChannel string `orm:"audit_channel" json:"auditChannel" description:"审核通道 yidun/platform/manual"`
AuditAction string `orm:"audit_action" json:"auditAction" description:"审核动作 submit/pass/reject/revoke"`
AuditStatus string `orm:"audit_status" json:"auditStatus" description:"审核状态"`
Result string `orm:"result" json:"result" description:"平台返回详情(JSON) 原因/违规项"`
Operator string `orm:"operator" json:"operator" description:"操作人"`
AuditTime *gtime.Time `orm:"audit_time" json:"auditTime" description:"审核时间"`
}
// MaterialAuditCol 素材审核记录表字段定义
type MaterialAuditCol struct {
beans.SQLBaseCol
MaterialID string
AuditChannel string
AuditAction string
AuditStatus string
Result string
Operator string
AuditTime string
}
// MaterialAuditCols 素材审核记录表字段常量
var MaterialAuditCols = MaterialAuditCol{
SQLBaseCol: beans.DefSQLBaseCol,
MaterialID: "material_id",
AuditChannel: "audit_channel",
AuditAction: "audit_action",
AuditStatus: "audit_status",
Result: "result",
Operator: "operator",
AuditTime: "audit_time",
}
+57
View File
@@ -0,0 +1,57 @@
package platform
import (
"gitea.redpowerfuture.com/red-future/common/beans"
)
// AdAccount 广告账户实体(平台凭据 + 多级层级 + API能力标识)
type AdAccount struct {
beans.SQLBaseDO `orm:",inherit"`
// 业务字段
Name string `orm:"name" json:"name" description:"账户显示名"`
Platform string `orm:"platform" json:"platform" description:"广告平台 douyin/xiaohongshu/kuaishou"`
AccountID string `orm:"account_id" json:"accountId" description:"平台侧账户ID"`
ParentAccountID int64 `orm:"parent_account_id" json:"parentAccountId" description:"上级账户ID(多级代理层级,自引用)"`
AccountRole string `orm:"account_role" json:"accountRole" description:"账户角色 agent/owner/sub"`
Capabilities string `orm:"capabilities" json:"capabilities" description:"API能力(JSON) attribution/delivery/report"`
AppID string `orm:"app_id" json:"appId" description:"应用ID"`
AppSecret string `orm:"app_secret" json:"appSecret" description:"应用密钥"`
AccessToken string `orm:"access_token" json:"accessToken" description:"访问令牌"`
TokenExpireAt int64 `orm:"token_expire_at" json:"tokenExpireAt" description:"令牌过期时间戳"`
Config string `orm:"config" json:"config" description:"平台特定配置(JSON) 含回传事件映射等"`
Enabled bool `orm:"enabled" json:"enabled" description:"是否启用"`
}
// AdAccountCol 广告账户表字段定义
type AdAccountCol struct {
beans.SQLBaseCol
Name string
Platform string
AccountID string
ParentAccountID string
AccountRole string
Capabilities string
AppID string
AppSecret string
AccessToken string
TokenExpireAt string
Config string
Enabled string
}
// AdAccountCols 广告账户表字段常量
var AdAccountCols = AdAccountCol{
SQLBaseCol: beans.DefSQLBaseCol,
Name: "name",
Platform: "platform",
AccountID: "account_id",
ParentAccountID: "parent_account_id",
AccountRole: "account_role",
Capabilities: "capabilities",
AppID: "app_id",
AppSecret: "app_secret",
AccessToken: "access_token",
TokenExpireAt: "token_expire_at",
Config: "config",
Enabled: "enabled",
}
@@ -0,0 +1,42 @@
package platform
import (
"gitea.redpowerfuture.com/red-future/common/beans"
)
// CommercePlatformConfig 电商平台配置实体(下游商品平台对接配置)
type CommercePlatformConfig struct {
beans.SQLBaseDO `orm:",inherit"`
// 业务字段
Name string `orm:"name" json:"name" description:"配置显示名"`
Platform string `orm:"platform" json:"platform" description:"电商平台 jd/taobao"`
AppKey string `orm:"app_key" json:"appKey" description:"应用Key"`
AppSecret string `orm:"app_secret" json:"appSecret" description:"应用密钥"`
CallbackURL string `orm:"callback_url" json:"callbackUrl" description:"订单回调接收URL"`
Config string `orm:"config" json:"config" description:"平台特定配置(JSON)"`
Enabled bool `orm:"enabled" json:"enabled" description:"是否启用"`
}
// CommercePlatformConfigCol 电商平台配置表字段定义
type CommercePlatformConfigCol struct {
beans.SQLBaseCol
Name string
Platform string
AppKey string
AppSecret string
CallbackURL string
Config string
Enabled string
}
// CommercePlatformConfigCols 电商平台配置表字段常量
var CommercePlatformConfigCols = CommercePlatformConfigCol{
SQLBaseCol: beans.DefSQLBaseCol,
Name: "name",
Platform: "platform",
AppKey: "app_key",
AppSecret: "app_secret",
CallbackURL: "callback_url",
Config: "config",
Enabled: "enabled",
}
+39
View File
@@ -0,0 +1,39 @@
package platform
import (
"gitea.redpowerfuture.com/red-future/common/beans"
)
// ItemMapping 广告商品与电商商品映射实体
type ItemMapping struct {
beans.SQLBaseDO `orm:",inherit"`
// 业务字段
AdPlatform string `orm:"ad_platform" json:"adPlatform" description:"广告平台"`
AdItemID string `orm:"ad_item_id" json:"adItemId" description:"广告平台侧商品ID"`
CommercePlatform string `orm:"commerce_platform" json:"commercePlatform" description:"电商平台"`
CommerceItemID string `orm:"commerce_item_id" json:"commerceItemId" description:"电商平台侧商品ID"`
SkuID string `orm:"sku_id" json:"skuId" description:"SKU ID"`
Config string `orm:"config" json:"config" description:"映射扩展配置(JSON)"`
}
// ItemMappingCol 商品映射表字段定义
type ItemMappingCol struct {
beans.SQLBaseCol
AdPlatform string
AdItemID string
CommercePlatform string
CommerceItemID string
SkuID string
Config string
}
// ItemMappingCols 商品映射表字段常量
var ItemMappingCols = ItemMappingCol{
SQLBaseCol: beans.DefSQLBaseCol,
AdPlatform: "ad_platform",
AdItemID: "ad_item_id",
CommercePlatform: "commerce_platform",
CommerceItemID: "commerce_item_id",
SkuID: "sku_id",
Config: "config",
}
-71
View File
@@ -1,71 +0,0 @@
package entity
import (
"cid/model/config"
"gitea.com/red-future/common/beans"
)
const PlatformDeliveryRuleCollection = "platform_delivery_rule"
// PlatformDeliveryRule 平台投放规则实体
type PlatformDeliveryRule struct {
beans.MongoBaseDO `bson:",inline" json:",inline"`
Status string `bson:"status" json:"status"` // 状态:active、inactive、maintenance等
// 关联信息
AppID string `bson:"appId" json:"appId"` // 应用ID
PlatformID string `bson:"platformId" json:"platformId"` // 平台ID
// 规则基本信息
Name string `bson:"name" json:"name"` // 规则名称
Description string `bson:"description" json:"description"` // 规则描述
RuleType string `bson:"ruleType" json:"ruleType"` // 规则类型:budget、targeting、bidding、frequency等
// 预算配置
config.BudgetConfig `bson:",inline" json:",inline"` // 内联预算配置
// 出价配置
config.BiddingConfig `bson:",inline" json:",inline"` // 内联竞价配置
// 定向配置
TargetingConfig string `bson:"targetingConfig" json:"targetingConfig"` // 定向配置(JSON格式)
IncludeAudience []string `bson:"includeAudience" json:"includeAudience"` // 包含受众
ExcludeAudience []string `bson:"excludeAudience" json:"excludeAudience"` // 排除受众
// 频次控制配置
config.FrequencyCapConfig `bson:",inline" json:",inline"` // 内联频次控制配置
// 创意配置
CreativeRotation string `bson:"creativeRotation" json:"creativeRotation"` // 创意轮播方式:optimize、even、random
SelectedCreatives []string `bson:"selectedCreatives" json:"selectedCreatives"` // 选中的创意列表
ExcludedCreatives []string `bson:"excludedCreatives" json:"excludedCreatives"` // 排除的创意列表
// 平台特定配置
PlatformSpecific string `bson:"platformSpecific" json:"platformSpecific"` // 平台特定配置(JSON格式)
// 监控和告警
PerformanceThresholds string `bson:"performanceThresholds" json:"performanceThresholds"` // 性能阈值(JSON格式)
// 自动优化配置
IsAutoOptimize bool `bson:"isAutoOptimize" json:"isAutoOptimize"` // 是否自动优化
LastOptimizeTime int64 `bson:"lastOptimizeTime" json:"lastOptimizeTime"` // 最后优化时间
AutoOptimizeConfig string `bson:"autoOptimizeConfig" json:"autoOptimizeConfig"` // 自动优化配置(JSON格式)
// 执行统计
ExecutionCount int64 `bson:"executionCount" json:"executionCount"` // 执行次数
SuccessCount int64 `bson:"successCount" json:"successCount"` // 成功次数
FailureCount int64 `bson:"failureCount" json:"failureCount"` // 失败次数
LastExecutionTime int64 `bson:"lastExecutionTime" json:"lastExecutionTime"` // 最后执行时间
NextExecutionTime int64 `bson:"nextExecutionTime" json:"nextExecutionTime"` // 下次执行时间
// 执行信息
CreatedBy string `bson:"createdBy" json:"createdBy"` // 创建人
LastModifiedBy string `bson:"lastModifiedBy" json:"lastModifiedBy"` // 最后修改人
ModifiedReason string `bson:"modifiedReason" json:"modifiedReason"` // 修改原因
}
// GetCollectionName 获取集合名称
func (p *PlatformDeliveryRule) GetCollectionName() string {
return PlatformDeliveryRuleCollection
}
+53
View File
@@ -0,0 +1,53 @@
package report
import (
"github.com/gogf/gf/v2/os/gtime"
"gitea.redpowerfuture.com/red-future/common/beans"
)
// DailyReport 日报表实体(统一消耗/效果汇总:外投拉平台报表,DSP聚合auction_log
type DailyReport struct {
beans.SQLBaseDO `orm:",inherit"`
// 业务字段
ReportDate *gtime.Time `orm:"report_date" json:"reportDate" description:"报表日期"`
Channel string `orm:"channel" json:"channel" description:"投放通道 external外投/self_dsp自有"`
AdAccountID int64 `orm:"ad_account_id" json:"adAccountId" description:"广告账户ID"`
CampaignID int64 `orm:"campaign_id" json:"campaignId" description:"本地计划ID"`
Platform string `orm:"platform" json:"platform" description:"广告平台 douyin/xiaohongshu/kuaishou"`
Impressions int64 `orm:"impressions" json:"impressions" description:"展示数"`
Clicks int64 `orm:"clicks" json:"clicks" description:"点击数"`
Cost int64 `orm:"cost" json:"cost" description:"消耗(分)"`
Conversions int64 `orm:"conversions" json:"conversions" description:"转化数"`
Extra string `orm:"extra" json:"extra" description:"平台报表原始快照(JSON),对账用"`
}
// DailyReportCol 日报表字段定义
type DailyReportCol struct {
beans.SQLBaseCol
ReportDate string
Channel string
AdAccountID string
CampaignID string
Platform string
Impressions string
Clicks string
Cost string
Conversions string
Extra string
}
// DailyReportCols 日报表字段常量
var DailyReportCols = DailyReportCol{
SQLBaseCol: beans.DefSQLBaseCol,
ReportDate: "report_date",
Channel: "channel",
AdAccountID: "ad_account_id",
CampaignID: "campaign_id",
Platform: "platform",
Impressions: "impressions",
Clicks: "clicks",
Cost: "cost",
Conversions: "conversions",
Extra: "extra",
}
-28
View File
@@ -1,28 +0,0 @@
package entity
import (
"gitea.com/red-future/common/beans"
)
const StrategyCollection = "strategy"
// Strategy 匹配策略表
type Strategy struct {
beans.MongoBaseDO `bson:",inline" json:",inline"`
Status string `bson:"status" json:"status"` // 状态:active、inactive、maintenance等
// 策略基本信息
Name string `bson:"name" json:"name"` // 策略名称
Description string `bson:"description" json:"description"` // 描述
MinConversion float64 `bson:"minConversion" json:"minConversion"` // 最低转化率
MaxConversion float64 `bson:"maxConversion" json:"maxConversion"` // 最高转化率
SourceWeights string `bson:"sourceWeights" json:"sourceWeights"` // 广告源权重 (JSON格式)
MaxAdsPerReq int `bson:"maxAdsPerReq" json:"maxAdsPerReq"` // 每次请求最大广告数
MaxReqPerHour int `bson:"maxReqPerHour" json:"maxReqPerHour"` // 每小时最大请求次数
Priority int `bson:"priority" json:"priority"` // 优先级(用于策略排序)
}
// GetCollectionName 获取集合名称
func (s *Strategy) GetCollectionName() string {
return StrategyCollection
}
-68
View File
@@ -1,68 +0,0 @@
package entity
// UnifiedTargeting 统一的定向条件
type UnifiedTargeting struct {
// 地理定向
Countries []string `bson:"countries" json:"countries"` // 国家列表
Regions []string `bson:"regions" json:"regions"` // 地区列表
Cities []string `bson:"cities" json:"cities"` // 城市列表
PostalCodes []string `bson:"postalCodes" json:"postalCodes"` // 邮政编码列表
// 人口统计定向
AgeRange *UnifiedAgeRange `bson:"ageRange" json:"ageRange"` // 年龄范围
Gender []string `bson:"gender" json:"gender"` // 性别
Income []string `bson:"income" json:"income"` // 收入水平
Education []string `bson:"education" json:"education"` // 教育程度
Occupation []string `bson:"occupation" json:"occupation"` // 职业类型
// 兴趣定向
Interests []string `bson:"interests" json:"interests"` // 兴趣标签
Lifestyle []string `bson:"lifestyle" json:"lifestyle"` // 生活方式
// 行为定向
SearchHistory []string `bson:"searchHistory" json:"searchHistory"` // 搜索历史
BrowseHistory []string `bson:"browseHistory" json:"browseHistory"` // 浏览历史
PurchaseHistory []string `bson:"purchaseHistory" json:"purchaseHistory"` // 购买历史
AdInteractions []string `bson:"adInteractions" json:"adInteractions"` // 广告互动
Behaviors []string `bson:"behaviors" json:"behaviors"` // 行为标签
Segments []string `bson:"segments" json:"segments"` // 用户分群
// 上下文定向
Categories []string `bson:"categories" json:"categories"` // 内容分类
Keywords []string `bson:"keywords" json:"keywords"` // 关键词
Tags []string `bson:"tags" json:"tags"` // 标签
Sentiment string `bson:"sentiment" json:"sentiment"` // 情感倾向
ContentType string `bson:"contentType" json:"contentType"` // 内容类型
ContentRating string `bson:"contentRating" json:"contentRating"` // 内容评级
// 设备定向
DeviceTypes []string `bson:"deviceTypes" json:"deviceTypes"` // 设备类型
OS []string `bson:"os" json:"os"` // 操作系统
Browsers []string `bson:"browsers" json:"browsers"` // 浏览器
Carriers []string `bson:"carriers" json:"carriers"` // 运营商
ConnectionTypes []string `bson:"connectionTypes" json:"connectionTypes"` // 连接类型
// 时间定向
TimeSlots []UnifiedTimeSlot `bson:"timeSlots" json:"timeSlots"` // 时间段
DaysOfWeek []int `bson:"daysOfWeek" json:"daysOfWeek"` // 星期几
Dates []string `bson:"dates" json:"dates"` // 日期范围
Timezone string `bson:"timezone" json:"timezone"` // 时区
ExcludeHolidays bool `bson:"excludeHolidays" json:"excludeHolidays"` // 排除节假日
// 扩展定向条件
CustomTargeting map[string]interface{} `bson:"customTargeting" json:"customTargeting"` // 自定义定向
}
// UnifiedAgeRange 统一的年龄范围
type UnifiedAgeRange struct {
Min int `bson:"min" json:"min"` // 最小年龄
Max int `bson:"max" json:"max"` // 最大年龄
}
// UnifiedTimeSlot 统一的时间段
type UnifiedTimeSlot struct {
DayOfWeek int `bson:"dayOfWeek" json:"dayOfWeek"` // 星期几:0-60表示星期日
StartTime string `bson:"startTime" json:"startTime"` // 开始时间,格式:HH:mm
EndTime string `bson:"endTime" json:"endTime"` // 结束时间,格式:HH:mm
Timezone string `bson:"timezone" json:"timezone"` // 时区
}
File diff suppressed because it is too large Load Diff
-167
View File
@@ -1,167 +0,0 @@
package service
import (
"cid/dao"
"cid/model/dto"
"cid/model/entity"
"context"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"go.mongodb.org/mongo-driver/v2/bson"
)
var AdPosition = new(adPosition)
type adPosition struct{}
// Add 添加广告位
func (s *adPosition) Add(ctx context.Context, req *dto.AddAdPositionReq) (res *dto.AddAdPositionRes, err error) {
ids, err := dao.AdPosition.Insert(ctx, req)
if err != nil {
return
}
res = &dto.AddAdPositionRes{Id: ids[0].(*bson.ObjectID)}
return
}
// Update 更新广告位
func (s *adPosition) Update(ctx context.Context, req *dto.UpdateAdPositionReq) error {
// 转换ID
id, err := bson.ObjectIDFromHex(req.Id)
if err != nil {
return gerror.Wrap(err, "无效的ID格式")
}
// 先获取原始广告位信息
originalAdPosition, err := dao.AdPosition.GetOne(ctx, &id)
if err != nil {
return gerror.Wrap(err, "获取原始广告位信息失败")
}
// 修改字段
if !g.IsEmpty(req.Name) {
originalAdPosition.Name = req.Name
}
if !g.IsEmpty(req.Description) {
originalAdPosition.Description = req.Description
}
if !g.IsEmpty(req.PositionCode) {
originalAdPosition.PositionCode = req.PositionCode
}
if !g.IsEmpty(req.AdFormat) {
originalAdPosition.AdFormat = req.AdFormat
}
if req.Width != nil {
originalAdPosition.Width = int64(*req.Width)
}
if req.Height != nil {
originalAdPosition.Height = int64(*req.Height)
}
if !g.IsEmpty(req.Page) {
originalAdPosition.Page = req.Page
}
if !g.IsEmpty(req.Section) {
originalAdPosition.Section = req.Section
}
if !g.IsEmpty(req.Location) {
originalAdPosition.Location = req.Location
}
if req.MaxAds != nil {
originalAdPosition.MaxAds = *req.MaxAds
}
if req.RefreshInterval != nil {
originalAdPosition.RefreshInterval = *req.RefreshInterval
}
if req.IsLazyLoad != nil {
originalAdPosition.IsLazyLoad = *req.IsLazyLoad
}
if !g.IsEmpty(req.PricingModel) {
originalAdPosition.PricingModel = req.PricingModel
}
if req.BasePrice != nil {
originalAdPosition.BasePrice = *req.BasePrice
}
if req.FloorPrice != nil {
originalAdPosition.FloorPrice = *req.FloorPrice
}
if !g.IsEmpty(req.PriceUnit) {
originalAdPosition.PriceUnit = req.PriceUnit
}
if req.DisplayRules != nil {
originalAdPosition.DisplayRules = req.DisplayRules
}
if req.Status != nil {
originalAdPosition.Status = *req.Status
}
if req.IsExclusive != nil {
originalAdPosition.IsExclusive = *req.IsExclusive
}
return dao.AdPosition.Update(ctx, &id, originalAdPosition)
}
// UpdateStatus 更新广告位状态
func (s *adPosition) UpdateStatus(ctx context.Context, req *dto.UpdateAdPositionStatusReq) error {
id, err := bson.ObjectIDFromHex(req.Id)
if err != nil {
return gerror.Wrap(err, "无效的ID格式")
}
return dao.AdPosition.UpdateStatus(ctx, &id, req.Status)
}
// GetOne 获取广告位详情
func (s *adPosition) GetOne(ctx context.Context, req *dto.GetAdPositionReq) (res *dto.GetAdPositionRes, err error) {
id, err := bson.ObjectIDFromHex(req.Id)
if err != nil {
return nil, gerror.Wrap(err, "无效的ID格式")
}
adPosition, err := dao.AdPosition.GetOne(ctx, &id)
if err != nil {
return
}
res = &dto.GetAdPositionRes{
AdPosition: adPosition,
}
return
}
// List 获取广告位列表
func (s *adPosition) List(ctx context.Context, req *dto.ListAdPositionReq) (res *dto.ListAdPositionRes, err error) {
list, total, err := dao.AdPosition.List(ctx, req)
if err != nil {
return
}
res = &dto.ListAdPositionRes{
List: list,
Total: int(total),
}
return
}
// GetAvailableAdPositions 获取可用的广告位列表
func (s *adPosition) GetAvailableAdPositions(ctx context.Context) (list []*entity.AdPosition, err error) {
return dao.AdPosition.GetAvailableAdPositions(ctx)
}
// MatchAd 匹配广告
func (s *adPosition) MatchAd(ctx context.Context, positionCode string, userInfo map[string]interface{}) (ad *entity.Advertisement, err error) {
// 返回匹配的广告
// 这里返回第一个广告作为示例
ad = &entity.Advertisement{
Title: "示例广告",
MaterialUrl: "https://example.com/ad.jpg",
TargetUrl: "https://example.com",
}
return
}
// UpdateAdPositionStatistics 更新广告位统计
func (s *adPosition) UpdateAdPositionStatistics(ctx context.Context, id string, impressions, clicks, revenue int64) (err error) {
return
}

Some files were not shown because too many files have changed in this diff Show More