Browse Source

广告计划配置与预览

pudongliang 4 months ago
parent
commit
1b2852118f
25 changed files with 2120 additions and 0 deletions
  1. 1 0
      portal/src/api/account.js
  2. 7 0
      portal/src/api/accountGroup.js
  3. 69 0
      portal/src/api/adPlanConf.js
  4. 7 0
      portal/src/api/conversion.js
  5. 6 0
      portal/src/router/index.js
  6. 525 0
      portal/src/views/ad-plan-conf/index.vue
  7. 17 0
      src/main/java/com/moka/gdtauto/controller/AccountGroupController.java
  8. 212 0
      src/main/java/com/moka/gdtauto/controller/AdPlanConfController.java
  9. 127 0
      src/main/java/com/moka/gdtauto/controller/AdPlanController.java
  10. 166 0
      src/main/java/com/moka/gdtauto/controller/AdPlanCreativeController.java
  11. 15 0
      src/main/java/com/moka/gdtauto/controller/TencentConversionController.java
  12. 51 0
      src/main/java/com/moka/gdtauto/controller/vo/CreateAdPlanConfRequest.java
  13. 48 0
      src/main/java/com/moka/gdtauto/controller/vo/UpdateAdPlanConfRequest.java
  14. 116 0
      src/main/java/com/moka/gdtauto/entity/AdPlan.java
  15. 95 0
      src/main/java/com/moka/gdtauto/entity/AdPlanConf.java
  16. 90 0
      src/main/java/com/moka/gdtauto/entity/AdPlanCreative.java
  17. 15 0
      src/main/java/com/moka/gdtauto/mapper/AdPlanConfMapper.java
  18. 15 0
      src/main/java/com/moka/gdtauto/mapper/AdPlanCreativeMapper.java
  19. 15 0
      src/main/java/com/moka/gdtauto/mapper/AdPlanMapper.java
  20. 58 0
      src/main/java/com/moka/gdtauto/service/AdPlanConfService.java
  21. 75 0
      src/main/java/com/moka/gdtauto/service/AdPlanCreativeService.java
  22. 57 0
      src/main/java/com/moka/gdtauto/service/AdPlanService.java
  23. 98 0
      src/main/java/com/moka/gdtauto/service/impl/AdPlanConfServiceImpl.java
  24. 134 0
      src/main/java/com/moka/gdtauto/service/impl/AdPlanCreativeServiceImpl.java
  25. 101 0
      src/main/java/com/moka/gdtauto/service/impl/AdPlanServiceImpl.java

+ 1 - 0
portal/src/api/account.js

@@ -48,3 +48,4 @@ export function getAllAccounts(accountId) {
     method: 'get'
   })
 }
+

+ 7 - 0
portal/src/api/accountGroup.js

@@ -48,6 +48,13 @@ export function getAccountGroupList(pageNum = 1, pageSize = 10) {
   })
 }
 
+export function getAccountGroupListAll() {
+  return request({
+    url: '/api/account-group/all',
+    method: 'get'
+  })
+}
+
 /**
  * 获取账号组详情
  * @param {Number} groupId 账号组ID

+ 69 - 0
portal/src/api/adPlanConf.js

@@ -0,0 +1,69 @@
+import request from '@/utils/request'
+
+export function reCreateAdPlanConf(data) {
+  return request({
+    url: '/api/ad-plan-conf/reCreate',
+    method: 'post',
+    data
+  })
+}
+
+
+/**
+ * 创建广告计划配置
+ * @param {Object} data 配置数据
+ */
+export function createAdPlanConf(data) {
+  return request({
+    url: '/api/ad-plan-conf/create',
+    method: 'post',
+    data
+  })
+}
+
+/**
+ * 更新广告计划配置
+ * @param {Object} data 配置数据
+ */
+export function updateAdPlanConf(data) {
+  return request({
+    url: '/api/ad-plan-conf/update',
+    method: 'post',
+    data
+  })
+}
+
+/**
+ * 删除广告计划配置
+ * @param {Number} confId 配置ID
+ */
+export function deleteAdPlanConf(confId) {
+  return request({
+    url: `/api/ad-plan-conf/${confId}`,
+    method: 'delete'
+  })
+}
+
+/**
+ * 获取广告计划配置列表(分页)
+ * @param {Number} pageNum 页码
+ * @param {Number} pageSize 每页数量
+ */
+export function getAdPlanConfList(pageNum = 1, pageSize = 10) {
+  return request({
+    url: '/api/ad-plan-conf/list',
+    method: 'get',
+    params: { pageNum, pageSize }
+  })
+}
+
+/**
+ * 获取广告计划配置详情
+ * @param {Number} confId 配置ID
+ */
+export function getAdPlanConfDetail(confId) {
+  return request({
+    url: `/api/ad-plan-conf/${confId}`,
+    method: 'get'
+  })
+}

+ 7 - 0
portal/src/api/conversion.js

@@ -37,3 +37,10 @@ export function getConversionDetail(conversionId) {
     params: { conversionId }
   })
 }
+
+export function getConversions() {
+  return request({
+    url: '/api/gdt/conversion/all',
+    method: 'get'
+  })
+}

+ 6 - 0
portal/src/router/index.js

@@ -78,6 +78,12 @@ const routes = [
         name: 'Video',
         component: () => import('@/views/video/index.vue'),
         meta: { title: '视频上传', icon: 'VideoCamera' }
+      },
+      {
+        path: 'ad-plan-conf',
+        name: 'AdPlanConf',
+        component: () => import('@/views/ad-plan-conf/index.vue'),
+        meta: { title: '广告计划配置', icon: 'Setting' }
       }
     ]
   }

+ 525 - 0
portal/src/views/ad-plan-conf/index.vue

@@ -0,0 +1,525 @@
+<template>
+  <div class="space-y-6">
+    <!-- 主卡片 -->
+    <div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
+      <div class="border-b border-gray-200 pb-4 mb-6">
+        <div class="flex items-center justify-between">
+          <h2 class="text-xl font-semibold text-gray-800 flex items-center gap-2">
+            <el-icon><Setting /></el-icon>
+            广告计划配置管理
+          </h2>
+          <el-button type="primary" @click="handleCreate">
+            <el-icon><Plus /></el-icon>
+            创建配置
+          </el-button>
+        </div>
+      </div>
+
+      <!-- 配置列表 -->
+      <el-table
+        :data="confList"
+        style="width: 100%"
+        v-loading="loading"
+        stripe
+        border
+      >
+        <el-table-column prop="id" label="ID" width="80" />
+        <el-table-column prop="name" label="计划名称" width="200" show-overflow-tooltip />
+        <el-table-column label="广告类型" width="150">
+          <template #default="{ row }">
+            <el-tag :type="getAdTypeColor(row.adType)">
+              {{ getAdTypeText(row.adType) }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="出价金额" width="120">
+          <template #default="{ row }">
+            ¥{{ (row.bidAmount / 100).toFixed(2) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="accountGroupId" label="账号组ID" width="100" />
+        <el-table-column prop="conversionId" label="转化ID" width="120" />
+        <el-table-column prop="dramaId" label="短剧ID" width="100" />
+        <el-table-column prop="wxCorpid" label="企微主体ID" width="150" show-overflow-tooltip />
+        <el-table-column label="创建时间" width="180">
+          <template #default="{ row }">
+            {{ formatDate(row.createTime) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="200" fixed="right">
+          <template #default="{ row }">
+            <el-button type="primary" size="small" link @click="handleEdit(row)">
+              <el-icon><Edit /></el-icon>
+              编辑
+            </el-button>
+            <el-button type="danger" size="small" link @click="handleDelete(row)">
+              <el-icon><Delete /></el-icon>
+              删除
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- 分页 -->
+      <div class="mt-6 flex justify-end">
+        <el-pagination
+          v-if="total > 0"
+          v-model:current-page="currentPage"
+          v-model:page-size="pageSize"
+          :page-sizes="[10, 20, 50]"
+          :total="total"
+          layout="total, sizes, prev, pager, next, jumper"
+        />
+      </div>
+    </div>
+
+    <!-- 创建/编辑配置对话框 -->
+    <el-dialog
+      v-model="dialogVisible"
+      :title="dialogTitle"
+      width="60%"
+      @close="handleDialogClose"
+    >
+      <el-form
+        ref="formRef"
+        :model="formData"
+        :rules="formRules"
+        label-width="120px"
+      >
+        <el-form-item label="计划名称" prop="name">
+          <el-input
+            v-model="formData.name"
+            placeholder="请输入计划名称"
+            clearable
+          />
+        </el-form-item>
+        
+        <el-form-item label="广告类型" prop="adType">
+          <el-radio-group v-model="formData.adType">
+            <el-radio label="企微" :value="1" />
+            <el-radio label="ROI" :value="2" />
+          </el-radio-group>
+        </el-form-item>
+
+        <el-form-item label="账号组ID" prop="accountGroupId">
+          <el-select
+            v-model="formData.accountGroupId"
+            style="width: 100%"
+            placeholder="请选择账号组"
+          >
+            <el-option
+              v-for="accountGroup in accountGroupList"
+              :key="accountGroup.id"
+              :label="accountGroup.groupName"
+              :value="accountGroup.id"
+            />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="出价金额(元)" prop="bidAmount">
+          <el-input-number
+            v-model="bidAmountYuan"
+            :min="0"
+            :precision="2"
+            placeholder="请输入出价金额"
+            style="width: 100%"
+          />
+          <span class="ml-2 text-sm text-gray-500">= {{ formData.bidAmount }} 分</span>
+        </el-form-item>
+
+        <el-form-item label="转化ID" prop="conversionId">
+          <el-select
+            v-model="formData.conversionId"
+            :min="1"
+            placeholder="请输入转化ID"
+            style="width: 100%"
+          >
+            <el-option
+              v-for="conversion in conversions"
+              :key="conversion.conversionId"
+              :label="conversion.conversionName"
+              :value="conversion.conversionId"
+            />
+          </el-select>
+        </el-form-item>
+         <el-form-item label="定向IDs" prop="targets">
+          <el-input
+            v-model="formData.targets"
+            type="textarea"
+            :rows="2"
+            placeholder="多个定向ID用逗号分隔,例如:1,2,3"
+          />
+        </el-form-item>
+         <el-form-item label="版位IDs" prop="sites">
+          <el-input
+            v-model="formData.sites"
+            type="textarea"
+            :rows="2"
+            placeholder="多个版位ID用逗号分隔,例如:1,2,3"
+          />
+        </el-form-item>
+        <el-form-item label="企微主体ID" prop="wxCorpid" v-if="formData.adType === 1">
+          <el-input
+            v-model="formData.wxCorpid"
+            placeholder="请输入企微主体ID"
+            clearable
+          />
+        </el-form-item>
+
+        <el-form-item label="短剧ID" prop="dramaId" v-if="formData.adType === 2">
+          <el-input-number
+            v-model="formData.dramaId"
+            :min="1"
+            placeholder="请输入短剧ID"
+            style="width: 100%"
+          />
+        </el-form-item>
+
+        <el-form-item label="图片IDs" prop="imageIds">
+          <el-input
+            v-model="formData.imageIds"
+            type="textarea"
+            :rows="2"
+            placeholder="多个图片ID用逗号分隔,例如:1,2,3"
+          />
+        </el-form-item>
+
+        <el-form-item label="落地页IDs" prop="pageIds">
+          <el-input
+            v-model="formData.pageIds"
+            type="textarea"
+            :rows="2"
+            placeholder="多个落地页ID用逗号分隔,例如:1,2,3"
+          />
+        </el-form-item>
+      </el-form>
+      <el-button @click="handleReCreate">预览</el-button>
+      <el-table :data="plans" 
+      :preserve-expanded-content="true"
+      >
+        <el-table-column type="expand">
+        <template #default="props">
+            <div m="4">
+            <h3>创意</h3>
+            <el-table :data="props.row.adCreatives" border>
+                <el-table-column label="图片" prop="imageId" />
+            </el-table>
+            </div>
+        </template>
+        </el-table-column>
+        <el-table-column prop="adgroupName" label="计划名称" width="200" show-overflow-tooltip />
+        <el-table-column prop="accountId" label="账号ID" width="200" show-overflow-tooltip />
+        <el-table-column prop="target" label="定向IDs" width="150" show-overflow-tooltip />
+        <el-table-column prop="site" label="版位IDs" width="150" show-overflow-tooltip />
+        <el-table-column prop="adType" label="广告类型" width="150">
+          <template #default="{ row }">
+            <el-tag :type="getAdTypeColor(row.adType)">
+              {{ getAdTypeText(row.adType) }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="bidAmount" label="出价金额" width="120">
+          <template #default="{ row }">
+            ¥{{ (row.bidAmount / 100).toFixed(2) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="conversionId" label="转化ID" width="120" />
+        <el-table-column prop="wxCorpid" label="企微主体ID" width="150" show-overflow-tooltip />
+        <el-table-column prop="dramaId" label="短剧ID" width="100" />
+    
+        <el-table-column prop="imageId" label="图片IDs" width="150" show-overflow-tooltip />
+        <el-table-column prop="pageId" label="落地页IDs" width="150" show-overflow-tooltip />
+      </el-table>
+      <template #footer>
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="handleSubmit" :loading="submitting">
+          确定
+        </el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, onMounted, watch } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import {
+  getAdPlanConfList,
+  createAdPlanConf,
+  updateAdPlanConf,
+  deleteAdPlanConf,
+  reCreateAdPlanConf
+} from '@/api/adPlanConf'
+import { getConversions } from '@/api/conversion'
+import { getAccountGroupListAll } from '@/api/accountGroup'
+
+import {
+  Setting,
+  Plus,
+  Edit,
+  Delete
+} from '@element-plus/icons-vue'
+
+// 数据
+const loading = ref(false)
+const submitting = ref(false)
+const confList = ref([])
+const currentPage = ref(1)
+const pageSize = ref(10)
+const total = ref(0)
+
+// 对话框
+const dialogVisible = ref(false)
+const dialogTitle = ref('创建配置')
+const formRef = ref(null)
+const editingId = ref(null)
+const conversions = ref([])
+const accountGroupList = ref([])
+const plans = ref([])
+
+// 表单数据
+const formData = ref({
+  name: '',
+  accountGroupId: null,
+  adType: 1,
+  bidAmount: 0,
+  conversionId: null,
+  wxCorpid: '',
+  dramaId: null,
+  imageIds: '',
+  pageIds: '',
+  jobNumber: ''
+})
+
+// 出价金额(元)
+const bidAmountYuan = computed({
+  get: () => formData.value.bidAmount / 100,
+  set: (val) => {
+    formData.value.bidAmount = Math.round(val * 100)
+  }
+})
+
+// 表单验证规则
+const formRules = {
+  name: [
+    { required: true, message: '请输入计划名称', trigger: 'blur' }
+  ],
+  accountGroupId: [
+    { required: true, message: '请输入账号组ID', trigger: 'blur' }
+  ],
+  adType: [
+    { required: true, message: '请选择广告类型', trigger: 'change' }
+  ],
+  bidAmount: [
+    { required: true, message: '请输入出价金额', trigger: 'blur' }
+  ],
+  conversionId: [
+    { required: true, message: '请输入转化ID', trigger: 'blur' }
+  ],
+  wxCorpid: [
+    { required: true, message: '请输入企微主体ID', trigger: 'blur' }
+  ],
+  dramaId: [
+    { required: true, message: '请输入短剧ID', trigger: 'blur' }
+  ]
+}
+
+// 方法
+const loadConfList = async () => {
+  loading.value = true
+  try {
+    const res = await getAdPlanConfList(currentPage.value, pageSize.value)
+    if (res.success) {
+      confList.value = res.data?.records || []
+      total.value = res.data?.total || 0
+    } else {
+      ElMessage.error(res.message || '加载配置列表失败')
+    }
+  } catch (error) {
+    console.error('加载配置列表失败:', error)
+    ElMessage.error('加载配置列表失败')
+  } finally {
+    loading.value = false
+  }
+}
+
+const getAdTypeText = (type) => {
+  const typeMap = {
+    1: '企微',
+    2: 'ROI'
+  }
+  return typeMap[type] || '未知'
+}
+
+const getAdTypeColor = (type) => {
+  const colorMap = {
+    1: 'success',
+    2: 'warning'
+  }
+  return colorMap[type] || 'info'
+}
+
+const formatDate = (dateStr) => {
+  if (!dateStr) return '-'
+  return new Date(dateStr).toLocaleString('zh-CN')
+}
+
+const handleAdTypeChange = () => {
+  // 切换广告类型时清空相关字段
+  if (formData.value.adType === 1) {
+    formData.value.dramaId = null
+  } else if (formData.value.adType === 2) {
+    formData.value.wxCorpid = ''
+  }
+}
+
+const handleCreate = () => {
+  dialogTitle.value = '创建配置'
+  editingId.value = null
+  formData.value = {
+    name: '',
+    accountGroupId: null,
+    adType: 1,
+    bidAmount: 1500,
+    conversionId: null,
+    targets: null,
+    sites: null,
+    wxCorpid: '',
+    dramaId: null,
+    imageIds: '',
+    pageIds: '',
+    jobNumber: ''
+  }
+  dialogVisible.value = true
+}
+
+const handleEdit = (row) => {
+  dialogTitle.value = '编辑配置'
+  editingId.value = row.id
+  formData.value = {
+    name: row.name,
+    accountGroupId: row.accountGroupId,
+    adType: row.adType,
+    bidAmount: row.bidAmount,
+    conversionId: row.conversionId,
+    targets: row.targets,
+    sites: row.sites,
+    wxCorpid: row.wxCorpid || '',
+    dramaId: row.dramaId,
+    imageIds: row.imageIds || '',
+    pageIds: row.pageIds || '',
+    jobNumber: row.jobNumber || ''
+  }
+  dialogVisible.value = true
+}
+
+const handleDelete = async (row) => {
+  try {
+    await ElMessageBox.confirm(
+      `确定要删除配置"${row.name}"吗?`,
+      '提示',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }
+    )
+
+    const res = await deleteAdPlanConf(row.id)
+    if (res.success) {
+      ElMessage.success('删除成功')
+      await loadConfList()
+    } else {
+      ElMessage.error(res.message || '删除失败')
+    }
+  } catch (error) {
+    if (error !== 'cancel') {
+      console.error('删除配置失败:', error)
+      ElMessage.error('删除失败')
+    }
+  }
+}
+
+const handleReCreate = async () => {
+  const res = await reCreateAdPlanConf(formData.value)
+  if (res.success) {
+    ElMessage.success('预览成功')
+    console.log(res.data)
+    plans.value = res.data
+  } else {
+    ElMessage.error(res.message || '预览失败')
+  }
+}
+
+const handleSubmit = async () => {
+  try {
+    await formRef.value.validate()
+    
+    submitting.value = true
+    const data = { ...formData.value }
+
+    let res
+    if (editingId.value) {
+      // 编辑
+      data.id = editingId.value
+      res = await updateAdPlanConf(data)
+    } else {
+      // 创建
+      res = await createAdPlanConf(data)
+    }
+
+    if (res.success) {
+      ElMessage.success(editingId.value ? '更新成功' : '创建成功')
+      dialogVisible.value = false
+      await loadConfList()
+    } else {
+      ElMessage.error(res.message || '操作失败')
+    }
+  } catch (error) {
+    if (error !== false) {
+      console.error('提交失败:', error)
+      ElMessage.error('操作失败')
+    }
+  } finally {
+    submitting.value = false
+  }
+}
+
+const handleDialogClose = () => {
+  formRef.value?.resetFields()
+}
+
+const loadConversions = async () => {
+  const res = await getConversions()
+  if (res.success) {
+    conversions.value = res.data
+  } else {
+    ElMessage.error(res.message || '加载转化列表失败')
+  }
+}
+
+const loadAccountGroupList = async () => {
+  const res = await getAccountGroupListAll()
+  if (res.success) {
+    accountGroupList.value = res.data
+  } else {
+    ElMessage.error(res.message || '加载账号组列表失败')
+  }
+}
+
+// 监听分页变化
+watch([currentPage, pageSize], () => {
+  loadConfList()
+})
+
+// 生命周期
+onMounted(() => {
+  loadConfList()
+  loadConversions()
+  loadAccountGroupList()
+})
+</script>
+
+<style scoped>
+/* 自定义样式 */
+</style>

+ 17 - 0
src/main/java/com/moka/gdtauto/controller/AccountGroupController.java

@@ -128,6 +128,23 @@ public class AccountGroupController {
     }
 
     /**
+     * 获取用户的账号组列表(不分页)
+     */
+    @GetMapping("/all")
+    public Result<List<AccountGroup>> getAllAccountGroups(
+            @AuthUser SysUser sysUser) {
+        try {
+            List<AccountGroup> accountGroups = accountGroupService.lambdaQuery()
+                    .eq(AccountGroup::getUserId, sysUser.getId())
+                    .list();
+            return Result.success(accountGroups, "查询成功");
+        } catch (Exception e) {
+            log.error("查询账号组列表失败", e);
+            return Result.fail("查询账号组列表失败: " + e.getMessage());
+        }
+    }
+
+    /**
      * 获取账号组详情
      */
     @Operation(summary = "获取账号组详情", description = "根据ID获取账号组详细信息")

+ 212 - 0
src/main/java/com/moka/gdtauto/controller/AdPlanConfController.java

@@ -0,0 +1,212 @@
+package com.moka.gdtauto.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.moka.gdtauto.annotation.AuthUser;
+import com.moka.gdtauto.common.Result;
+import com.moka.gdtauto.controller.vo.CreateAdPlanConfRequest;
+import com.moka.gdtauto.controller.vo.UpdateAdPlanConfRequest;
+import com.moka.gdtauto.entity.AccountGroupAccountRelationship;
+import com.moka.gdtauto.entity.AdPlanConf;
+import com.moka.gdtauto.entity.SysUser;
+import com.moka.gdtauto.entity.AdPlan;
+import com.moka.gdtauto.entity.AdPlanCreative;
+
+
+import com.moka.gdtauto.mapper.AccountGroupAccountRelationshipMapper;
+import com.moka.gdtauto.service.AdPlanConfService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 广告计划配置管理控制器
+ * 
+ * @author moka
+ * @since 2026-02-24
+ */
+@Tag(name = "广告计划配置管理", description = "广告计划配置的创建、更新、删除及查询")
+@Slf4j
+@RestController
+@RequestMapping("/api/ad-plan-conf")
+@RequiredArgsConstructor
+public class AdPlanConfController {
+
+    private final AdPlanConfService adPlanConfService;
+    private final AccountGroupAccountRelationshipMapper relationshipMapper;
+
+    @PostMapping("/reCreate")
+    public Result<List<AdPlan>> reCreateAdPlanConf(
+            @RequestBody CreateAdPlanConfRequest request,
+            @AuthUser SysUser sysUser) {
+        try {
+            Long accountGroupId = request.getAccountGroupId();
+            List<AccountGroupAccountRelationship> relationships = relationshipMapper.selectList(
+                    new LambdaQueryWrapper<AccountGroupAccountRelationship>()
+                            .eq(AccountGroupAccountRelationship::getGroupId, accountGroupId));
+            String confName = request.getName();
+            String imageIds = request.getImageIds();
+            String[] imageIdArray = imageIds.split(",");
+            List<AdPlan>  plans = new ArrayList<>();
+            for (AccountGroupAccountRelationship relationship : relationships) {
+                Long accountId = relationship.getAccountId();
+                
+                String targets = request.getTargets();
+                String sites = request.getSites();
+
+                String[] targetArray = targets.split(",");
+                String[] siteArray = sites.split(",");
+                int i = 0;
+                for (String target : targetArray) {
+                    for (String site : siteArray) {
+                        i++;
+                        // 创建广告计划配置
+                        AdPlan adPlan = new AdPlan();
+                        String planName = confName + " - " + i;
+                        adPlan.setAccountId(accountId);
+                        adPlan.setAdgroupName(planName);
+                        adPlan.setAdType(request.getAdType());
+                        adPlan.setTarget(target);
+                        adPlan.setSite(site);
+                        adPlan.setConversionId(request.getConversionId());
+                        adPlan.setDramaId(request.getDramaId());
+                        adPlan.setBidAmount(request.getBidAmount());
+                        adPlan.setWxCorpid(request.getWxCorpid());
+                        plans.add(adPlan);
+                        List<AdPlanCreative> adPlanCreatives = new ArrayList<>();
+                        adPlan.setAdCreatives(adPlanCreatives);
+                        for (String imageId : imageIdArray) {
+                            AdPlanCreative adPlanCreative = new AdPlanCreative();
+                            adPlanCreative.setImageId(Long.valueOf(imageId));
+                            adPlanCreatives.add(adPlanCreative);
+                        }
+                    }
+                }
+            }
+            
+            return Result.success(plans,"重新创建广告计划配置成功");
+        } catch (Exception e) {
+            log.error("重新创建广告计划配置失败", e);
+            return Result.fail("重新创建广告计划配置失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 创建广告计划配置
+     */
+    @Operation(summary = "创建广告计划配置", description = "为当前用户创建新的广告计划配置")
+    @PostMapping("/create")
+    public Result<Long> createAdPlanConf(
+            @RequestBody CreateAdPlanConfRequest request,
+            @AuthUser SysUser sysUser) {
+        try {
+            AdPlanConf adPlanConf = new AdPlanConf();
+            BeanUtils.copyProperties(request, adPlanConf);
+            adPlanConf.setUserId(sysUser.getId());
+            
+            Long confId = adPlanConfService.createAdPlanConf(adPlanConf);
+            return Result.success(confId, "创建广告计划配置成功");
+        } catch (Exception e) {
+            log.error("创建广告计划配置失败", e);
+            return Result.fail("创建广告计划配置失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 更新广告计划配置
+     */
+    @Operation(summary = "更新广告计划配置", description = "更新指定的广告计划配置")
+    @PostMapping("/update")
+    public Result<Void> updateAdPlanConf(
+            @RequestBody UpdateAdPlanConfRequest request,
+            @AuthUser SysUser sysUser) {
+        try {
+            AdPlanConf adPlanConf = new AdPlanConf();
+            BeanUtils.copyProperties(request, adPlanConf);
+            adPlanConf.setUserId(sysUser.getId());
+            
+            boolean success = adPlanConfService.updateAdPlanConf(adPlanConf);
+            if (success) {
+                return Result.success("更新广告计划配置成功");
+            } else {
+                return Result.fail("广告计划配置不存在或无权限");
+            }
+        } catch (Exception e) {
+            log.error("更新广告计划配置失败", e);
+            return Result.fail("更新广告计划配置失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 删除广告计划配置
+     */
+    @Operation(summary = "删除广告计划配置", description = "删除指定的广告计划配置")
+    @DeleteMapping("/{confId}")
+    public Result<Void> deleteAdPlanConf(
+            @Parameter(description = "配置ID", required = true)
+            @PathVariable Long confId,
+            @AuthUser SysUser sysUser) {
+        try {
+            boolean success = adPlanConfService.deleteAdPlanConf(confId, sysUser.getId());
+            if (success) {
+                return Result.success("删除广告计划配置成功");
+            } else {
+                return Result.fail("广告计划配置不存在或无权限");
+            }
+        } catch (Exception e) {
+            log.error("删除广告计划配置失败", e);
+            return Result.fail("删除广告计划配置失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取用户的广告计划配置列表(分页)
+     */
+    @Operation(summary = "获取广告计划配置列表", description = "分页获取当前用户的所有广告计划配置")
+    @GetMapping("/list")
+    public Result<Page<AdPlanConf>> getAdPlanConfs(
+            @Parameter(description = "页码", example = "1")
+            @RequestParam(defaultValue = "1") Integer pageNum,
+            @Parameter(description = "每页大小", example = "10")
+            @RequestParam(defaultValue = "10") Integer pageSize,
+            @AuthUser SysUser sysUser) {
+        try {
+            Page<AdPlanConf> page = adPlanConfService.getUserAdPlanConfs(
+                    sysUser.getId(), pageNum, pageSize);
+            return Result.success(page, "查询成功");
+        } catch (Exception e) {
+            log.error("查询广告计划配置列表失败", e);
+            return Result.fail("查询广告计划配置列表失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取广告计划配置详情
+     */
+    @Operation(summary = "获取广告计划配置详情", description = "根据ID获取广告计划配置详细信息")
+    @GetMapping("/{confId}")
+    public Result<AdPlanConf> getAdPlanConf(
+            @Parameter(description = "配置ID", required = true)
+            @PathVariable Long confId,
+            @AuthUser SysUser sysUser) {
+        try {
+            AdPlanConf adPlanConf = adPlanConfService.getAdPlanConfById(confId, sysUser.getId());
+            if (adPlanConf != null) {
+                return Result.success(adPlanConf, "查询成功");
+            } else {
+                return Result.fail("广告计划配置不存在或无权限");
+            }
+        } catch (Exception e) {
+            log.error("查询广告计划配置详情失败", e);
+            return Result.fail("查询广告计划配置详情失败: " + e.getMessage());
+        }
+    }
+}

+ 127 - 0
src/main/java/com/moka/gdtauto/controller/AdPlanController.java

@@ -0,0 +1,127 @@
+package com.moka.gdtauto.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.moka.gdtauto.common.Result;
+import com.moka.gdtauto.entity.AdPlan;
+import com.moka.gdtauto.service.AdPlanService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 广告计划管理控制器
+ * 
+ * @author moka
+ * @since 2026-02-24
+ */
+@Tag(name = "广告计划管理", description = "广告计划的创建、更新、删除及查询")
+@Slf4j
+@RestController
+@RequestMapping("/api/ad-plan")
+@RequiredArgsConstructor
+public class AdPlanController {
+
+    private final AdPlanService adPlanService;
+
+    /**
+     * 创建广告计划
+     */
+    @Operation(summary = "创建广告计划", description = "创建新的广告计划")
+    @PostMapping("/create")
+    public Result<Long> createAdPlan(@RequestBody AdPlan adPlan) {
+        try {
+            Long planId = adPlanService.createAdPlan(adPlan);
+            return Result.success(planId, "创建广告计划成功");
+        } catch (Exception e) {
+            log.error("创建广告计划失败", e);
+            return Result.fail("创建广告计划失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 更新广告计划
+     */
+    @Operation(summary = "更新广告计划", description = "更新指定的广告计划")
+    @PostMapping("/update")
+    public Result<Void> updateAdPlan(@RequestBody AdPlan adPlan) {
+        try {
+            boolean success = adPlanService.updateAdPlan(adPlan);
+            if (success) {
+                return Result.success("更新广告计划成功");
+            } else {
+                return Result.fail("广告计划不存在");
+            }
+        } catch (Exception e) {
+            log.error("更新广告计划失败", e);
+            return Result.fail("更新广告计划失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 删除广告计划
+     */
+    @Operation(summary = "删除广告计划", description = "删除指定的广告计划")
+    @DeleteMapping("/{planId}")
+    public Result<Void> deleteAdPlan(
+            @Parameter(description = "计划ID", required = true)
+            @PathVariable Long planId) {
+        try {
+            boolean success = adPlanService.deleteAdPlan(planId);
+            if (success) {
+                return Result.success("删除广告计划成功");
+            } else {
+                return Result.fail("广告计划不存在");
+            }
+        } catch (Exception e) {
+            log.error("删除广告计划失败", e);
+            return Result.fail("删除广告计划失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取广告计划列表(分页)
+     */
+    @Operation(summary = "获取广告计划列表", description = "分页获取广告计划列表,可按账户ID和状态筛选")
+    @GetMapping("/list")
+    public Result<Page<AdPlan>> getAdPlans(
+            @Parameter(description = "账户ID,可选", example = "123456")
+            @RequestParam(required = false) Long accountId,
+            @Parameter(description = "状态,可选:1-待执行,2-执行中,3-失败,4-已结束", example = "1")
+            @RequestParam(required = false) Integer status,
+            @Parameter(description = "页码", example = "1")
+            @RequestParam(defaultValue = "1") Integer pageNum,
+            @Parameter(description = "每页大小", example = "10")
+            @RequestParam(defaultValue = "10") Integer pageSize) {
+        try {
+            Page<AdPlan> page = adPlanService.getAdPlans(accountId, status, pageNum, pageSize);
+            return Result.success(page, "查询成功");
+        } catch (Exception e) {
+            log.error("查询广告计划列表失败", e);
+            return Result.fail("查询广告计划列表失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取广告计划详情
+     */
+    @Operation(summary = "获取广告计划详情", description = "根据ID获取广告计划详细信息")
+    @GetMapping("/{planId}")
+    public Result<AdPlan> getAdPlan(
+            @Parameter(description = "计划ID", required = true)
+            @PathVariable Long planId) {
+        try {
+            AdPlan adPlan = adPlanService.getAdPlanById(planId);
+            if (adPlan != null) {
+                return Result.success(adPlan, "查询成功");
+            } else {
+                return Result.fail("广告计划不存在");
+            }
+        } catch (Exception e) {
+            log.error("查询广告计划详情失败", e);
+            return Result.fail("查询广告计划详情失败: " + e.getMessage());
+        }
+    }
+}

+ 166 - 0
src/main/java/com/moka/gdtauto/controller/AdPlanCreativeController.java

@@ -0,0 +1,166 @@
+package com.moka.gdtauto.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.moka.gdtauto.common.Result;
+import com.moka.gdtauto.entity.AdPlanCreative;
+import com.moka.gdtauto.service.AdPlanCreativeService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 广告计划创意管理控制器
+ * 
+ * @author moka
+ * @since 2026-02-24
+ */
+@Tag(name = "广告计划创意管理", description = "广告计划创意的创建、更新、删除及查询")
+@Slf4j
+@RestController
+@RequestMapping("/api/ad-plan-creative")
+@RequiredArgsConstructor
+public class AdPlanCreativeController {
+
+    private final AdPlanCreativeService adPlanCreativeService;
+
+    /**
+     * 创建广告计划创意
+     */
+    @Operation(summary = "创建广告计划创意", description = "创建新的广告计划创意")
+    @PostMapping("/create")
+    public Result<Long> createAdPlanCreative(@RequestBody AdPlanCreative adPlanCreative) {
+        try {
+            Long creativeId = adPlanCreativeService.createAdPlanCreative(adPlanCreative);
+            return Result.success(creativeId, "创建广告计划创意成功");
+        } catch (Exception e) {
+            log.error("创建广告计划创意失败", e);
+            return Result.fail("创建广告计划创意失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 批量创建广告计划创意
+     */
+    @Operation(summary = "批量创建广告计划创意", description = "批量创建多个广告计划创意")
+    @PostMapping("/batch-create")
+    public Result<Void> batchCreateAdPlanCreatives(@RequestBody List<AdPlanCreative> adPlanCreatives) {
+        try {
+            boolean success = adPlanCreativeService.batchCreateAdPlanCreatives(adPlanCreatives);
+            if (success) {
+                return Result.success("批量创建广告计划创意成功");
+            } else {
+                return Result.fail("批量创建广告计划创意失败");
+            }
+        } catch (Exception e) {
+            log.error("批量创建广告计划创意失败", e);
+            return Result.fail("批量创建广告计划创意失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 更新广告计划创意
+     */
+    @Operation(summary = "更新广告计划创意", description = "更新指定的广告计划创意")
+    @PostMapping("/update")
+    public Result<Void> updateAdPlanCreative(@RequestBody AdPlanCreative adPlanCreative) {
+        try {
+            boolean success = adPlanCreativeService.updateAdPlanCreative(adPlanCreative);
+            if (success) {
+                return Result.success("更新广告计划创意成功");
+            } else {
+                return Result.fail("广告计划创意不存在");
+            }
+        } catch (Exception e) {
+            log.error("更新广告计划创意失败", e);
+            return Result.fail("更新广告计划创意失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 删除广告计划创意
+     */
+    @Operation(summary = "删除广告计划创意", description = "删除指定的广告计划创意")
+    @DeleteMapping("/{creativeId}")
+    public Result<Void> deleteAdPlanCreative(
+            @Parameter(description = "创意ID", required = true)
+            @PathVariable Long creativeId) {
+        try {
+            boolean success = adPlanCreativeService.deleteAdPlanCreative(creativeId);
+            if (success) {
+                return Result.success("删除广告计划创意成功");
+            } else {
+                return Result.fail("广告计划创意不存在");
+            }
+        } catch (Exception e) {
+            log.error("删除广告计划创意失败", e);
+            return Result.fail("删除广告计划创意失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 根据计划ID获取创意列表
+     */
+    @Operation(summary = "根据计划ID获取创意列表", description = "获取指定广告计划的所有创意")
+    @GetMapping("/plan/{planId}")
+    public Result<List<AdPlanCreative>> getCreativesByPlanId(
+            @Parameter(description = "计划ID", required = true)
+            @PathVariable Long planId) {
+        try {
+            List<AdPlanCreative> creatives = adPlanCreativeService.getCreativesByPlanId(planId);
+            return Result.success(creatives, "查询成功");
+        } catch (Exception e) {
+            log.error("查询广告计划创意列表失败", e);
+            return Result.fail("查询广告计划创意列表失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取广告计划创意列表(分页)
+     */
+    @Operation(summary = "获取广告计划创意列表", description = "分页获取广告计划创意列表,可按计划ID和状态筛选")
+    @GetMapping("/list")
+    public Result<Page<AdPlanCreative>> getAdPlanCreatives(
+            @Parameter(description = "计划ID,可选", example = "1")
+            @RequestParam(required = false) Long planId,
+            @Parameter(description = "状态,可选:1-待执行,2-执行中,3-失败,4-已结束", example = "1")
+            @RequestParam(required = false) Integer status,
+            @Parameter(description = "页码", example = "1")
+            @RequestParam(defaultValue = "1") Integer pageNum,
+            @Parameter(description = "每页大小", example = "10")
+            @RequestParam(defaultValue = "10") Integer pageSize) {
+        try {
+            Page<AdPlanCreative> page = adPlanCreativeService.getAdPlanCreatives(
+                    planId, status, pageNum, pageSize);
+            return Result.success(page, "查询成功");
+        } catch (Exception e) {
+            log.error("查询广告计划创意列表失败", e);
+            return Result.fail("查询广告计划创意列表失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取广告计划创意详情
+     */
+    @Operation(summary = "获取广告计划创意详情", description = "根据ID获取广告计划创意详细信息")
+    @GetMapping("/{creativeId}")
+    public Result<AdPlanCreative> getAdPlanCreative(
+            @Parameter(description = "创意ID", required = true)
+            @PathVariable Long creativeId) {
+        try {
+            AdPlanCreative adPlanCreative = adPlanCreativeService.getAdPlanCreativeById(creativeId);
+            if (adPlanCreative != null) {
+                return Result.success(adPlanCreative, "查询成功");
+            } else {
+                return Result.fail("广告计划创意不存在");
+            }
+        } catch (Exception e) {
+            log.error("查询广告计划创意详情失败", e);
+            return Result.fail("查询广告计划创意详情失败: " + e.getMessage());
+        }
+    }
+}

+ 15 - 0
src/main/java/com/moka/gdtauto/controller/TencentConversionController.java

@@ -128,4 +128,19 @@ public class TencentConversionController {
             return Result.fail("查询失败:" + e.getMessage());
         }
     }
+
+    @Operation(
+        summary = "查询所有转化归因",
+        description = "查询所有已同步的转化归因信息"
+    )
+    @GetMapping("/all")
+    public Result<List<TencentConversion>> getAllConversions() {
+        try {
+            List<TencentConversion> conversions = tencentConversionService.list();
+            return Result.success(conversions, "查询成功");
+        } catch (Exception e) {
+            log.error("查询所有转化归因失败", e);
+            return Result.fail("查询失败:" + e.getMessage());
+        }
+    }
 }

+ 51 - 0
src/main/java/com/moka/gdtauto/controller/vo/CreateAdPlanConfRequest.java

@@ -0,0 +1,51 @@
+package com.moka.gdtauto.controller.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * 创建广告计划配置请求VO
+ * 
+ * @author moka
+ * @since 2026-02-24
+ */
+@Data
+@Schema(description = "创建广告计划配置请求")
+public class CreateAdPlanConfRequest {
+
+    @Schema(description = "计划名称", example = "测试计划")
+    private String name;
+
+    @Schema(description = "账号组ID", example = "1")
+    private Long accountGroupId;
+
+    @Schema(description = "广告类型:1-企微,2-ROI", example = "1")
+    private Integer adType;
+
+    @Schema(description = "出价金额(分)", example = "10000")
+    private Long bidAmount;
+
+    @Schema(description = "转化ID", example = "123456")
+    private Long conversionId;
+
+    @Schema(description = "定向", example = "1,2,3")
+    private String targets;
+
+    @Schema(description = "版位", example = "1,2,3")
+    private String sites;
+
+    @Schema(description = "企微主体ID", example = "wx123456")
+    private String wxCorpid;
+
+    @Schema(description = "短剧ID", example = "1")
+    private Long dramaId;
+
+    @Schema(description = "图片IDs,多个用逗号分隔", example = "1,2,3")
+    private String imageIds;
+
+    @Schema(description = "落地页IDs,多个用逗号分隔", example = "1,2,3")
+    private String pageIds;
+
+    @Schema(description = "工号", example = "A001")
+    private String jobNumber;
+}

+ 48 - 0
src/main/java/com/moka/gdtauto/controller/vo/UpdateAdPlanConfRequest.java

@@ -0,0 +1,48 @@
+package com.moka.gdtauto.controller.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * 更新广告计划配置请求VO
+ * 
+ * @author moka
+ * @since 2026-02-24
+ */
+@Data
+@Schema(description = "更新广告计划配置请求")
+public class UpdateAdPlanConfRequest {
+
+    @Schema(description = "配置ID", example = "1")
+    private Long id;
+
+    @Schema(description = "计划名称", example = "测试计划")
+    private String name;
+
+    @Schema(description = "账号组ID", example = "1")
+    private Long accountGroupId;
+
+    @Schema(description = "广告类型:1-企微,2-ROI", example = "1")
+    private Integer adType;
+
+    @Schema(description = "出价金额(分)", example = "10000")
+    private Long bidAmount;
+
+    @Schema(description = "转化ID", example = "123456")
+    private Long conversionId;
+
+    @Schema(description = "企微主体ID", example = "wx123456")
+    private String wxCorpid;
+
+    @Schema(description = "短剧ID", example = "1")
+    private Long dramaId;
+
+    @Schema(description = "图片IDs,多个用逗号分隔", example = "1,2,3")
+    private String imageIds;
+
+    @Schema(description = "落地页IDs,多个用逗号分隔", example = "1,2,3")
+    private String pageIds;
+
+    @Schema(description = "工号", example = "A001")
+    private String jobNumber;
+}

+ 116 - 0
src/main/java/com/moka/gdtauto/entity/AdPlan.java

@@ -0,0 +1,116 @@
+package com.moka.gdtauto.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import java.util.List;
+import com.moka.gdtauto.entity.AdPlanCreative;
+
+import java.time.LocalDateTime;
+
+/**
+ * 广告计划实体
+ * 
+ * @author moka
+ * @since 2026-02-24
+ */
+@Data
+@TableName("ad_plan")
+public class AdPlan {
+
+    /**
+     * 主键ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 账户ID
+     */
+    private Long accountId;
+
+    /**
+     * 广告组ID
+     */
+    private Long adgroupId;
+
+    /**
+     * 广告组名称60个中文
+     */
+    private String adgroupName;
+
+    /**
+     * 广告类型:1-企微,2-ROI
+     */
+    private Integer adType;
+
+    /**
+     * 出价金额(分)
+     */
+    private Long bidAmount;
+
+    /**
+     * 转化ID
+     */
+    private Long conversionId;
+
+    /**
+     *定向
+     */
+    private String target;
+    /**
+     *版位
+     */
+    private String site;
+
+    /**
+     * 企微主体ID
+     */
+    private String wxCorpid;
+
+    /**
+     * 短剧ID
+     */
+    private Long dramaId;
+
+    /**
+     * 图片ID
+     */
+    private Long imageId;
+
+    @TableField(exist = false)
+    private List<AdPlanCreative> adCreatives;
+
+    /**
+     * 计划状态:1-待执行,2-执行中,3-失败,4-已结束
+     */
+    private Integer status;
+
+    /**
+     * 失败原因
+     */
+    private String failReason;
+
+    /**
+     * 成功时间
+     */
+    private LocalDateTime successTime;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 逻辑删除:0-未删除,1-已删除
+     */
+    private Integer deleted;
+}

+ 95 - 0
src/main/java/com/moka/gdtauto/entity/AdPlanConf.java

@@ -0,0 +1,95 @@
+package com.moka.gdtauto.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 广告计划配置实体
+ * 
+ * @author moka
+ * @since 2026-02-24
+ */
+@Data
+@TableName("ad_plan_conf")
+public class AdPlanConf {
+
+    /**
+     * 主键ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 计划名称
+     */
+    private String name;
+
+    /**
+     * 账号组ID
+     */
+    private Long accountGroupId;
+
+    /**
+     * 广告类型:1-企微,2-ROI
+     */
+    private Integer adType;
+
+    /**
+     * 出价金额(分)
+     */
+    private Long bidAmount;
+
+    /**
+     * 转化ID
+     */
+    private Long conversionId;
+
+    /**
+     * 企微主体ID
+     */
+    private String wxCorpid;
+
+    /**
+     * 短剧ID
+     */
+    private Long dramaId;
+
+    /**
+     * 图片IDs
+     */
+    private String imageIds;
+
+    /**
+     * 落地页IDs
+     */
+    private String pageIds;
+
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 工号
+     */
+    private String jobNumber;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 逻辑删除:0-未删除,1-已删除
+     */
+    private Integer deleted;
+}

+ 90 - 0
src/main/java/com/moka/gdtauto/entity/AdPlanCreative.java

@@ -0,0 +1,90 @@
+package com.moka.gdtauto.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 广告计划创意实体
+ * 
+ * @author moka
+ * @since 2026-02-24
+ */
+@Data
+@TableName("ad_plan_creative")
+public class AdPlanCreative {
+
+    /**
+     * 主键ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 计划ID
+     */
+    private Long adPlanId;
+
+    /**
+     * 账户ID
+     */
+    private Long accountId;
+
+    /**
+     * 广告组ID
+     */
+    private Long adgroupId;
+
+    /**
+     * 创意ID
+     */
+    private Long creativeId;
+
+    /**
+     * 创意名称60个中文字
+     */
+    private String creativeName;
+
+    /**
+     * 图片ID
+     */
+    private Long imageId;
+
+    /**
+     * 落地页ID
+     */
+    private Long pageId;
+
+    /**
+     * 计划状态:1-待执行,2-执行中,3-失败,4-已结束
+     */
+    private Integer status;
+
+    /**
+     * 失败原因
+     */
+    private String failReason;
+
+    /**
+     * 成功时间
+     */
+    private LocalDateTime successTime;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 逻辑删除:0-未删除,1-已删除
+     */
+    private Integer deleted;
+}

+ 15 - 0
src/main/java/com/moka/gdtauto/mapper/AdPlanConfMapper.java

@@ -0,0 +1,15 @@
+package com.moka.gdtauto.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.moka.gdtauto.entity.AdPlanConf;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 广告计划配置Mapper接口
+ * 
+ * @author moka
+ * @since 2026-02-24
+ */
+@Mapper
+public interface AdPlanConfMapper extends BaseMapper<AdPlanConf> {
+}

+ 15 - 0
src/main/java/com/moka/gdtauto/mapper/AdPlanCreativeMapper.java

@@ -0,0 +1,15 @@
+package com.moka.gdtauto.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.moka.gdtauto.entity.AdPlanCreative;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 广告计划创意Mapper接口
+ * 
+ * @author moka
+ * @since 2026-02-24
+ */
+@Mapper
+public interface AdPlanCreativeMapper extends BaseMapper<AdPlanCreative> {
+}

+ 15 - 0
src/main/java/com/moka/gdtauto/mapper/AdPlanMapper.java

@@ -0,0 +1,15 @@
+package com.moka.gdtauto.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.moka.gdtauto.entity.AdPlan;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 广告计划Mapper接口
+ * 
+ * @author moka
+ * @since 2026-02-24
+ */
+@Mapper
+public interface AdPlanMapper extends BaseMapper<AdPlan> {
+}

+ 58 - 0
src/main/java/com/moka/gdtauto/service/AdPlanConfService.java

@@ -0,0 +1,58 @@
+package com.moka.gdtauto.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.moka.gdtauto.entity.AdPlanConf;
+
+/**
+ * 广告计划配置服务接口
+ * 
+ * @author moka
+ * @since 2026-02-24
+ */
+public interface AdPlanConfService extends IService<AdPlanConf> {
+
+    /**
+     * 创建广告计划配置
+     * 
+     * @param adPlanConf 广告计划配置
+     * @return 配置ID
+     */
+    Long createAdPlanConf(AdPlanConf adPlanConf);
+
+    /**
+     * 更新广告计划配置
+     * 
+     * @param adPlanConf 广告计划配置
+     * @return 是否成功
+     */
+    boolean updateAdPlanConf(AdPlanConf adPlanConf);
+
+    /**
+     * 删除广告计划配置
+     * 
+     * @param confId 配置ID
+     * @param userId 用户ID
+     * @return 是否成功
+     */
+    boolean deleteAdPlanConf(Long confId, Long userId);
+
+    /**
+     * 获取用户的广告计划配置列表(分页)
+     * 
+     * @param userId 用户ID
+     * @param pageNum 页码
+     * @param pageSize 每页大小
+     * @return 配置分页列表
+     */
+    Page<AdPlanConf> getUserAdPlanConfs(Long userId, Integer pageNum, Integer pageSize);
+
+    /**
+     * 获取广告计划配置详情
+     * 
+     * @param confId 配置ID
+     * @param userId 用户ID
+     * @return 配置信息
+     */
+    AdPlanConf getAdPlanConfById(Long confId, Long userId);
+}

+ 75 - 0
src/main/java/com/moka/gdtauto/service/AdPlanCreativeService.java

@@ -0,0 +1,75 @@
+package com.moka.gdtauto.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.moka.gdtauto.entity.AdPlanCreative;
+
+import java.util.List;
+
+/**
+ * 广告计划创意服务接口
+ * 
+ * @author moka
+ * @since 2026-02-24
+ */
+public interface AdPlanCreativeService extends IService<AdPlanCreative> {
+
+    /**
+     * 创建广告计划创意
+     * 
+     * @param adPlanCreative 广告计划创意
+     * @return 创意ID
+     */
+    Long createAdPlanCreative(AdPlanCreative adPlanCreative);
+
+    /**
+     * 批量创建广告计划创意
+     * 
+     * @param adPlanCreatives 广告计划创意列表
+     * @return 是否成功
+     */
+    boolean batchCreateAdPlanCreatives(List<AdPlanCreative> adPlanCreatives);
+
+    /**
+     * 更新广告计划创意
+     * 
+     * @param adPlanCreative 广告计划创意
+     * @return 是否成功
+     */
+    boolean updateAdPlanCreative(AdPlanCreative adPlanCreative);
+
+    /**
+     * 删除广告计划创意
+     * 
+     * @param creativeId 创意ID
+     * @return 是否成功
+     */
+    boolean deleteAdPlanCreative(Long creativeId);
+
+    /**
+     * 根据计划ID获取创意列表
+     * 
+     * @param planId 计划ID
+     * @return 创意列表
+     */
+    List<AdPlanCreative> getCreativesByPlanId(Long planId);
+
+    /**
+     * 获取广告计划创意列表(分页)
+     * 
+     * @param planId 计划ID(可选)
+     * @param status 状态(可选)
+     * @param pageNum 页码
+     * @param pageSize 每页大小
+     * @return 创意分页列表
+     */
+    Page<AdPlanCreative> getAdPlanCreatives(Long planId, Integer status, Integer pageNum, Integer pageSize);
+
+    /**
+     * 获取广告计划创意详情
+     * 
+     * @param creativeId 创意ID
+     * @return 创意信息
+     */
+    AdPlanCreative getAdPlanCreativeById(Long creativeId);
+}

+ 57 - 0
src/main/java/com/moka/gdtauto/service/AdPlanService.java

@@ -0,0 +1,57 @@
+package com.moka.gdtauto.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.moka.gdtauto.entity.AdPlan;
+
+/**
+ * 广告计划服务接口
+ * 
+ * @author moka
+ * @since 2026-02-24
+ */
+public interface AdPlanService extends IService<AdPlan> {
+
+    /**
+     * 创建广告计划
+     * 
+     * @param adPlan 广告计划
+     * @return 计划ID
+     */
+    Long createAdPlan(AdPlan adPlan);
+
+    /**
+     * 更新广告计划
+     * 
+     * @param adPlan 广告计划
+     * @return 是否成功
+     */
+    boolean updateAdPlan(AdPlan adPlan);
+
+    /**
+     * 删除广告计划
+     * 
+     * @param planId 计划ID
+     * @return 是否成功
+     */
+    boolean deleteAdPlan(Long planId);
+
+    /**
+     * 获取广告计划列表(分页)
+     * 
+     * @param accountId 账户ID(可选)
+     * @param status 状态(可选)
+     * @param pageNum 页码
+     * @param pageSize 每页大小
+     * @return 计划分页列表
+     */
+    Page<AdPlan> getAdPlans(Long accountId, Integer status, Integer pageNum, Integer pageSize);
+
+    /**
+     * 获取广告计划详情
+     * 
+     * @param planId 计划ID
+     * @return 计划信息
+     */
+    AdPlan getAdPlanById(Long planId);
+}

+ 98 - 0
src/main/java/com/moka/gdtauto/service/impl/AdPlanConfServiceImpl.java

@@ -0,0 +1,98 @@
+package com.moka.gdtauto.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.moka.gdtauto.entity.AdPlanConf;
+import com.moka.gdtauto.mapper.AdPlanConfMapper;
+import com.moka.gdtauto.service.AdPlanConfService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+
+/**
+ * 广告计划配置服务实现类
+ * 
+ * @author moka
+ * @since 2026-02-24
+ */
+@Slf4j
+@Service
+public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanConf> implements AdPlanConfService {
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createAdPlanConf(AdPlanConf adPlanConf) {
+        adPlanConf.setCreateTime(LocalDateTime.now());
+        adPlanConf.setUpdateTime(LocalDateTime.now());
+        adPlanConf.setDeleted(0);
+        
+        this.save(adPlanConf);
+        log.info("创建广告计划配置成功,配置ID: {}, 配置名称: {}", adPlanConf.getId(), adPlanConf.getName());
+        
+        return adPlanConf.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean updateAdPlanConf(AdPlanConf adPlanConf) {
+        AdPlanConf existingConf = this.getOne(new LambdaQueryWrapper<AdPlanConf>()
+                .eq(AdPlanConf::getId, adPlanConf.getId())
+                .eq(AdPlanConf::getUserId, adPlanConf.getUserId())
+                .eq(AdPlanConf::getDeleted, 0));
+        
+        if (existingConf == null) {
+            log.warn("广告计划配置不存在或无权限,配置ID: {}, 用户ID: {}", adPlanConf.getId(), adPlanConf.getUserId());
+            return false;
+        }
+        
+        adPlanConf.setUpdateTime(LocalDateTime.now());
+        boolean result = this.updateById(adPlanConf);
+        log.info("更新广告计划配置成功,配置ID: {}", adPlanConf.getId());
+        
+        return result;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean deleteAdPlanConf(Long confId, Long userId) {
+        AdPlanConf existingConf = this.getOne(new LambdaQueryWrapper<AdPlanConf>()
+                .eq(AdPlanConf::getId, confId)
+                .eq(AdPlanConf::getUserId, userId)
+                .eq(AdPlanConf::getDeleted, 0));
+        
+        if (existingConf == null) {
+            log.warn("广告计划配置不存在或无权限,配置ID: {}, 用户ID: {}", confId, userId);
+            return false;
+        }
+        
+        existingConf.setDeleted(1);
+        existingConf.setUpdateTime(LocalDateTime.now());
+        boolean result = this.updateById(existingConf);
+        
+        log.info("删除广告计划配置成功,配置ID: {}", confId);
+        return result;
+    }
+
+    @Override
+    public Page<AdPlanConf> getUserAdPlanConfs(Long userId, Integer pageNum, Integer pageSize) {
+        Page<AdPlanConf> page = new Page<>(pageNum, pageSize);
+        
+        LambdaQueryWrapper<AdPlanConf> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(AdPlanConf::getUserId, userId)
+                .eq(AdPlanConf::getDeleted, 0)
+                .orderByDesc(AdPlanConf::getCreateTime);
+        
+        return this.page(page, queryWrapper);
+    }
+
+    @Override
+    public AdPlanConf getAdPlanConfById(Long confId, Long userId) {
+        return this.getOne(new LambdaQueryWrapper<AdPlanConf>()
+                .eq(AdPlanConf::getId, confId)
+                .eq(AdPlanConf::getUserId, userId)
+                .eq(AdPlanConf::getDeleted, 0));
+    }
+}

+ 134 - 0
src/main/java/com/moka/gdtauto/service/impl/AdPlanCreativeServiceImpl.java

@@ -0,0 +1,134 @@
+package com.moka.gdtauto.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.moka.gdtauto.entity.AdPlanCreative;
+import com.moka.gdtauto.mapper.AdPlanCreativeMapper;
+import com.moka.gdtauto.service.AdPlanCreativeService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 广告计划创意服务实现类
+ * 
+ * @author moka
+ * @since 2026-02-24
+ */
+@Slf4j
+@Service
+public class AdPlanCreativeServiceImpl extends ServiceImpl<AdPlanCreativeMapper, AdPlanCreative> implements AdPlanCreativeService {
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createAdPlanCreative(AdPlanCreative adPlanCreative) {
+        adPlanCreative.setCreateTime(LocalDateTime.now());
+        adPlanCreative.setUpdateTime(LocalDateTime.now());
+        adPlanCreative.setDeleted(0);
+        if (adPlanCreative.getStatus() == null) {
+            adPlanCreative.setStatus(1); // 默认待执行
+        }
+        
+        this.save(adPlanCreative);
+        log.info("创建广告计划创意成功,创意ID: {}, 创意名称: {}", adPlanCreative.getId(), adPlanCreative.getCreativeName());
+        
+        return adPlanCreative.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean batchCreateAdPlanCreatives(List<AdPlanCreative> adPlanCreatives) {
+        if (adPlanCreatives == null || adPlanCreatives.isEmpty()) {
+            log.warn("批量创建广告计划创意失败,创意列表为空");
+            return false;
+        }
+        
+        LocalDateTime now = LocalDateTime.now();
+        for (AdPlanCreative creative : adPlanCreatives) {
+            creative.setCreateTime(now);
+            creative.setUpdateTime(now);
+            creative.setDeleted(0);
+            if (creative.getStatus() == null) {
+                creative.setStatus(1); // 默认待执行
+            }
+        }
+        
+        boolean result = this.saveBatch(adPlanCreatives);
+        log.info("批量创建广告计划创意成功,数量: {}", adPlanCreatives.size());
+        
+        return result;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean updateAdPlanCreative(AdPlanCreative adPlanCreative) {
+        AdPlanCreative existingCreative = this.getById(adPlanCreative.getId());
+        
+        if (existingCreative == null || existingCreative.getDeleted() == 1) {
+            log.warn("广告计划创意不存在,创意ID: {}", adPlanCreative.getId());
+            return false;
+        }
+        
+        adPlanCreative.setUpdateTime(LocalDateTime.now());
+        boolean result = this.updateById(adPlanCreative);
+        log.info("更新广告计划创意成功,创意ID: {}", adPlanCreative.getId());
+        
+        return result;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean deleteAdPlanCreative(Long creativeId) {
+        AdPlanCreative existingCreative = this.getById(creativeId);
+        
+        if (existingCreative == null || existingCreative.getDeleted() == 1) {
+            log.warn("广告计划创意不存在,创意ID: {}", creativeId);
+            return false;
+        }
+        
+        existingCreative.setDeleted(1);
+        existingCreative.setUpdateTime(LocalDateTime.now());
+        boolean result = this.updateById(existingCreative);
+        
+        log.info("删除广告计划创意成功,创意ID: {}", creativeId);
+        return result;
+    }
+
+    @Override
+    public List<AdPlanCreative> getCreativesByPlanId(Long planId) {
+        return this.list(new LambdaQueryWrapper<AdPlanCreative>()
+                .eq(AdPlanCreative::getAdPlanId, planId)
+                .eq(AdPlanCreative::getDeleted, 0)
+                .orderByDesc(AdPlanCreative::getCreateTime));
+    }
+
+    @Override
+    public Page<AdPlanCreative> getAdPlanCreatives(Long planId, Integer status, Integer pageNum, Integer pageSize) {
+        Page<AdPlanCreative> page = new Page<>(pageNum, pageSize);
+        
+        LambdaQueryWrapper<AdPlanCreative> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(AdPlanCreative::getDeleted, 0);
+        
+        if (planId != null) {
+            queryWrapper.eq(AdPlanCreative::getAdPlanId, planId);
+        }
+        if (status != null) {
+            queryWrapper.eq(AdPlanCreative::getStatus, status);
+        }
+        
+        queryWrapper.orderByDesc(AdPlanCreative::getCreateTime);
+        
+        return this.page(page, queryWrapper);
+    }
+
+    @Override
+    public AdPlanCreative getAdPlanCreativeById(Long creativeId) {
+        return this.getOne(new LambdaQueryWrapper<AdPlanCreative>()
+                .eq(AdPlanCreative::getId, creativeId)
+                .eq(AdPlanCreative::getDeleted, 0));
+    }
+}

+ 101 - 0
src/main/java/com/moka/gdtauto/service/impl/AdPlanServiceImpl.java

@@ -0,0 +1,101 @@
+package com.moka.gdtauto.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.moka.gdtauto.entity.AdPlan;
+import com.moka.gdtauto.mapper.AdPlanMapper;
+import com.moka.gdtauto.service.AdPlanService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+
+/**
+ * 广告计划服务实现类
+ * 
+ * @author moka
+ * @since 2026-02-24
+ */
+@Slf4j
+@Service
+public class AdPlanServiceImpl extends ServiceImpl<AdPlanMapper, AdPlan> implements AdPlanService {
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createAdPlan(AdPlan adPlan) {
+        adPlan.setCreateTime(LocalDateTime.now());
+        adPlan.setUpdateTime(LocalDateTime.now());
+        adPlan.setDeleted(0);
+        if (adPlan.getStatus() == null) {
+            adPlan.setStatus(1); // 默认待执行
+        }
+        
+        this.save(adPlan);
+        log.info("创建广告计划成功,计划ID: {}, 广告组名称: {}", adPlan.getId(), adPlan.getAdgroupName());
+        
+        return adPlan.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean updateAdPlan(AdPlan adPlan) {
+        AdPlan existingPlan = this.getById(adPlan.getId());
+        
+        if (existingPlan == null || existingPlan.getDeleted() == 1) {
+            log.warn("广告计划不存在,计划ID: {}", adPlan.getId());
+            return false;
+        }
+        
+        adPlan.setUpdateTime(LocalDateTime.now());
+        boolean result = this.updateById(adPlan);
+        log.info("更新广告计划成功,计划ID: {}", adPlan.getId());
+        
+        return result;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean deleteAdPlan(Long planId) {
+        AdPlan existingPlan = this.getById(planId);
+        
+        if (existingPlan == null || existingPlan.getDeleted() == 1) {
+            log.warn("广告计划不存在,计划ID: {}", planId);
+            return false;
+        }
+        
+        existingPlan.setDeleted(1);
+        existingPlan.setUpdateTime(LocalDateTime.now());
+        boolean result = this.updateById(existingPlan);
+        
+        log.info("删除广告计划成功,计划ID: {}", planId);
+        return result;
+    }
+
+    @Override
+    public Page<AdPlan> getAdPlans(Long accountId, Integer status, Integer pageNum, Integer pageSize) {
+        Page<AdPlan> page = new Page<>(pageNum, pageSize);
+        
+        LambdaQueryWrapper<AdPlan> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(AdPlan::getDeleted, 0);
+        
+        if (accountId != null) {
+            queryWrapper.eq(AdPlan::getAccountId, accountId);
+        }
+        if (status != null) {
+            queryWrapper.eq(AdPlan::getStatus, status);
+        }
+        
+        queryWrapper.orderByDesc(AdPlan::getCreateTime);
+        
+        return this.page(page, queryWrapper);
+    }
+
+    @Override
+    public AdPlan getAdPlanById(Long planId) {
+        return this.getOne(new LambdaQueryWrapper<AdPlan>()
+                .eq(AdPlan::getId, planId)
+                .eq(AdPlan::getDeleted, 0));
+    }
+}