pudongliang 4 месяцев назад
Родитель
Сommit
7f1e0c95b1
31 измененных файлов с 321 добавлено и 78 удалено
  1. 24 2
      portal/src/layout/index.vue
  2. 2 2
      portal/src/router/index.js
  3. 4 0
      portal/src/views/account-group/index.vue
  4. 4 0
      portal/src/views/account/index.vue
  5. 86 37
      portal/src/views/ad-auto-conf/edit.vue
  6. 13 0
      portal/src/views/ad-auto-conf/index.vue
  7. 4 0
      portal/src/views/ad-plan-conf/detail.vue
  8. 4 0
      portal/src/views/ad-plan-conf/index.vue
  9. 4 0
      portal/src/views/adgroup/index.vue
  10. 4 0
      portal/src/views/auth/index.vue
  11. 4 0
      portal/src/views/conversion/index.vue
  12. 4 0
      portal/src/views/daily-report/index.vue
  13. 4 0
      portal/src/views/dashboard/index.vue
  14. 4 0
      portal/src/views/drama/index.vue
  15. 4 0
      portal/src/views/fiction/index.vue
  16. 4 0
      portal/src/views/image/index.vue
  17. 4 0
      portal/src/views/load-page-conf/index.vue
  18. 4 0
      portal/src/views/login/index.vue
  19. 4 0
      portal/src/views/oss-file/index.vue
  20. 4 0
      portal/src/views/profile/index.vue
  21. 4 0
      portal/src/views/user/index.vue
  22. 4 0
      portal/src/views/video/index.vue
  23. 6 0
      src/main/java/com/moka/gdtauto/entity/AdAutoConf.java
  24. 2 0
      src/main/java/com/moka/gdtauto/entity/AdPlan.java
  25. 39 3
      src/main/java/com/moka/gdtauto/service/AdPlanAutoExecutor.java
  26. 4 0
      src/main/java/com/moka/gdtauto/service/AdPlanService.java
  27. 17 25
      src/main/java/com/moka/gdtauto/service/impl/AdPlanConfServiceImpl.java
  28. 29 0
      src/main/java/com/moka/gdtauto/service/impl/AdPlanServiceImpl.java
  29. 9 9
      src/main/java/com/moka/gdtauto/task/AdAutoTask.java
  30. 10 0
      src/main/java/com/moka/gdtauto/util/StrUtil.java
  31. 8 0
      src/test/java/com/moka/gdtauto/util/SampleTest.java

+ 24 - 2
portal/src/layout/index.vue

@@ -88,8 +88,11 @@
       <div class="flex-1 overflow-y-auto bg-gray-50 p-6">
         <div class="max-w-9xl">
           <router-view v-slot="{ Component, route }">
-            <keep-alive>
-              <component :is="Component" :key="route.fullPath" />
+            <keep-alive :exclude="noCacheComponents">
+              <component
+                :is="Component"
+                :key="route.meta.noCache ? route.fullPath : route.name"
+              />
             </keep-alive>
           </router-view>
         </div>
@@ -162,6 +165,25 @@ const currentTitle = computed(() => {
   return route.meta.title || '腾讯广告自动化管理后台'
 })
 
+// 获取不需要缓存的组件名列表
+const noCacheComponents = computed(() => {
+  const routes = router.getRoutes()
+  const noCacheNames = []
+  routes.forEach(r => {
+    if (r.meta && r.meta.noCache && r.name) {
+      noCacheNames.push(r.name)
+    }
+    if (r.children) {
+      r.children.forEach(child => {
+        if (child.meta && child.meta.noCache && child.name) {
+          noCacheNames.push(child.name)
+        }
+      })
+    }
+  })
+  return noCacheNames
+})
+
 // 跳转到个人中心
 const goToProfile = () => {
   router.push('/profile')

+ 2 - 2
portal/src/router/index.js

@@ -45,13 +45,13 @@ const routes = [
         path: 'ad-auto-conf',
         name: 'AdAutoConf',
         component: () => import('@/views/ad-auto-conf/index.vue'),
-        meta: { title: '广告自动划配置', icon: 'Setting' }
+        meta: { title: '广告自动划配置', icon: 'Setting', noCache: true }
       },
       {
         path: 'ad-auto-conf/edit',
         name: 'AdAutoConfEdit',
         component: () => import('@/views/ad-auto-conf/edit.vue'),
-        meta: { title: '编辑广告自动划配置', icon: 'Setting', hideInMenu: true }
+        meta: { title: '编辑广告自动划配置', icon: 'Setting', hideInMenu: true, noCache: true }
       },
       {
         path: 'oss-file',

+ 4 - 0
portal/src/views/account-group/index.vue

@@ -234,6 +234,10 @@
 </template>
 
 <script setup>
+defineOptions({
+  name: 'AccountGroup'
+})
+
 import { ref, computed, onMounted, watch } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import {

+ 4 - 0
portal/src/views/account/index.vue

@@ -200,6 +200,10 @@
 </template>
 
 <script setup>
+defineOptions({
+  name: 'Account'
+})
+
 import { ref, computed, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { syncAccounts, getAccountList, getAllAccounts } from '@/api/account'

+ 86 - 37
portal/src/views/ad-auto-conf/edit.vue

@@ -4,14 +4,19 @@
       <div class="border-b border-gray-200 pb-4 mb-6">
         <div class="flex items-center ">
           <h2 class="text-xl font-semibold text-gray-800 flex items-center gap-2">
-            <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
-              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
-              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
+            <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-blue-500" fill="none" viewBox="0 0 24 24"
+              stroke="currentColor">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
+                d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
+                d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
             </svg>
             {{ isEdit ? '编辑配置' : '新增配置' }}
           </h2>
           <el-button @click="handleBack">
-            <el-icon><ArrowLeft /></el-icon>
+            <el-icon>
+              <ArrowLeft />
+            </el-icon>
             返回列表
           </el-button>
         </div>
@@ -22,73 +27,95 @@
           <el-input v-model="form.ruleName" placeholder="请输入规则名称" style="width: 50%" />
         </el-form-item>
         <el-form-item label="广告组" prop="adGroupId">
-          <el-select v-model="form.adGroupId" placeholder="请选择广告组" style="width: 50%"  filterable>
+          <el-select v-model="form.adGroupId" placeholder="请选择广告组" style="width: 50%" filterable>
             <el-option label="全部广告组" :value="0" />
-            <el-option
-              v-for="group in accountGroupList"
-              :key="group.id"
-              :label="`${group.groupName} (${group.id})`"
-              :value="group.id"
-            />
+            <el-option v-for="group in accountGroupList" :key="group.id" :label="`${group.groupName} (${group.id})`"
+              :value="group.id" />
           </el-select>
           <p class="text-xs text-gray-500 mt-1">选择"全部广告组"表示不考虑分组情况</p>
         </el-form-item>
         <el-form-item label="新广告天数" prop="newAdDays">
           <el-input-number v-model="form.newAdDays" :min="1" placeholder="请输入新广告天数定义" style="width: 50%" />
         </el-form-item>
-
+        <p class="text-xs text-red-500" style="padding-left :120px">回车录入预算登记</p>
+        <el-form-item label="日预算等级(元)" prop="dailyBudgetLevelArr">
+            <el-input-tag
+              v-model="form.dailyBudgetLevelArr"
+              placeholder="请输入(元)"
+              aria-label=""
+              style="width: 50%" 
+            />
+        </el-form-item>
+        
+        <el-form-item label="日预算升级占比" prop="dailyBudgetRateTop">
+          <el-input-number v-model="form.dailyBudgetRateTop" :precision="2" :min="0.1" :step="0.1" :max="1" placeholder="请输入日预算升级百分比" style="width: 50%" />
+        </el-form-item>
         <!-- 规则配置 -->
         <el-divider content-position="left">规则配置</el-divider>
         <p class="text-sm text-red-500">规则执行将按照最小成本从高到低排序,当最小成本为0时候,将不考虑成本,直接计算ROI或粉价</p>
-         <div class="rules-container">
+        <div class="rules-container">
           <div class="grid grid-cols-2 gap-3">
             <div v-for="(rule, index) in form.rules" :key="index" class="rule-item p-3 bg-gray-50 rounded-lg">
               <div class="flex items-center justify-between mb-1">
                 <span class="font-medium text-gray-700">【规则 {{ index + 1 }}】</span>
                 <div class="bg-blue-50 rounded text-xs text-blue-800">
-                    <template v-if="rule.type === 1">
-                    <span class="font-medium">{{ rule.newAd === 1 ? '新广告' : '老广告' }}:</span>{{ rule.minCost > 0 ? `消耗 > ${rule.minCost}元 且 ` : '' }} ROI &lt; {{ rule.minIor }} 时暂停
-                    </template>
-                    <template v-else>
-                    <span class="font-medium">{{ rule.newAd === 1 ? '新广告' : '老广告' }}:</span>{{ rule.minCost > 0 ? `消耗 > ${rule.minCost}元 且 ` : '' }}粉价 > {{ rule.scanMaxCost }}元 时暂停
-                    </template>
-                 </div>
-                 <el-button type="danger" size="small" link @click="removeRule(index)">
-                  <el-icon><Delete /></el-icon>
+                  <template v-if="rule.type === 1">
+                    <span class="font-medium">{{ rule.newAd === 1 ? '新广告' : '老广告' }}:</span>{{ rule.minCost > 0 ? `消耗 >
+                    ${rule.minCost}元 且 ` : '' }} ROI &lt; {{ rule.minIor }} 时暂停
+                  </template>
+                  <template v-else>
+                    <span class="font-medium">{{ rule.newAd === 1 ? '新广告' : '老广告' }}:</span>{{ rule.minCost > 0 ? `消耗 >
+                    ${rule.minCost}元 且 ` : '' }}粉价 > {{ rule.scanMaxCost }}元 时暂停
+                  </template>
+                </div>
+                <el-button type="danger" size="small" link @click="removeRule(index)">
+                  <el-icon>
+                    <Delete />
+                  </el-icon>
                   删除
                 </el-button>
               </div>
-              
+
               <!-- 广告类型 radio -->
-              <el-form-item style="margin-bottom:4px" label="广告" :prop="`rules.${index}.newAd`" :rules="{ required: true, message: '请选择', trigger: 'change' }" label-width="70px">
+              <el-form-item style="margin-bottom:4px" label="广告" :prop="`rules.${index}.newAd`"
+                :rules="{ required: true, message: '请选择', trigger: 'change' }" label-width="70px">
                 <el-radio-group v-model="rule.newAd" size="small">
                   <el-radio :label="1">新广告</el-radio>
                   <el-radio :label="2">老广告</el-radio>
                 </el-radio-group>
               </el-form-item>
               <!-- 规则类型 radio -->
-              <el-form-item style="margin-bottom:4px" label="规则" :prop="`rules.${index}.type`" :rules="{ required: true, message: '请选择', trigger: 'change' }" label-width="70px">
+              <el-form-item style="margin-bottom:4px" label="规则" :prop="`rules.${index}.type`"
+                :rules="{ required: true, message: '请选择', trigger: 'change' }" label-width="70px">
                 <el-radio-group v-model="rule.type" size="small">
                   <el-radio :label="1">ROI规则</el-radio>
                   <el-radio :label="2">粉价规则</el-radio>
                 </el-radio-group>
               </el-form-item>
               <!-- 最小成本 -->
-              <el-form-item style="margin-bottom:4px" label="最小成本" :prop="`rules.${index}.minCost`" :rules="{ required: true, message: '请输入', trigger: 'blur' }" label-width="80px">
+              <el-form-item style="margin-bottom:4px" label="最小成本" :prop="`rules.${index}.minCost`"
+                :rules="{ required: true, message: '请输入', trigger: 'blur' }" label-width="80px">
                 <el-input-number v-model="rule.minCost" :min="0" placeholder="最小成本" style="width: 80%" size="small" />
               </el-form-item>
               <!-- ROI规则显示最小ROI -->
-              <el-form-item v-if="rule.type === 1" style="margin-bottom:4px" label="最小ROI" :prop="`rules.${index}.minIor`" :rules="{ required: true, message: '请输入', trigger: 'blur' }" label-width="80px">
-                <el-input-number v-model="rule.minIor" :min="0" placeholder="最小ROI" style="width: 80%" size="small" />
+              <el-form-item v-if="rule.type === 1" style="margin-bottom:4px" label="最小ROI"
+                :prop="`rules.${index}.minIor`" :rules="{ required: true, message: '请输入', trigger: 'blur' }"
+                label-width="80px">
+                <el-input-number v-model="rule.minIor" :precision="2" :min="0" placeholder="最小ROI" style="width: 80%" size="small" />
               </el-form-item>
               <!-- 粉价规则显示最大粉价 -->
-              <el-form-item v-if="rule.type === 2" style="margin-bottom:4px" label="最大粉价" :prop="`rules.${index}.scanMaxCost`" :rules="{ required: true, message: '请输入', trigger: 'blur' }" label-width="80px">
-                <el-input-number v-model="rule.scanMaxCost" :min="0" placeholder="最大粉价" style="width: 80%" size="small" />
+              <el-form-item v-if="rule.type === 2" style="margin-bottom:4px" label="最大粉价"
+                :prop="`rules.${index}.scanMaxCost`" :rules="{ required: true, message: '请输入', trigger: 'blur' }"
+                label-width="80px">
+                <el-input-number v-model="rule.scanMaxCost" :precision="2" :min="0" placeholder="最大粉价" style="width: 80%"
+                  size="small" />
               </el-form-item>
             </div>
           </div>
           <el-button type="primary" link @click="addRule" class="w-full">
-            <el-icon><Plus /></el-icon>
+            <el-icon>
+              <Plus />
+            </el-icon>
             添加规则
           </el-button>
         </div>
@@ -103,6 +130,10 @@
 </template>
 
 <script setup>
+defineOptions({
+  name: 'AdAutoConfEdit'
+})
+
 import { ref, reactive, onMounted } from 'vue'
 import { useRouter, useRoute } from 'vue-router'
 import { ElMessage } from 'element-plus'
@@ -114,6 +145,11 @@ import {
 } from '@/api/adAutoConf'
 import { getAccountGroupListAll } from '@/api/accountGroup'
 
+
+defineOptions({
+  name: 'AdAutoConfEdit'
+})
+
 // 响应式数据
 const submitting = ref(false)
 const formRef = ref(null)
@@ -132,6 +168,11 @@ const form = reactive({
   ruleName: '',
   adGroupId: 0,
   newAdDays: 3,
+  // 日预算等级 
+  dailyBudgetLevelArr: [100, 300, 500, 1000, 5000, 10000],
+  dailyBudgetLevel: '[10000, 30000, 50000, 100000, 500000, 1000000]',
+  // 达到日预算百分比后升级预算
+  dailyBudgetRateTop: 0.9,
   rules: []
 })
 
@@ -140,6 +181,9 @@ const rules = {
   ruleName: [
     { required: true, message: '请输入规则名称', trigger: 'blur' }
   ],
+  dailyBudgetLevelArr: [
+    { required: true, message: '请选择日预算等级', trigger: 'change' }
+  ],
   adGroupId: [
     { required: true, message: '请选择广告组', trigger: 'change' }
   ],
@@ -159,9 +203,9 @@ const createDefaultRule = () => ({
 
 const createFirstDefaultRule = () => ([
   { type: 1, newAd: 1, minCost: 100, minIor: 0.15, scanMaxCost: 10 },
-  { type: 1, newAd: 1, minCost: 30,  minIor: 0.10, scanMaxCost: 10 },
-  { type: 2, newAd: 1, minCost: 0,   minIor: 0,    scanMaxCost: 10  },
-  { type: 2, newAd: 2, minCost: 0,   minIor: 0,    scanMaxCost: 100 }
+  { type: 1, newAd: 1, minCost: 30, minIor: 0.10, scanMaxCost: 10 },
+  { type: 2, newAd: 1, minCost: 0, minIor: 0, scanMaxCost: 10 },
+  { type: 2, newAd: 2, minCost: 0, minIor: 0, scanMaxCost: 100 }
 ])
 
 // 添加规则
@@ -202,6 +246,9 @@ const loadDetail = async (id) => {
       form.jobNumber = data.jobNumber
       form.adGroupId = data.adGroupId
       form.newAdDays = data.newAdDays
+      form.dailyBudgetLevelArr = data.dailyBudgetLevel ? JSON.parse(data.dailyBudgetLevel).map(r => r / 100) : [100, 300, 500, 1000, 5000, 10000]
+      form.dailyBudgetLevel = data.dailyBudgetLevel
+      form.dailyBudgetRateTop = data.dailyBudgetRateTop
       // 解析 rules JSON
       if (data.rules) {
         try {
@@ -246,7 +293,9 @@ const handleSubmit = async () => {
         jobNumber: form.jobNumber,
         adGroupId: form.adGroupId,
         newAdDays: form.newAdDays,
-        rules: JSON.stringify(form.rules)
+        rules: JSON.stringify(form.rules),
+        dailyBudgetLevel: JSON.stringify(form.dailyBudgetLevelArr.map(r => r * 100).sort((a, b) => a - b)),
+        dailyBudgetRateTop: form.dailyBudgetRateTop
       }
       let response
       if (isEdit.value) {
@@ -254,7 +303,7 @@ const handleSubmit = async () => {
       } else {
         response = await saveAdAutoConf(data)
       }
-     
+
       if (response.success) {
         ElMessage.success(isEdit.value ? '更新成功' : '新增成功')
         handleBack()
@@ -273,7 +322,7 @@ const handleSubmit = async () => {
 // 页面加载
 onMounted(() => {
   loadAccountGroupList()
-  
+
   // 检查是否有id参数,有则是编辑模式
   const id = route.query.id
   if (id) {

+ 13 - 0
portal/src/views/ad-auto-conf/index.vue

@@ -119,6 +119,11 @@
         <el-table-column prop="adGroupId" label="广告组ID" width="120">
         </el-table-column>
         <el-table-column prop="newAdDays" label="新广告天数" width="120" />
+        <el-table-column prop="dailyBudgetLevel" label="日预算等级" width="200" show-overflow-tooltip>
+          <template #default="{ row }">
+            {{ JSON.parse(row.dailyBudgetLevel).map(r => r/100).join(',') }}
+          </template>
+        </el-table-column>
         <el-table-column label="规则数量" width="100">
           <template #default="{ row }">
             <el-tag @click="handleEdit(row)" size="small" type="info">{{ getRulesCount(row.rules) }} 条</el-tag>
@@ -224,6 +229,10 @@
 </template>
 
 <script setup>
+defineOptions({
+  name: 'AdAutoConf'
+})
+
 import { ref, reactive, computed, onMounted } from 'vue'
 import { useRouter } from 'vue-router'
 import { ElMessage, ElMessageBox } from 'element-plus'
@@ -237,6 +246,10 @@ import {
   getChildLog
 } from '@/api/adAutoConf'
 
+defineOptions({
+  name: 'AdAutoConf'
+})
+
 // 响应式数据
 const loading = ref(false)
 const confList = ref([])

+ 4 - 0
portal/src/views/ad-plan-conf/detail.vue

@@ -475,6 +475,10 @@
 </template>
 
 <script setup>
+defineOptions({
+  name: 'AdPlanConfDetail'
+})
+
 import { ref, onMounted, computed } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
 import { ElMessage, ElMessageBox } from 'element-plus'

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

@@ -719,6 +719,10 @@
 </template>
 
 <script setup>
+defineOptions({
+  name: 'AdPlanConf'
+})
+
 import { ref, computed, onMounted, watch, toRaw } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import {

+ 4 - 0
portal/src/views/adgroup/index.vue

@@ -135,6 +135,10 @@
 </template>
 
 <script setup>
+defineOptions({
+  name: 'Adgroup'
+})
+
 import { ref, reactive, onMounted } from 'vue'
 import { ElMessage } from 'element-plus'
 import request from '@/utils/request'

+ 4 - 0
portal/src/views/auth/index.vue

@@ -186,6 +186,10 @@
 </template>
 
 <script setup>
+defineOptions({
+  name: 'Auth'
+})
+
 import { ref, onMounted, computed } from 'vue'
 import { ElMessage } from 'element-plus'
 import { Promotion } from '@element-plus/icons-vue'

+ 4 - 0
portal/src/views/conversion/index.vue

@@ -165,6 +165,10 @@
 </template>
 
 <script setup>
+defineOptions({
+  name: 'Conversion'
+})
+
 import { ref, reactive, computed, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { Refresh, View } from '@element-plus/icons-vue'

+ 4 - 0
portal/src/views/daily-report/index.vue

@@ -244,6 +244,10 @@
 </template>
 
 <script setup>
+defineOptions({
+  name: 'DailyReport'
+})
+
 import { ref, reactive, computed, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { Refresh, Search } from '@element-plus/icons-vue'

+ 4 - 0
portal/src/views/dashboard/index.vue

@@ -230,6 +230,10 @@
 </template>
 
 <script setup>
+defineOptions({
+  name: 'Dashboard'
+})
+
 import { ref, onMounted } from 'vue'
 import { ElMessage } from 'element-plus'
 import {

+ 4 - 0
portal/src/views/drama/index.vue

@@ -245,6 +245,10 @@
 </template>
 
 <script setup>
+defineOptions({
+  name: 'Drama'
+})
+
 import { ref, computed, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { getDramaList, syncDrama, myConfList, addConf, delConf } from '@/api/drama'

+ 4 - 0
portal/src/views/fiction/index.vue

@@ -245,6 +245,10 @@
 </template>
 
 <script setup>
+defineOptions({
+  name: 'Fiction'
+})
+
 import { ref, computed, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { getFictionList, syncFiction } from '@/api/fiction'

+ 4 - 0
portal/src/views/image/index.vue

@@ -223,6 +223,10 @@
 </template>
 
 <script setup>
+defineOptions({
+  name: 'Image'
+})
+
 import { ref, reactive, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { UploadFilled } from '@element-plus/icons-vue'

+ 4 - 0
portal/src/views/load-page-conf/index.vue

@@ -141,6 +141,10 @@
 </template>
 
 <script setup>
+defineOptions({
+  name: 'LoadPageConf'
+})
+
 import { ref, reactive, computed, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { Plus } from '@element-plus/icons-vue'

+ 4 - 0
portal/src/views/login/index.vue

@@ -62,6 +62,10 @@
 </template>
 
 <script setup>
+defineOptions({
+  name: 'Login'
+})
+
 import { ref, reactive } from 'vue'
 import { useRouter } from 'vue-router'
 import { ElMessage } from 'element-plus'

+ 4 - 0
portal/src/views/oss-file/index.vue

@@ -211,6 +211,10 @@
 </template>
 
 <script setup>
+defineOptions({
+  name: 'OssFile'
+})
+
 import { ref, reactive, computed, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { UploadFilled } from '@element-plus/icons-vue'

+ 4 - 0
portal/src/views/profile/index.vue

@@ -220,6 +220,10 @@
 </template>
 
 <script setup>
+defineOptions({
+  name: 'Profile'
+})
+
 import { ref, reactive, computed, onMounted } from 'vue'
 import { ElMessage } from 'element-plus'
 import { WarningFilled, Avatar } from '@element-plus/icons-vue'

+ 4 - 0
portal/src/views/user/index.vue

@@ -178,6 +178,10 @@
 </template>
 
 <script setup>
+defineOptions({
+  name: 'User'
+})
+
 import { ref, onMounted, computed } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { Lock } from '@element-plus/icons-vue'

+ 4 - 0
portal/src/views/video/index.vue

@@ -183,6 +183,10 @@
 </template>
 
 <script setup>
+defineOptions({
+  name: 'Video'
+})
+
 import { ref, reactive, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { uploadVideo, getVideoList, deleteVideo } from '@/api/video'

+ 6 - 0
src/main/java/com/moka/gdtauto/entity/AdAutoConf.java

@@ -11,6 +11,7 @@ import lombok.Setter;
 import lombok.ToString;
 
 import java.io.Serializable;
+import java.math.BigDecimal;
 import java.time.LocalDateTime;
 
 /**
@@ -52,6 +53,11 @@ public class AdAutoConf implements Serializable {
      */
     private Long adGroupId;
 
+    // 日预算等级 [100,300,500,1000,5000,10000]
+    private String dailyBudgetLevel;
+    // 达到日预算百分比后升级预算
+    private BigDecimal dailyBudgetRateTop;
+
     /**
      * com.moka.gdtauto.common.AutoPlanRule数组
      * 规则json

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

@@ -52,6 +52,8 @@ public class AdPlan {
      * 出价金额(分)
      */
     private Long bidAmount;
+
+    private Long dailyBudget;
     /**
      * 期望ROI
      */

+ 39 - 3
src/main/java/com/moka/gdtauto/service/AdPlanAutoExecutor.java

@@ -16,10 +16,12 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
+import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
 
 /**
@@ -141,6 +143,8 @@ public class AdPlanAutoExecutor {
         executeLog.setParentLogId(parentExecuteLogId);
         executeLog.setExecuteTime(LocalDateTime.now());
         adAutoExecuteLogService.save(executeLog);
+
+        List<Long> dailyBudgetLevel = JSON.parseArray(autoConf.getDailyBudgetLevel(), Long.class);
         // 同步日报
         try {
             log.info("[AdAutoExecutor] 同步日报,confId={}", confId);
@@ -149,6 +153,7 @@ public class AdPlanAutoExecutor {
             // 分页查询当前 conf 下的 AdPlan,进行规则检测
             long adCount = 0;
             List<AdPlan> plansToSuspend = new ArrayList<>();
+            List<AdPlan> plansToAddDailyBudget = new ArrayList<>();
             int pageNum = 1;
             int pageSize = 100;
             while (true) {
@@ -171,10 +176,12 @@ public class AdPlanAutoExecutor {
                 }).collect(Collectors.toList());
                 
                 adCount += plans.size();
-
                 for (AdPlan plan : plans) {
-                    if (shouldSuspend(plan, rules, autoConf.getNewAdDays())) {
-                        plansToSuspend.add(plan);
+                    // if (shouldSuspend(plan, rules, autoConf.getNewAdDays())) {
+                    //     plansToSuspend.add(plan);
+                    // }
+                    if (shouldAddDailyBudget(plan, dailyBudgetLevel, autoConf.getDailyBudgetRateTop())) {
+                        plansToAddDailyBudget.add(plan);
                     }
                 }
 
@@ -188,6 +195,12 @@ public class AdPlanAutoExecutor {
                 adPlanConfService.gdtSuspendByPlans(adPlanConf, plansToSuspend);
             }
 
+            if (!plansToAddDailyBudget.isEmpty()) {
+                for (AdPlan plan : plansToAddDailyBudget) {
+                    adPlanService.updateAdgroupDailyBudget(adPlanConf, plan, plan.getDailyBudget());
+                }
+            }
+
             log.info("[AdAutoExecutor] confId={} 巡检:广告数={}, 需暂停数={}", confId, adCount, plansToSuspend.size());
             executeLog.setAdCount(adCount);
             executeLog.setSuspendCount(plansToSuspend.size());
@@ -204,6 +217,29 @@ public class AdPlanAutoExecutor {
 
     }
 
+    private boolean shouldAddDailyBudget(AdPlan plan, List<Long> dailyBudgetLevels, BigDecimal dailyBudgetRateTop) {
+        Long cost = plan.getCost();
+        if (cost == null || cost == 0) {
+            return false;
+        }
+        Long dailyBudget = plan.getDailyBudget();
+        Long dailyBudgetLimit = dailyBudgetRateTop.multiply(BigDecimal.valueOf(dailyBudget)).longValue();
+        if (cost >= dailyBudgetLimit) {
+            Long targetDailyBudget = 0L;
+            for (int i = 0; i < dailyBudgetLevels.size(); i++) {
+                if (dailyBudgetLevels.get(i) > cost) {
+                    targetDailyBudget = dailyBudgetLevels.get(i+1);
+                    break;
+                }
+            }
+            if (targetDailyBudget > 0) {
+                plan.setDailyBudget(targetDailyBudget);
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * 判断广告是否应该被暂停
      * 遍历所有规则,有任一规则满足条件则暂停

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

@@ -5,6 +5,7 @@ import java.util.List;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.moka.gdtauto.entity.AdPlan;
+import com.moka.gdtauto.entity.AdPlanConf;
 
 /**
  * 广告计划服务接口
@@ -14,6 +15,9 @@ import com.moka.gdtauto.entity.AdPlan;
  */
 public interface AdPlanService extends IService<AdPlan> {
 
+    //修改预算
+    void updateAdgroupDailyBudget(AdPlanConf conf, AdPlan plan, Long dailyBudget) throws Exception;
+
     /**
      * 创建广告计划
      * 

+ 17 - 25
src/main/java/com/moka/gdtauto/service/impl/AdPlanConfServiceImpl.java

@@ -50,6 +50,7 @@ import com.tencent.ads.exception.TencentAdsResponseException;
 import com.tencent.ads.model.v3.*;
 import com.tencent.ads.v3.TencentAds;
 import com.moka.gdtauto.util.AdgroupsAddReqPool;
+import com.moka.gdtauto.util.StrUtil;
 import com.moka.gdtauto.common.AdPlanStatus;
 
 import java.io.File;
@@ -107,7 +108,7 @@ public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanC
             this.lambdaUpdate()
                 .eq(AdPlanConf::getId, conf.getId())
                 .set(AdPlanConf::getSystemDeleteProcessStatus, 3)
-                .set(AdPlanConf::getFailReason, truncateError(e.getMessage())).update();
+                .set(AdPlanConf::getFailReason, StrUtil.truncateError(e.getMessage())).update();
         }
     }
 
@@ -144,7 +145,7 @@ public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanC
                 tencentAds.adgroups().adgroupsDelete(req);
                 plan.setFailReason("删除成功");
             } catch (Exception e) {
-                plan.setFailReason(truncateError(e.getMessage()));
+                plan.setFailReason(StrUtil.truncateError(e.getMessage()));
             }
             adPlanService.lambdaUpdate()
                     .eq(AdPlan::getId, plan.getId())
@@ -187,7 +188,7 @@ public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanC
                 tencentAds.dynamicCreatives().dynamicCreativesDelete(req);
                 creative.setFailReason("删除成功");
             } catch (Exception e) {
-                creative.setFailReason(truncateError(e.getMessage()));
+                creative.setFailReason(StrUtil.truncateError(e.getMessage()));
             }
             adPlanCreativeService.lambdaUpdate()
                     .eq(AdPlanCreative::getId, creative.getId())
@@ -228,7 +229,7 @@ public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanC
             this.lambdaUpdate()
                 .eq(AdPlanConf::getId, conf.getId())
                 .eq(AdPlanConf::getSystemDeleteProcessStatus, 3)
-                .set(AdPlanConf::getFailReason, truncateError(e.getMessage())).update();
+                .set(AdPlanConf::getFailReason, StrUtil.truncateError(e.getMessage())).update();
         }
     }
 
@@ -247,7 +248,7 @@ public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanC
             this.lambdaUpdate()
                 .eq(AdPlanConf::getId, conf.getId())
                 .set(AdPlanConf::getSystemSuspendProcessStatus, 3)
-                .set(AdPlanConf::getFailReason, truncateError(e.getMessage())).update();
+                .set(AdPlanConf::getFailReason, StrUtil.truncateError(e.getMessage())).update();
         }
     }
 
@@ -282,10 +283,11 @@ public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanC
                 req.setAccountId(plan.getAccountId());
                 req.setAdgroupId(plan.getAdgroupId());
                 req.setConfiguredStatus(ConfiguredStatus.SUSPEND);
+                req.setSmartTargetingMode(SmartTargetingMode.SMART_TARGETING_MANUAL);
                 tencentAds.adgroups().adgroupsUpdate(req);
                 plan.setConfiguredStatus(ConfiguredStatus.SUSPEND.getValue());
             } catch (Exception e) {
-                plan.setFailReason(truncateError(e.getMessage()));
+                plan.setFailReason(StrUtil.truncateError(e.getMessage()));
             }
             adPlanService.lambdaUpdate()
                     .eq(AdPlan::getId, plan.getId())
@@ -332,7 +334,7 @@ public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanC
                 tencentAds.dynamicCreatives().dynamicCreativesUpdate(req);
                 creative.setConfiguredStatus(ConfiguredStatus.SUSPEND.getValue());
             } catch (Exception e) {
-                creative.setFailReason(truncateError(e.getMessage()));
+                creative.setFailReason(StrUtil.truncateError(e.getMessage()));
             }
             adPlanCreativeService.lambdaUpdate()
                     .eq(AdPlanCreative::getId, creative.getId())
@@ -371,7 +373,7 @@ public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanC
             this.lambdaUpdate()
                 .eq(AdPlanConf::getId, conf.getId())
                 .set(AdPlanConf::getSystemSuspendProcessStatus, 3)
-                .set(AdPlanConf::getFailReason, truncateError(e.getMessage())).update();
+                .set(AdPlanConf::getFailReason, StrUtil.truncateError(e.getMessage())).update();
         }
     }
 
@@ -389,7 +391,7 @@ public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanC
             this.lambdaUpdate()
                 .eq(AdPlanConf::getId, conf.getId())
                 .set(AdPlanConf::getSystemSuspendProcessStatus, 3)
-                .set(AdPlanConf::getFailReason, truncateError(e.getMessage())).update();
+                .set(AdPlanConf::getFailReason, StrUtil.truncateError(e.getMessage())).update();
         }
     }
 
@@ -407,7 +409,7 @@ public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanC
             this.lambdaUpdate()
                 .eq(AdPlanConf::getId, conf.getId())
                 .set(AdPlanConf::getSystemSuspendProcessStatus, 3)
-                .set(AdPlanConf::getFailReason, truncateError(e.getMessage())).update();
+                .set(AdPlanConf::getFailReason, StrUtil.truncateError(e.getMessage())).update();
         }
     }
 
@@ -555,7 +557,7 @@ public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanC
         } catch (Exception e) {
             log.error("创建TencentAds实例失败,confId={}", confId, e);
             conf.setStatus(AdPlanStatus.FAILED);
-            conf.setFailReason("创建TencentAds实例失败: " + truncateError(e.getMessage()));
+            conf.setFailReason("创建TencentAds实例失败: " + StrUtil.truncateError(e.getMessage()));
             this.updateById(conf);
             return;
         }
@@ -567,7 +569,7 @@ public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanC
             log.error("准备创意资产失败,confId={}", confId, e);
             // 更新 AdPlanConf 状态为失败
             conf.setStatus(AdPlanStatus.FAILED);
-            conf.setFailReason("准备创意资产失败: " + truncateError(e.getMessage()));
+            conf.setFailReason("准备创意资产失败: " + StrUtil.truncateError(e.getMessage()));
             this.updateById(conf);
             return;
         }
@@ -624,12 +626,12 @@ public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanC
                 log.error("创建广告失败, planId={} code={} message={} messageCn={}", plan.getId(), e.getCode(),
                         e.getMessage(), e.getMessageCn());
                 plan.setStatus(AdPlanStatus.FAILED);
-                plan.setFailReason(truncateError(e.getMessageCn()));
+                plan.setFailReason(StrUtil.truncateError(e.getMessageCn()));
                 adPlanService.updateById(plan);
             } catch (Exception e) {
                 log.error("创建广告失败, planId={}", plan.getId(), e);
                 plan.setStatus(AdPlanStatus.FAILED);
-                plan.setFailReason(truncateError(e.getMessage()));
+                plan.setFailReason(StrUtil.truncateError(e.getMessage()));
                 adPlanService.updateById(plan);
             }
         }
@@ -785,7 +787,7 @@ public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanC
             } catch (TencentAdsResponseException e) {
                 log.error("创建动态创意失败, creativeId={}", creative.getId(), e);
                 creative.setStatus(AdPlanStatus.FAILED);
-                creative.setFailReason(truncateError(e.getMessage()));
+                creative.setFailReason(StrUtil.truncateError(e.getMessage()));
                 adPlanCreativeService.updateById(creative);
             }
         }
@@ -990,16 +992,6 @@ public class AdPlanConfServiceImpl extends ServiceImpl<AdPlanConfMapper, AdPlanC
     }
 
     /**
-     * 截断错误信息到200字符
-     */
-    private String truncateError(String error) {
-        if (error == null) {
-            return null;
-        }
-        return error.length() > 200 ? error.substring(0, 200) + "..." : error;
-    }
-
-    /**
      * 计算开始日期 (明天)
      */
     private String calculateBeginDate() {

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

@@ -6,11 +6,15 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.moka.gdtauto.client.TencentAdsApiClientFactory;
 import com.moka.gdtauto.common.Constant;
 import com.moka.gdtauto.entity.AdPlan;
+import com.moka.gdtauto.entity.AdPlanConf;
 import com.moka.gdtauto.mapper.AdPlanMapper;
 import com.moka.gdtauto.service.AdPlanService;
+import com.moka.gdtauto.util.StrUtil;
+import com.tencent.ads.model.v3.AdgroupsUpdateRequest;
 import com.tencent.ads.model.v3.AdgroupsGetResponseData;
 import com.tencent.ads.model.v3.FilterOperator;
 import com.tencent.ads.model.v3.FilteringStruct;
+import com.tencent.ads.model.v3.SmartTargetingMode;
 import com.tencent.ads.v3.TencentAds;
 
 import lombok.RequiredArgsConstructor;
@@ -39,6 +43,8 @@ public class AdPlanServiceImpl extends ServiceImpl<AdPlanMapper, AdPlan> impleme
     private final AdPlanMapper adPlanMapper;
     private final TencentAdsApiClientFactory clientFactory;
 
+   
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Long createAdPlan(AdPlan adPlan) {
@@ -257,4 +263,27 @@ public class AdPlanServiceImpl extends ServiceImpl<AdPlanMapper, AdPlan> impleme
         log.info("[定时任务] accountId={} 批量更新 {} 条 system_status", accountId, toUpdate.size());
         return toUpdate.size();
     }
+
+    @Override
+    public void updateAdgroupDailyBudget(AdPlanConf conf, AdPlan plan, Long dailyBudget) throws Exception {
+       try {
+        TencentAds tencentAds = clientFactory.getTencentAds(conf.getJobNumber(), Constant.MAIN_ORG_ACCOUNT_ID);
+        AdgroupsUpdateRequest req = new AdgroupsUpdateRequest();
+        req.setAccountId(plan.getAccountId());
+        req.setAdgroupId(plan.getAdgroupId());
+        req.setDailyBudget(dailyBudget);
+        req.setSmartTargetingMode(SmartTargetingMode.SMART_TARGETING_MANUAL);
+        tencentAds.adgroups().adgroupsUpdate(req);
+        plan.setDailyBudget(dailyBudget);
+        plan.setFailReason("设置日预算成功");
+       } catch (Exception e) {
+           log.error("[定时任务] 修改广告日预算失败,计划ID: {}", plan.getId(), e);
+           plan.setFailReason("设置日预算失败:" + StrUtil.truncateError(e.getMessage()));
+       }
+       this.lambdaUpdate()
+       .eq(AdPlan::getId, plan.getId())
+       .set(AdPlan::getDailyBudget, plan.getDailyBudget())
+       .set(AdPlan::getFailReason, plan.getFailReason())
+       .update();
+    }
 }

+ 9 - 9
src/main/java/com/moka/gdtauto/task/AdAutoTask.java

@@ -24,15 +24,15 @@ public class AdAutoTask {
     private final AdPlanCreativeAutoExecutor adPlanCreativeAutoExecutor;
     // private final ExecutorService executorService = Executors.newFixedThreadPool(10);
 
-    // @Scheduled(cron = "30 30 * * * ?")
-    // @SchedulerLock(name = "ad-plan-auto-task", lockAtMostFor = "9m", lockAtLeastFor = "2m")
-    // public void adPlanAutoTask() {
-    //     log.info("AdPlanAutoTask execute");
-    //     List<AdAutoConf> confList = adAutoConfService.lambdaQuery().list();
-    //     confList.forEach(conf -> adPlanAutoExecutor.asyncExecute(conf));
-    // }
-
-    @Scheduled(cron = "30 30 * * * ?")
+    @Scheduled(cron = "0 0/10 * * * ?")
+    @SchedulerLock(name = "ad-plan-auto-task", lockAtMostFor = "9m", lockAtLeastFor = "2m")
+    public void adPlanAutoTask() {
+        log.info("AdPlanAutoTask execute");
+        List<AdAutoConf> confList = adAutoConfService.lambdaQuery().list();
+        confList.forEach(conf -> adPlanAutoExecutor.asyncExecute(conf));
+    }
+
+    @Scheduled(cron = "30 0/10 * * * ?")
     @SchedulerLock(name = "ad-creative-auto-task", lockAtMostFor = "9m", lockAtLeastFor = "2m")
     public void adCreativeAutoTask() {
         log.info("AdCreativeAutoTask execute");

+ 10 - 0
src/main/java/com/moka/gdtauto/util/StrUtil.java

@@ -0,0 +1,10 @@
+package com.moka.gdtauto.util;
+
+public class StrUtil {
+    public static String truncateError(String error) {
+        if (error == null) {
+            return null;
+        }
+        return error.length() > 200 ? error.substring(0, 200) + "..." : error;
+    }
+}

+ 8 - 0
src/test/java/com/moka/gdtauto/util/SampleTest.java

@@ -79,4 +79,12 @@ public class SampleTest {
         BigDecimal ruleRoi = new BigDecimal("0.2");
         System.out.println(planRoi.compareTo(ruleRoi)); // > 1 表示 planRoi 大于 ruleRoi
     }
+
+    @Test
+    public void testAddDailyBudget() {
+        BigDecimal dailyBudgetRateTop = new BigDecimal("0.9");
+        Long dailyBudget = 10000L;
+        Long dailyBudgetLimit = dailyBudgetRateTop.multiply(BigDecimal.valueOf(dailyBudget)).longValue();
+        System.out.println(dailyBudgetLimit);
+    }
 }