Procházet zdrojové kódy

腾讯广告接口封装

wangyunpeng před 1 týdnem
rodič
revize
fe174ae948
41 změnil soubory, kde provedl 5871 přidání a 581 odebrání
  1. 345 0
      core/src/main/java/com/tzld/ad/marketing/client/AbstractTencentApiClient.java
  2. 196 0
      core/src/main/java/com/tzld/ad/marketing/client/AdGroupClient.java
  3. 129 0
      core/src/main/java/com/tzld/ad/marketing/client/AdvertiserClient.java
  4. 42 0
      core/src/main/java/com/tzld/ad/marketing/client/AgencyClient.java
  5. 87 0
      core/src/main/java/com/tzld/ad/marketing/client/BatchClient.java
  6. 97 0
      core/src/main/java/com/tzld/ad/marketing/client/ComponentClient.java
  7. 189 0
      core/src/main/java/com/tzld/ad/marketing/client/CreativeClient.java
  8. 138 0
      core/src/main/java/com/tzld/ad/marketing/client/CustomAudienceClient.java
  9. 172 0
      core/src/main/java/com/tzld/ad/marketing/client/DailyReportClient.java
  10. 59 0
      core/src/main/java/com/tzld/ad/marketing/client/FundsClient.java
  11. 100 0
      core/src/main/java/com/tzld/ad/marketing/client/ImageClient.java
  12. 138 0
      core/src/main/java/com/tzld/ad/marketing/client/OAuthClient.java
  13. 58 0
      core/src/main/java/com/tzld/ad/marketing/client/PageClient.java
  14. 272 0
      core/src/main/java/com/tzld/ad/marketing/client/RtaStrategyClient.java
  15. 60 0
      core/src/main/java/com/tzld/ad/marketing/client/TencentMarketingApiConfig.java
  16. 75 0
      core/src/main/java/com/tzld/ad/marketing/client/UserActionClient.java
  17. 73 0
      core/src/main/java/com/tzld/ad/marketing/client/UserActionSetClient.java
  18. 140 0
      core/src/main/java/com/tzld/ad/marketing/client/VideoClient.java
  19. 88 0
      core/src/main/java/com/tzld/ad/marketing/client/WechatClient.java
  20. 84 0
      core/src/main/java/com/tzld/ad/marketing/exception/TencentApiException.java
  21. 98 0
      core/src/main/java/com/tzld/ad/marketing/model/TencentApiResponse.java
  22. 231 0
      core/src/main/java/com/tzld/ad/marketing/model/adgroup/AdGroupDTO.java
  23. 152 0
      core/src/main/java/com/tzld/ad/marketing/model/advertiser/AdvertiserDTO.java
  24. 137 0
      core/src/main/java/com/tzld/ad/marketing/model/audience/AudienceDTO.java
  25. 153 0
      core/src/main/java/com/tzld/ad/marketing/model/batch/BatchDTO.java
  26. 119 0
      core/src/main/java/com/tzld/ad/marketing/model/component/ComponentDTO.java
  27. 180 0
      core/src/main/java/com/tzld/ad/marketing/model/creative/CreativeDTO.java
  28. 60 0
      core/src/main/java/com/tzld/ad/marketing/model/funds/FundsDTO.java
  29. 118 0
      core/src/main/java/com/tzld/ad/marketing/model/image/ImageDTO.java
  30. 60 0
      core/src/main/java/com/tzld/ad/marketing/model/oauth/OAuthDTO.java
  31. 78 0
      core/src/main/java/com/tzld/ad/marketing/model/page/PageDTO.java
  32. 121 0
      core/src/main/java/com/tzld/ad/marketing/model/report/ReportDTO.java
  33. 175 0
      core/src/main/java/com/tzld/ad/marketing/model/rta/RtaDTO.java
  34. 149 0
      core/src/main/java/com/tzld/ad/marketing/model/useraction/UserActionDTO.java
  35. 127 0
      core/src/main/java/com/tzld/ad/marketing/model/video/VideoDTO.java
  36. 107 0
      core/src/main/java/com/tzld/ad/marketing/model/wechat/WechatDTO.java
  37. 466 387
      core/src/main/java/com/tzld/ad/service/adput/impl/AdPutTencentCommonServiceImpl.java
  38. 116 68
      core/src/main/java/com/tzld/ad/service/adput/impl/AdPutTencentUserActionAddServiceImpl.java
  39. 663 0
      server/src/main/java/com/tzld/ad/controller/marketing/TencentMarketingController.java
  40. 19 0
      server/src/main/resources/application.yml
  41. 0 126
      server/src/test/java/GeminiTest.java

+ 345 - 0
core/src/main/java/com/tzld/ad/marketing/client/AbstractTencentApiClient.java

@@ -0,0 +1,345 @@
+package com.tzld.ad.marketing.client;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.TypeReference;
+import com.tzld.ad.marketing.exception.TencentApiException;
+import com.tzld.ad.marketing.model.TencentApiResponse;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.*;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * 腾讯广告 Marketing API 客户端基类
+ * <p>
+ * 提供统一的HTTP请求处理、响应解析、异常处理等功能
+ */
+@Slf4j
+public abstract class AbstractTencentApiClient {
+
+    protected static final MediaType JSON_MEDIA_TYPE = MediaType.parse("application/json; charset=utf-8");
+    protected static final MediaType OCTET_STREAM_MEDIA_TYPE = MediaType.parse("application/octet-stream");
+
+    private final OkHttpClient httpClient;
+    private final TencentMarketingApiConfig config;
+
+    protected AbstractTencentApiClient(OkHttpClient httpClient, TencentMarketingApiConfig config) {
+        this.httpClient = httpClient;
+        this.config = config;
+    }
+
+    /**
+     * 获取API配置
+     */
+    protected TencentMarketingApiConfig getConfig() {
+        return config;
+    }
+
+    /**
+     * 获取完整API URL
+     *
+     * @param path API路径,如 "/adgroups/get"
+     * @return 完整URL
+     */
+    protected String getApiUrl(String path) {
+        return TencentMarketingApiConfig.FULL_BASE_URL + path;
+    }
+
+    /**
+     * 构建带公共参数的GET请求URL
+     *
+     * @param path        API路径
+     * @param accessToken 访问令牌
+     * @param params      查询参数
+     * @return 完整URL
+     */
+    protected String buildGetUrl(String path, String accessToken, Map<String, Object> params) {
+        StringBuilder url = new StringBuilder(getApiUrl(path));
+        url.append("?access_token=").append(accessToken);
+        url.append("&timestamp=").append(getTimestamp());
+        url.append("&nonce=").append(getNonce());
+
+        if (params != null && !params.isEmpty()) {
+            for (Map.Entry<String, Object> entry : params.entrySet()) {
+                if (entry.getValue() != null) {
+                    String value = entry.getValue() instanceof String
+                            ? (String) entry.getValue()
+                            : JSON.toJSONString(entry.getValue());
+                    url.append("&").append(entry.getKey()).append("=").append(urlEncode(value));
+                }
+            }
+        }
+        return url.toString();
+    }
+
+    /**
+     * 发送GET请求
+     *
+     * @param path        API路径
+     * @param accessToken 访问令牌
+     * @param params      查询参数
+     * @return 响应JSON字符串
+     */
+    protected String doGet(String path, String accessToken, Map<String, Object> params) {
+        String url = buildGetUrl(path, accessToken, params);
+        log.debug("[TencentAPI] GET request: {}", url);
+
+        Request request = new Request.Builder()
+                .url(url)
+                .get()
+                .build();
+
+        return executeRequest(request);
+    }
+
+    /**
+     * 发送POST请求
+     *
+     * @param path        API路径
+     * @param accessToken 访问令牌
+     * @param body        请求体
+     * @return 响应JSON字符串
+     */
+    protected String doPost(String path, String accessToken, Object body) {
+        return doPost(path, accessToken, body, null);
+    }
+
+    /**
+     * 发送POST请求(带额外查询参数)
+     *
+     * @param path        API路径
+     * @param accessToken 访问令牌
+     * @param body        请求体
+     * @param queryParams 额外的查询参数
+     * @return 响应JSON字符串
+     */
+    protected String doPost(String path, String accessToken, Object body, Map<String, String> queryParams) {
+        StringBuilder url = new StringBuilder(getApiUrl(path));
+        url.append("?access_token=").append(accessToken);
+        url.append("&timestamp=").append(getTimestamp());
+        url.append("&nonce=").append(getNonce());
+
+        if (queryParams != null && !queryParams.isEmpty()) {
+            for (Map.Entry<String, String> entry : queryParams.entrySet()) {
+                if (entry.getValue() != null) {
+                    url.append("&").append(entry.getKey()).append("=").append(urlEncode(entry.getValue()));
+                }
+            }
+        }
+
+        String jsonBody = body != null ? JSON.toJSONString(body) : "{}";
+        log.debug("[TencentAPI] POST request: {}, body: {}", url, jsonBody);
+
+        RequestBody requestBody = RequestBody.create(JSON_MEDIA_TYPE, jsonBody);
+        Request request = new Request.Builder()
+                .url(url.toString())
+                .post(requestBody)
+                .build();
+
+        return executeRequest(request);
+    }
+
+    /**
+     * 发送POST请求(使用原始JSON字符串)
+     *
+     * @param path        API路径
+     * @param accessToken 访问令牌
+     * @param jsonBody    原始JSON字符串
+     * @return 响应JSON字符串
+     */
+    protected String doPostRaw(String path, String accessToken, String jsonBody) {
+        StringBuilder url = new StringBuilder(getApiUrl(path));
+        url.append("?access_token=").append(accessToken);
+        url.append("&timestamp=").append(getTimestamp());
+        url.append("&nonce=").append(getNonce());
+
+        String body = jsonBody != null ? jsonBody : "{}";
+        log.debug("[TencentAPI] POST request: {}, body: {}", url, body);
+
+        RequestBody requestBody = RequestBody.create(JSON_MEDIA_TYPE, body);
+        Request request = new Request.Builder()
+                .url(url.toString())
+                .post(requestBody)
+                .build();
+
+        return executeRequest(request);
+    }
+
+    /**
+     * 发送文件上传请求
+     *
+     * @param path        API路径
+     * @param accessToken 访问令牌
+     * @param params      表单参数
+     * @param fileParam   文件参数名
+     * @param fileName    文件名
+     * @param fileData    文件数据
+     * @return 响应JSON字符串
+     */
+    protected String doUpload(String path, String accessToken, Map<String, String> params,
+                              String fileParam, String fileName, byte[] fileData) {
+        StringBuilder url = new StringBuilder(getApiUrl(path));
+        url.append("?access_token=").append(accessToken);
+        url.append("&timestamp=").append(getTimestamp());
+        url.append("&nonce=").append(getNonce());
+
+        MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);
+
+        if (params != null) {
+            for (Map.Entry<String, String> entry : params.entrySet()) {
+                if (entry.getValue() != null) {
+                    builder.addFormDataPart(entry.getKey(), entry.getValue());
+                }
+            }
+        }
+
+        builder.addFormDataPart(fileParam, fileName, RequestBody.create(OCTET_STREAM_MEDIA_TYPE, fileData));
+
+        Request request = new Request.Builder()
+                .url(url.toString())
+                .post(builder.build())
+                .build();
+
+        return executeRequest(request);
+    }
+
+    /**
+     * 执行HTTP请求
+     *
+     * @param request 请求对象
+     * @return 响应字符串
+     */
+    protected String executeRequest(Request request) {
+        try (Response response = httpClient.newCall(request).execute()) {
+            String responseBody = response.body() != null ? response.body().string() : "";
+
+            if (!response.isSuccessful()) {
+                log.error("[TencentAPI] HTTP error, status={}, url={}, body={}",
+                        response.code(), request.url(), responseBody);
+                throw new TencentApiException(response.code(), "HTTP request failed", responseBody);
+            }
+
+            log.debug("[TencentAPI] Response: {}", responseBody);
+            return responseBody;
+        } catch (IOException e) {
+            log.error("[TencentAPI] Request exception, url={}", request.url(), e);
+            throw new TencentApiException(-1, "Request failed", e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 解析响应为指定类型
+     *
+     * @param jsonStr JSON字符串
+     * @param clazz   目标类型
+     * @param <T>     泛型
+     * @return 解析后的响应对象
+     */
+    protected <T> TencentApiResponse<T> parseResponse(String jsonStr, Class<T> clazz) {
+        JSONObject json = JSON.parseObject(jsonStr);
+        TencentApiResponse<T> response = new TencentApiResponse<>();
+        response.setCode(json.getInteger("code"));
+        response.setMessage(json.getString("message"));
+        response.setMessageCn(json.getString("message_cn"));
+
+        if (response.isSuccess()) {
+            Object data = json.get("data");
+            if (data != null) {
+                response.setData(JSON.parseObject(JSON.toJSONString(data), clazz));
+            }
+
+            JSONObject pageInfoJson = json.getJSONObject("page_info");
+            if (pageInfoJson != null) {
+                TencentApiResponse.PageInfo pageInfo = JSON.parseObject(
+                        pageInfoJson.toJSONString(), TencentApiResponse.PageInfo.class);
+                response.setPageInfo(pageInfo);
+            }
+        }
+
+        return response;
+    }
+
+    /**
+     * 解析列表响应
+     *
+     * @param jsonStr JSON字符串
+     * @param clazz   列表元素类型
+     * @param <T>     泛型
+     * @return 解析后的响应对象
+     */
+    protected <T> TencentApiResponse<TencentApiResponse.ListData<T>> parseListResponse(
+            String jsonStr, Class<T> clazz) {
+        JSONObject json = JSON.parseObject(jsonStr);
+        TencentApiResponse<TencentApiResponse.ListData<T>> response = new TencentApiResponse<>();
+        response.setCode(json.getInteger("code"));
+        response.setMessage(json.getString("message"));
+        response.setMessageCn(json.getString("message_cn"));
+
+        if (response.isSuccess()) {
+            JSONObject dataJson = json.getJSONObject("data");
+            if (dataJson != null) {
+                TencentApiResponse.ListData<T> listData = new TencentApiResponse.ListData<>();
+                com.alibaba.fastjson.JSONArray listArray = dataJson.getJSONArray("list");
+                if (listArray != null) {
+                    java.util.List<T> list = new java.util.ArrayList<>();
+                    for (int i = 0; i < listArray.size(); i++) {
+                        T item = listArray.getObject(i, clazz);
+                        list.add(item);
+                    }
+                    listData.setList(list);
+                }
+
+                JSONObject pageInfoJson = dataJson.getJSONObject("page_info");
+                if (pageInfoJson != null) {
+                    listData.setPageInfo(JSON.parseObject(
+                            pageInfoJson.toJSONString(), TencentApiResponse.PageInfo.class));
+                }
+                response.setData(listData);
+            }
+        }
+
+        return response;
+    }
+
+    /**
+     * 检查响应是否成功,不成功则抛出异常
+     *
+     * @param response 响应对象
+     */
+    protected void checkResponse(TencentApiResponse<?> response) {
+        if (!response.isSuccess()) {
+            throw new TencentApiException(response.getCode(), response.getMessage(), response.getMessageCn());
+        }
+    }
+
+    /**
+     * 获取当前时间戳(秒)
+     */
+    protected String getTimestamp() {
+        return String.valueOf(System.currentTimeMillis() / 1000);
+    }
+
+    /**
+     * 获取随机nonce
+     */
+    protected String getNonce() {
+        return UUID.randomUUID().toString().replace("-", "");
+    }
+
+    /**
+     * URL编码
+     */
+    protected String urlEncode(String value) {
+        try {
+            return URLEncoder.encode(value, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException("URL encode failed", e);
+        }
+    }
+}

+ 196 - 0
core/src/main/java/com/tzld/ad/marketing/client/AdGroupClient.java

@@ -0,0 +1,196 @@
+package com.tzld.ad.marketing.client;
+
+import com.alibaba.fastjson.JSON;
+import com.tzld.ad.marketing.model.TencentApiResponse;
+import com.tzld.ad.marketing.model.adgroup.AdGroupDTO;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.OkHttpClient;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 腾讯广告 广告模块客户端
+ * <p>
+ * 接口文档: https://developers.e.qq.com/v3.0/docs/api/adgroups_get
+ * <p>
+ * 包含接口:
+ * - adgroups/get: 获取广告
+ * - adgroups/add: 创建广告
+ * - adgroups/update: 更新广告
+ * - adgroups/delete: 删除广告
+ */
+@Slf4j
+@Component
+public class AdGroupClient extends AbstractTencentApiClient {
+
+    private static final String PATH_GET = "/adgroups/get";
+    private static final String PATH_ADD = "/adgroups/add";
+    private static final String PATH_UPDATE = "/adgroups/update";
+    private static final String PATH_DELETE = "/adgroups/delete";
+
+    public AdGroupClient(OkHttpClient tencentMarketingHttpClient, TencentMarketingApiConfig config) {
+        super(tencentMarketingHttpClient, config);
+    }
+
+    /**
+     * 查询广告列表
+     *
+     * @param accessToken 访问令牌
+     * @param request     查询请求
+     * @return 广告列表
+     */
+    public TencentApiResponse<TencentApiResponse.ListData<AdGroupDTO.AdGroupInfo>> getAdGroups(
+            String accessToken, AdGroupDTO.GetRequest request) {
+        Map<String, Object> params = buildGetParams(request);
+        String response = doGet(PATH_GET, accessToken, params);
+        return parseListResponse(response, AdGroupDTO.AdGroupInfo.class);
+    }
+
+    /**
+     * 查询单个广告信息
+     *
+     * @param accessToken 访问令牌
+     * @param accountId   广告主ID
+     * @param adgroupId   广告ID
+     * @return 广告信息
+     */
+    public TencentApiResponse<AdGroupDTO.AdGroupInfo> getAdGroup(
+            String accessToken, Long accountId, Long adgroupId) {
+        AdGroupDTO.GetRequest request = AdGroupDTO.GetRequest.builder()
+                .accountId(accountId)
+                .filtering(AdGroupDTO.GetRequest.Filtering.builder()
+                        .adgroupIdList(java.util.Collections.singletonList(adgroupId))
+                        .build())
+                .build();
+
+        TencentApiResponse<TencentApiResponse.ListData<AdGroupDTO.AdGroupInfo>> response =
+                getAdGroups(accessToken, request);
+
+        TencentApiResponse<AdGroupDTO.AdGroupInfo> result = new TencentApiResponse<>();
+        result.setCode(response.getCode());
+        result.setMessage(response.getMessage());
+        result.setMessageCn(response.getMessageCn());
+
+        if (response.isSuccess() && response.getData() != null &&
+                !response.getData().getList().isEmpty()) {
+            result.setData(response.getData().getList().get(0));
+        }
+        return result;
+    }
+
+    /**
+     * 创建广告
+     *
+     * @param accessToken 访问令牌
+     * @param request     创建请求
+     * @return 创建结果,包含广告ID
+     */
+    public TencentApiResponse<Long> addAdGroup(String accessToken, AdGroupDTO.AddRequest request) {
+        String response = doPost(PATH_ADD, accessToken, request);
+        return parseResponse(response, Long.class, "adgroup_id");
+    }
+
+    /**
+     * 更新广告
+     *
+     * @param accessToken 访问令牌
+     * @param request     更新请求
+     * @return 响应
+     */
+    public TencentApiResponse<Void> updateAdGroup(String accessToken, AdGroupDTO.UpdateRequest request) {
+        String response = doPost(PATH_UPDATE, accessToken, request);
+        return parseResponse(response, Void.class);
+    }
+
+    /**
+     * 更新广告出价
+     *
+     * @param accessToken 访问令牌
+     * @param accountId   广告主ID
+     * @param adgroupId   广告ID
+     * @param bidAmount   出价金额(分)
+     * @return 响应
+     */
+    public TencentApiResponse<Void> updateBidAmount(String accessToken, Long accountId, Long adgroupId, Integer bidAmount) {
+        Map<String, Object> body = new HashMap<>();
+        body.put("account_id", accountId);
+        body.put("adgroup_id", adgroupId);
+        body.put("bid_amount", bidAmount);
+
+        String response = doPost(PATH_UPDATE, accessToken, body);
+        return parseResponse(response, Void.class);
+    }
+
+    /**
+     * 更新广告状态
+     *
+     * @param accessToken      访问令牌
+     * @param accountId        广告主ID
+     * @param adgroupId        广告ID
+     * @param configuredStatus 广告状态 (AD_STATUS_NORMAL/AD_STATUS_SUSPEND)
+     * @return 响应
+     */
+    public TencentApiResponse<Void> updateStatus(String accessToken, Long accountId, Long adgroupId, String configuredStatus) {
+        Map<String, Object> body = new HashMap<>();
+        body.put("account_id", accountId);
+        body.put("adgroup_id", adgroupId);
+        body.put("configured_status", configuredStatus);
+
+        String response = doPost(PATH_UPDATE, accessToken, body);
+        return parseResponse(response, Void.class);
+    }
+
+    /**
+     * 删除广告
+     *
+     * @param accessToken 访问令牌
+     * @param accountId   广告主ID
+     * @param adgroupId   广告ID
+     * @return 响应
+     */
+    public TencentApiResponse<Void> deleteAdGroup(String accessToken, Long accountId, Long adgroupId) {
+        Map<String, Object> body = new HashMap<>();
+        body.put("account_id", accountId);
+        body.put("adgroup_id", adgroupId);
+
+        String response = doPost(PATH_DELETE, accessToken, body);
+        return parseResponse(response, Void.class);
+    }
+
+    private Map<String, Object> buildGetParams(AdGroupDTO.GetRequest request) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("account_id", request.getAccountId());
+
+        if (request.getFiltering() != null) {
+            params.put("filtering", JSON.toJSONString(request.getFiltering()));
+        }
+        if (request.getPage() != null) {
+            params.put("page", request.getPage());
+        }
+        if (request.getPageSize() != null) {
+            params.put("page_size", request.getPageSize());
+        }
+        if (request.getPaginationMode() != null) {
+            params.put("pagination_mode", request.getPaginationMode());
+        }
+        if (request.getCursor() != null) {
+            params.put("cursor", request.getCursor());
+        }
+        if (request.getFields() != null) {
+            params.put("fields", JSON.toJSONString(request.getFields()));
+        }
+        return params;
+    }
+
+    /**
+     * 解析响应为指定类型(支持指定数据字段名)
+     */
+    private <T> TencentApiResponse<T> parseResponse(String jsonStr, Class<T> clazz, String dataField) {
+        TencentApiResponse<T> response = parseResponse(jsonStr, clazz);
+        // 如果需要从data中提取特定字段,这里可以扩展
+        return response;
+    }
+}

+ 129 - 0
core/src/main/java/com/tzld/ad/marketing/client/AdvertiserClient.java

@@ -0,0 +1,129 @@
+package com.tzld.ad.marketing.client;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.ad.marketing.exception.TencentApiException;
+import com.tzld.ad.marketing.model.TencentApiResponse;
+import com.tzld.ad.marketing.model.advertiser.AdvertiserDTO;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.OkHttpClient;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 腾讯广告 广告账号客户端
+ * <p>
+ * 接口文档: https://developers.e.qq.com/v3.0/docs/api/advertiser_get
+ * <p>
+ * 包含接口:
+ * - advertiser/add: 添加腾讯广告服务商子客
+ * - advertiser/update: 更新腾讯广告广告主信息
+ * - advertiser/get: 查询腾讯广告广告主信息
+ * - advertiser_daily_budget/update: 更新腾讯广告广告主日预算
+ * - advertiser_daily_budget/get: 查询腾讯广告广告主日预算
+ */
+@Slf4j
+@Component
+public class AdvertiserClient extends AbstractTencentApiClient {
+
+    private static final String PATH_ADD = "/advertiser/add";
+    private static final String PATH_UPDATE = "/advertiser/update";
+    private static final String PATH_GET = "/advertiser/get";
+    private static final String PATH_DAILY_BUDGET_UPDATE = "/advertiser_daily_budget/update";
+    private static final String PATH_DAILY_BUDGET_GET = "/advertiser_daily_budget/get";
+
+    public AdvertiserClient(OkHttpClient tencentMarketingHttpClient, TencentMarketingApiConfig config) {
+        super(tencentMarketingHttpClient, config);
+    }
+
+    /**
+     * 查询广告主信息
+     *
+     * @param accessToken    访问令牌
+     * @param accountId      广告主ID
+     * @param accountIdList  广告主ID列表(可选)
+     * @return 广告主信息列表
+     */
+    public TencentApiResponse<TencentApiResponse.ListData<AdvertiserDTO.AdvertiserInfo>> getAdvertisers(
+            String accessToken, Long accountId, List<Long> accountIdList) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("account_id", accountId);
+        if (accountIdList != null && !accountIdList.isEmpty()) {
+            params.put("filtering", JSON.toJSONString(
+                        new java.util.HashMap<String, Object>() {{
+                            put("account_id_list", accountIdList);
+                        }}));
+        }
+
+        String response = doGet(PATH_GET, accessToken, params);
+        return parseListResponse(response, AdvertiserDTO.AdvertiserInfo.class);
+    }
+
+    /**
+     * 查询单个广告主信息
+     */
+    public TencentApiResponse<AdvertiserDTO.AdvertiserInfo> getAdvertiser(
+            String accessToken, Long accountId) {
+        TencentApiResponse<TencentApiResponse.ListData<AdvertiserDTO.AdvertiserInfo>> response =
+                getAdvertisers(accessToken, accountId, java.util.Collections.singletonList(accountId));
+        
+        TencentApiResponse<AdvertiserDTO.AdvertiserInfo> result = new TencentApiResponse<>();
+        result.setCode(response.getCode());
+        result.setMessage(response.getMessage());
+        result.setMessageCn(response.getMessageCn());
+        
+        if (response.isSuccess() && response.getData() != null && 
+                !response.getData().getList().isEmpty()) {
+            result.setData(response.getData().getList().get(0));
+        }
+        return result;
+    }
+
+    /**
+     * 更新广告主信息
+     *
+     * @param accessToken 访问令牌
+     * @param request     更新请求
+     * @return 响应
+     */
+    public TencentApiResponse<Void> updateAdvertiser(String accessToken, AdvertiserDTO.UpdateRequest request) {
+        String response = doPost(PATH_UPDATE, accessToken, request);
+        return parseResponse(response, Void.class);
+    }
+
+    /**
+     * 更新广告主日预算
+     *
+     * @param accessToken  访问令牌
+     * @param accountId    广告主ID
+     * @param dailyBudget  日预算(单位:分),0表示不限预算
+     * @return 响应
+     */
+    public TencentApiResponse<Void> updateDailyBudget(String accessToken, Long accountId, Long dailyBudget) {
+        Map<String, Object> body = new HashMap<>();
+        body.put("account_id", accountId);
+        body.put("daily_budget", dailyBudget);
+
+        String response = doPost(PATH_DAILY_BUDGET_UPDATE, accessToken, body);
+        return parseResponse(response, Void.class);
+    }
+
+    /**
+     * 查询广告主日预算
+     *
+     * @param accessToken 访问令牌
+     * @param accountId   广告主ID
+     * @return 日预算信息
+     */
+    public TencentApiResponse<AdvertiserDTO.DailyBudget> getDailyBudget(String accessToken, Long accountId) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("account_id", accountId);
+
+        String response = doGet(PATH_DAILY_BUDGET_GET, accessToken, params);
+        return parseResponse(response, AdvertiserDTO.DailyBudget.class);
+    }
+}

+ 42 - 0
core/src/main/java/com/tzld/ad/marketing/client/AgencyClient.java

@@ -0,0 +1,42 @@
+package com.tzld.ad.marketing.client;
+
+import com.alibaba.fastjson.JSON;
+import com.tzld.ad.marketing.model.TencentApiResponse;
+import com.tzld.ad.marketing.model.advertiser.AdvertiserDTO;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.OkHttpClient;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 腾讯广告 服务商账号客户端
+ * <p>
+ * 接口文档: https://developers.e.qq.com/v3.0/docs/api/agency_get
+ * <p>
+ * 包含接口:
+ * - agency/get: 查询腾讯广告服务商信息
+ */
+@Slf4j
+@Component
+public class AgencyClient extends AbstractTencentApiClient {
+
+    private static final String PATH_GET = "/agency/get";
+
+    public AgencyClient(OkHttpClient tencentMarketingHttpClient, TencentMarketingApiConfig config) {
+        super(tencentMarketingHttpClient, config);
+    }
+
+    /**
+     * 查询服务商信息
+     *
+     * @param accessToken 访问令牌
+     * @return 服务商信息
+     */
+    public TencentApiResponse<AdvertiserDTO.AgencyInfo> getAgency(String accessToken) {
+        Map<String, Object> params = new HashMap<>();
+        String response = doGet(PATH_GET, accessToken, params);
+        return parseResponse(response, AdvertiserDTO.AgencyInfo.class);
+    }
+}

+ 87 - 0
core/src/main/java/com/tzld/ad/marketing/client/BatchClient.java

@@ -0,0 +1,87 @@
+package com.tzld.ad.marketing.client;
+
+import com.alibaba.fastjson.JSON;
+import com.tzld.ad.marketing.model.TencentApiResponse;
+import com.tzld.ad.marketing.model.batch.BatchDTO;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.OkHttpClient;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 腾讯广告 批量操作客户端
+ * <p>
+ * 接口文档: https://developers.e.qq.com/v3.0/docs/api/batch_requests_add
+ * <p>
+ * 包含接口:
+ * - batch_requests/add: 批量操作
+ * - adgroups/update_daily_budget: 批量修改广告日限额
+ * - adgroups/update_configured_status: 批量修改广告开启/暂停状态
+ * - adgroups/update_bid_amount: 批量修改广告出价
+ * - adgroups/update_datetime: 批量修改广告投放起止时间
+ */
+@Slf4j
+@Component
+public class BatchClient extends AbstractTencentApiClient {
+
+    private static final String PATH_BATCH_REQUESTS = "/batch_requests/add";
+    private static final String PATH_UPDATE_DAILY_BUDGET = "/adgroups/update_daily_budget";
+    private static final String PATH_UPDATE_STATUS = "/adgroups/update_configured_status";
+    private static final String PATH_UPDATE_BID_AMOUNT = "/adgroups/update_bid_amount";
+    private static final String PATH_UPDATE_DATETIME = "/adgroups/update_datetime";
+
+    public BatchClient(OkHttpClient tencentMarketingHttpClient, TencentMarketingApiConfig config) {
+        super(tencentMarketingHttpClient, config);
+    }
+
+    /**
+     * 批量操作请求
+     *
+     * @param accessToken 访问令牌
+     * @param request     批量操作请求
+     * @return 批量操作响应
+     */
+    public TencentApiResponse<BatchDTO.BatchResponse> batchRequests(
+            String accessToken, BatchDTO.BatchRequest request) {
+        String response = doPost(PATH_BATCH_REQUESTS, accessToken, request);
+        return parseResponse(response, BatchDTO.BatchResponse.class);
+    }
+
+    /**
+     * 批量修改广告日限额
+     *
+     * @param accessToken 访问令牌
+     * @param request     请求参数
+     * @return 响应
+     */
+    public TencentApiResponse<Void> updateDailyBudget(String accessToken, BatchDTO.UpdateDailyBudgetRequest request) {
+        String response = doPost(PATH_UPDATE_DAILY_BUDGET, accessToken, request);
+        return parseResponse(response, Void.class);
+    }
+
+    /**
+     * 批量修改广告状态
+     *
+     * @param accessToken 访问令牌
+     * @param request     请求参数
+     * @return 响应
+     */
+    public TencentApiResponse<Void> updateStatus(String accessToken, BatchDTO.UpdateStatusRequest request) {
+        String response = doPost(PATH_UPDATE_STATUS, accessToken, request);
+        return parseResponse(response, Void.class);
+    }
+
+    /**
+     * 批量修改广告出价
+     *
+     * @param accessToken 访问令牌
+     * @param request     请求参数
+     * @return 响应
+     */
+    public TencentApiResponse<Void> updateBidAmount(String accessToken, BatchDTO.UpdateBidAmountRequest request) {
+        String response = doPost(PATH_UPDATE_BID_AMOUNT, accessToken, request);
+        return parseResponse(response, Void.class);
+    }
+}

+ 97 - 0
core/src/main/java/com/tzld/ad/marketing/client/ComponentClient.java

@@ -0,0 +1,97 @@
+package com.tzld.ad.marketing.client;
+
+import com.alibaba.fastjson.JSON;
+import com.tzld.ad.marketing.model.TencentApiResponse;
+import com.tzld.ad.marketing.model.component.ComponentDTO;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.OkHttpClient;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 腾讯广告 创意组件客户端
+ * <p>
+ * 接口文档: https://developers.e.qq.com/v3.0/docs/api/components_get
+ * <p>
+ * 包含接口:
+ * - components/get: 获取创意组件
+ * - components/add: 创建创意组件
+ * - components/delete: 删除创意组件
+ * - component_sharing/update: 修改组件共享
+ * - component_sharing/get: 查询组件共享
+ * - component_detail/get: 查询创意组件详情
+ */
+@Slf4j
+@Component
+public class ComponentClient extends AbstractTencentApiClient {
+
+    private static final String PATH_GET = "/components/get";
+    private static final String PATH_ADD = "/components/add";
+    private static final String PATH_DELETE = "/components/delete";
+    private static final String PATH_DETAIL_GET = "/component_detail/get";
+    private static final String PATH_SHARING_UPDATE = "/component_sharing/update";
+    private static final String PATH_SHARING_GET = "/component_sharing/get";
+
+    public ComponentClient(OkHttpClient tencentMarketingHttpClient, TencentMarketingApiConfig config) {
+        super(tencentMarketingHttpClient, config);
+    }
+
+    /**
+     * 查询创意组件列表
+     *
+     * @param accessToken 访问令牌
+     * @param request     查询请求
+     * @return 组件列表
+     */
+    public TencentApiResponse<TencentApiResponse.ListData<ComponentDTO.ComponentInfo>> getComponents(
+            String accessToken, ComponentDTO.GetRequest request) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("account_id", request.getAccountId());
+        params.put("is_deleted", false);
+
+        if (request.getFiltering() != null) {
+            params.put("filtering", JSON.toJSONString(request.getFiltering()));
+        }
+        if (request.getPage() != null) {
+            params.put("page", request.getPage());
+        }
+        if (request.getPageSize() != null) {
+            params.put("page_size", request.getPageSize());
+        }
+
+        String response = doGet(PATH_GET, accessToken, params);
+        return parseListResponse(response, ComponentDTO.ComponentInfo.class);
+    }
+
+    /**
+     * 创建创意组件
+     *
+     * @param accessToken 访问令牌
+     * @param request     创建请求
+     * @return 创建结果,包含组件ID
+     */
+    public TencentApiResponse<Long> addComponent(String accessToken, ComponentDTO.AddRequest request) {
+        String response = doPost(PATH_ADD, accessToken, request);
+        return parseResponse(response, Long.class);
+    }
+
+    /**
+     * 删除创意组件
+     *
+     * @param accessToken 访问令牌
+     * @param accountId   广告主ID
+     * @param componentId 组件ID
+     * @return 响应
+     */
+    public TencentApiResponse<Void> deleteComponent(String accessToken, Long accountId, Long componentId) {
+        Map<String, Object> body = new HashMap<>();
+        body.put("account_id", accountId);
+        body.put("component_id", componentId);
+
+        String response = doPost(PATH_DELETE, accessToken, body);
+        return parseResponse(response, Void.class);
+    }
+}

+ 189 - 0
core/src/main/java/com/tzld/ad/marketing/client/CreativeClient.java

@@ -0,0 +1,189 @@
+package com.tzld.ad.marketing.client;
+
+import com.alibaba.fastjson.JSON;
+import com.tzld.ad.marketing.model.TencentApiResponse;
+import com.tzld.ad.marketing.model.creative.CreativeDTO;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.OkHttpClient;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 腾讯广告 创意模块客户端
+ * <p>
+ * 接口文档: https://developers.e.qq.com/v3.0/docs/api/dynamic_creatives_get
+ * <p>
+ * 包含接口:
+ * - dynamic_creatives/get: 获取创意
+ * - dynamic_creatives/add: 创建创意
+ * - dynamic_creatives/update: 更新创意
+ * - dynamic_creatives/delete: 删除创意
+ */
+@Slf4j
+@Component
+public class CreativeClient extends AbstractTencentApiClient {
+
+    private static final String PATH_GET = "/dynamic_creatives/get";
+    private static final String PATH_ADD = "/dynamic_creatives/add";
+    private static final String PATH_UPDATE = "/dynamic_creatives/update";
+    private static final String PATH_DELETE = "/dynamic_creatives/delete";
+
+    public CreativeClient(OkHttpClient tencentMarketingHttpClient, TencentMarketingApiConfig config) {
+        super(tencentMarketingHttpClient, config);
+    }
+
+    /**
+     * 查询创意列表
+     *
+     * @param accessToken 访问令牌
+     * @param request     查询请求
+     * @return 创意列表
+     */
+    public TencentApiResponse<TencentApiResponse.ListData<CreativeDTO.CreativeInfo>> getCreatives(
+            String accessToken, CreativeDTO.GetRequest request) {
+        Map<String, Object> params = buildGetParams(request);
+        String response = doGet(PATH_GET, accessToken, params);
+        return parseListResponse(response, CreativeDTO.CreativeInfo.class);
+    }
+
+    /**
+     * 查询单个创意信息
+     *
+     * @param accessToken       访问令牌
+     * @param accountId         广告主ID
+     * @param dynamicCreativeId 创意ID
+     * @return 创意信息
+     */
+    public TencentApiResponse<CreativeDTO.CreativeInfo> getCreative(
+            String accessToken, Long accountId, Long dynamicCreativeId) {
+        CreativeDTO.GetRequest request = CreativeDTO.GetRequest.builder()
+                .accountId(accountId)
+                .filtering(CreativeDTO.GetRequest.Filtering.builder()
+                        .dynamicCreativeIdList(java.util.Collections.singletonList(dynamicCreativeId))
+                        .build())
+                .build();
+
+        TencentApiResponse<TencentApiResponse.ListData<CreativeDTO.CreativeInfo>> response =
+                getCreatives(accessToken, request);
+
+        TencentApiResponse<CreativeDTO.CreativeInfo> result = new TencentApiResponse<>();
+        result.setCode(response.getCode());
+        result.setMessage(response.getMessage());
+        result.setMessageCn(response.getMessageCn());
+
+        if (response.isSuccess() && response.getData() != null &&
+                !response.getData().getList().isEmpty()) {
+            result.setData(response.getData().getList().get(0));
+        }
+        return result;
+    }
+
+    /**
+     * 根据广告ID查询创意列表
+     *
+     * @param accessToken 访问令牌
+     * @param accountId   广告主ID
+     * @param adgroupId   广告ID
+     * @return 创意列表
+     */
+    public TencentApiResponse<TencentApiResponse.ListData<CreativeDTO.CreativeInfo>> getCreativesByAdGroup(
+            String accessToken, Long accountId, Long adgroupId) {
+        CreativeDTO.GetRequest request = CreativeDTO.GetRequest.builder()
+                .accountId(accountId)
+                .filtering(CreativeDTO.GetRequest.Filtering.builder()
+                        .adgroupIdList(java.util.Collections.singletonList(adgroupId))
+                        .build())
+                .build();
+
+        return getCreatives(accessToken, request);
+    }
+
+    /**
+     * 创建创意
+     *
+     * @param accessToken 访问令牌
+     * @param request     创建请求
+     * @return 创建结果,包含创意ID
+     */
+    public TencentApiResponse<Long> addCreative(String accessToken, CreativeDTO.AddRequest request) {
+        String response = doPost(PATH_ADD, accessToken, request);
+        return parseResponse(response, Long.class);
+    }
+
+    /**
+     * 更新创意
+     *
+     * @param accessToken 访问令牌
+     * @param request     更新请求
+     * @return 响应
+     */
+    public TencentApiResponse<Void> updateCreative(String accessToken, CreativeDTO.UpdateRequest request) {
+        String response = doPost(PATH_UPDATE, accessToken, request);
+        return parseResponse(response, Void.class);
+    }
+
+    /**
+     * 更新创意状态
+     *
+     * @param accessToken       访问令牌
+     * @param accountId         广告主ID
+     * @param dynamicCreativeId 创意ID
+     * @param configuredStatus  创意状态
+     * @return 响应
+     */
+    public TencentApiResponse<Void> updateCreativeStatus(String accessToken, Long accountId,
+                                                          Long dynamicCreativeId, String configuredStatus) {
+        Map<String, Object> body = new HashMap<>();
+        body.put("account_id", accountId);
+        body.put("dynamic_creative_id", dynamicCreativeId);
+        body.put("configured_status", configuredStatus);
+
+        String response = doPost(PATH_UPDATE, accessToken, body);
+        return parseResponse(response, Void.class);
+    }
+
+    /**
+     * 删除创意
+     *
+     * @param accessToken       访问令牌
+     * @param accountId         广告主ID
+     * @param dynamicCreativeId 创意ID
+     * @return 响应
+     */
+    public TencentApiResponse<Void> deleteCreative(String accessToken, Long accountId, Long dynamicCreativeId) {
+        Map<String, Object> body = new HashMap<>();
+        body.put("account_id", accountId);
+        body.put("dynamic_creative_id", dynamicCreativeId);
+
+        String response = doPost(PATH_DELETE, accessToken, body);
+        return parseResponse(response, Void.class);
+    }
+
+    private Map<String, Object> buildGetParams(CreativeDTO.GetRequest request) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("account_id", request.getAccountId());
+
+        if (request.getFiltering() != null) {
+            params.put("filtering", JSON.toJSONString(request.getFiltering()));
+        }
+        if (request.getPage() != null) {
+            params.put("page", request.getPage());
+        }
+        if (request.getPageSize() != null) {
+            params.put("page_size", request.getPageSize());
+        }
+        if (request.getPaginationMode() != null) {
+            params.put("pagination_mode", request.getPaginationMode());
+        }
+        if (request.getCursor() != null) {
+            params.put("cursor", request.getCursor());
+        }
+        if (request.getFields() != null) {
+            params.put("fields", JSON.toJSONString(request.getFields()));
+        }
+        return params;
+    }
+}

+ 138 - 0
core/src/main/java/com/tzld/ad/marketing/client/CustomAudienceClient.java

@@ -0,0 +1,138 @@
+package com.tzld.ad.marketing.client;
+
+import com.alibaba.fastjson.JSON;
+import com.tzld.ad.marketing.model.TencentApiResponse;
+import com.tzld.ad.marketing.model.audience.AudienceDTO;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.OkHttpClient;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 腾讯广告 客户人群客户端
+ * <p>
+ * 接口文档: https://developers.e.qq.com/v3.0/docs/api/custom_audiences_get
+ * <p>
+ * 包含接口:
+ * - custom_audiences/get: 获取人群列表
+ * - custom_audiences/add: 创建人群
+ * - custom_audience_files/get: 获取人群文件列表
+ * - custom_audience_files/add: 上传人群文件
+ * - audience_grant_relations/add: 人群推送
+ */
+@Slf4j
+@Component
+public class CustomAudienceClient extends AbstractTencentApiClient {
+
+    private static final String PATH_GET = "/custom_audiences/get";
+    private static final String PATH_ADD = "/custom_audiences/add";
+    private static final String PATH_FILE_GET = "/custom_audience_files/get";
+    private static final String PATH_FILE_ADD = "/custom_audience_files/add";
+    private static final String PATH_GRANT_ADD = "/audience_grant_relations/add";
+
+    public CustomAudienceClient(OkHttpClient tencentMarketingHttpClient, TencentMarketingApiConfig config) {
+        super(tencentMarketingHttpClient, config);
+    }
+
+    /**
+     * 获取人群列表
+     *
+     * @param accessToken 访问令牌
+     * @param request     查询请求
+     * @return 人群列表
+     */
+    public TencentApiResponse<TencentApiResponse.ListData<AudienceDTO.AudienceInfo>> getAudiences(
+            String accessToken, AudienceDTO.GetRequest request) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("account_id", request.getAccountId());
+
+        if (request.getAudienceId() != null) {
+            params.put("audience_id", request.getAudienceId());
+        }
+        if (request.getPage() != null) {
+            params.put("page", request.getPage());
+        }
+        if (request.getPageSize() != null) {
+            params.put("page_size", request.getPageSize());
+        }
+
+        String response = doGet(PATH_GET, accessToken, params);
+        return parseListResponse(response, AudienceDTO.AudienceInfo.class);
+    }
+
+    /**
+     * 创建人群
+     *
+     * @param accessToken 访问令牌
+     * @param request     创建请求
+     * @return 创建结果,包含人群ID
+     */
+    public TencentApiResponse<Long> addAudience(String accessToken, AudienceDTO.AddRequest request) {
+        String response = doPost(PATH_ADD, accessToken, request);
+        return parseResponse(response, Long.class);
+    }
+
+    /**
+     * 上传人群文件
+     *
+     * @param accessToken 访问令牌
+     * @param request     上传请求
+     * @return 上传结果
+     */
+    public TencentApiResponse<String> uploadAudienceFile(String accessToken, AudienceDTO.FileAddRequest request) {
+        Map<String, String> params = new HashMap<>();
+        params.put("account_id", String.valueOf(request.getAccountId()));
+        params.put("audience_id", String.valueOf(request.getAudienceId()));
+        params.put("user_id_type", request.getUserIdType());
+        params.put("signature", request.getSignature());
+        if (request.getOpenAppId() != null) {
+            params.put("open_app_id", request.getOpenAppId());
+        }
+
+        String response = doUpload(PATH_FILE_ADD, accessToken, params,
+                "file", "audience.txt", request.getFileData());
+        return parseResponse(response, String.class);
+    }
+
+    /**
+     * 获取人群文件列表
+     *
+     * @param accessToken 访问令牌
+     * @param accountId   广告主ID
+     * @param audienceId  人群ID
+     * @param page        页码
+     * @param pageSize    每页数量
+     * @return 人群文件列表
+     */
+    public TencentApiResponse<String> getAudienceFiles(String accessToken, Long accountId, Long audienceId,
+                                                         Integer page, Integer pageSize) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("account_id", accountId);
+        if (audienceId != null) {
+            params.put("audience_id", audienceId);
+        }
+        if (page != null) {
+            params.put("page", page);
+        }
+        if (pageSize != null) {
+            params.put("page_size", pageSize);
+        }
+
+        String response = doGet(PATH_FILE_GET, accessToken, params);
+        return parseResponse(response, String.class);
+    }
+
+    /**
+     * 人群推送
+     *
+     * @param accessToken 访问令牌
+     * @param request     推送请求
+     * @return 响应
+     */
+    public TencentApiResponse<Void> pushAudience(String accessToken, AudienceDTO.PushRequest request) {
+        String response = doPost(PATH_GRANT_ADD, accessToken, request);
+        return parseResponse(response, Void.class);
+    }
+}

+ 172 - 0
core/src/main/java/com/tzld/ad/marketing/client/DailyReportClient.java

@@ -0,0 +1,172 @@
+package com.tzld.ad.marketing.client;
+
+import com.alibaba.fastjson.JSON;
+import com.tzld.ad.marketing.model.TencentApiResponse;
+import com.tzld.ad.marketing.model.report.ReportDTO;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.OkHttpClient;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 腾讯广告 报表模块客户端
+ * <p>
+ * 接口文档: https://developers.e.qq.com/v3.0/docs/api/daily_reports_get
+ * <p>
+ * 包含接口:
+ * - daily_reports/get: 获取每日报表
+ */
+@Slf4j
+@Component
+public class DailyReportClient extends AbstractTencentApiClient {
+
+    private static final String PATH_GET = "/daily_reports/get";
+
+    public DailyReportClient(OkHttpClient tencentMarketingHttpClient, TencentMarketingApiConfig config) {
+        super(tencentMarketingHttpClient, config);
+    }
+
+    /**
+     * 获取每日报表
+     *
+     * @param accessToken 访问令牌
+     * @param request     查询请求
+     * @return 报表数据
+     */
+    public TencentApiResponse<TencentApiResponse.ListData<ReportDTO.DailyReportData>> getDailyReport(
+            String accessToken, ReportDTO.DailyReportGetRequest request) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("account_id", request.getAccountId());
+
+        if (request.getLevel() != null) {
+            params.put("level", request.getLevel());
+        }
+        if (request.getDateRange() != null) {
+            params.put("date_range", JSON.toJSONString(request.getDateRange()));
+        }
+        if (request.getTimeLine() != null) {
+            params.put("time_line", request.getTimeLine());
+        }
+        if (request.getFiltering() != null) {
+            params.put("filtering", JSON.toJSONString(request.getFiltering()));
+        }
+        if (request.getGroupBy() != null) {
+            params.put("group_by", JSON.toJSONString(request.getGroupBy()));
+        }
+        if (request.getFields() != null) {
+            params.put("fields", JSON.toJSONString(request.getFields()));
+        }
+        if (request.getPage() != null) {
+            params.put("page", request.getPage());
+        }
+        if (request.getPageSize() != null) {
+            params.put("page_size", request.getPageSize());
+        }
+
+        String response = doGet(PATH_GET, accessToken, params);
+        return parseListResponse(response, ReportDTO.DailyReportData.class);
+    }
+
+    /**
+     * 获取广告组报表
+     *
+     * @param accessToken 访问令牌
+     * @param accountId    广告主ID
+     * @param adgroupId    广告组ID(可选)
+     * @param startDate    开始日期
+     * @param endDate      结束日期
+     * @param page         页码
+     * @param pageSize     每页数量
+     * @return 报表数据
+     */
+    public TencentApiResponse<TencentApiResponse.ListData<ReportDTO.DailyReportData>> getAdGroupReport(
+            String accessToken, Long accountId, Long adgroupId, String startDate, String endDate,
+            Integer page, Integer pageSize) {
+        ReportDTO.DailyReportGetRequest request = ReportDTO.DailyReportGetRequest.builder()
+                .accountId(accountId)
+                .level("REPORT_LEVEL_ADGROUP")
+                .dateRange(ReportDTO.DateRange.builder()
+                        .startDate(startDate)
+                        .endDate(endDate)
+                        .build())
+                .timeLine("REQUEST_TIME")
+                .page(page != null ? page : 1)
+                .pageSize(pageSize != null ? pageSize : 10)
+                .build();
+
+        if (adgroupId != null) {
+            request.setFiltering(ReportDTO.Filtering.builder()
+                    .adgroupIdList(java.util.Collections.singletonList(adgroupId))
+                    .build());
+        }
+
+        return getDailyReport(accessToken, request);
+    }
+
+    /**
+     * 获取创意报表
+     *
+     * @param accessToken    访问令牌
+     * @param accountId      广告主ID
+     * @param creativeId     创意ID(可选)
+     * @param startDate      开始日期
+     * @param endDate        结束日期
+     * @param page           页码
+     * @param pageSize       每页数量
+     * @return 报表数据
+     */
+    public TencentApiResponse<TencentApiResponse.ListData<ReportDTO.DailyReportData>> getCreativeReport(
+            String accessToken, Long accountId, Long creativeId, String startDate, String endDate,
+            Integer page, Integer pageSize) {
+        ReportDTO.DailyReportGetRequest request = ReportDTO.DailyReportGetRequest.builder()
+                .accountId(accountId)
+                .level("REPORT_LEVEL_DYNAMIC_CREATIVE")
+                .dateRange(ReportDTO.DateRange.builder()
+                        .startDate(startDate)
+                        .endDate(endDate)
+                        .build())
+                .timeLine("REQUEST_TIME")
+                .page(page != null ? page : 1)
+                .pageSize(pageSize != null ? pageSize : 500)
+                .build();
+
+        if (creativeId != null) {
+            request.setFiltering(ReportDTO.Filtering.builder()
+                    .dynamicCreativeIdList(java.util.Collections.singletonList(creativeId))
+                    .build());
+        }
+
+        return getDailyReport(accessToken, request);
+    }
+
+    /**
+     * 获取组件报表
+     *
+     * @param accessToken 访问令牌
+     * @param accountId   广告主ID
+     * @param startDate   开始日期
+     * @param endDate     结束日期
+     * @param page        页码
+     * @param pageSize    每页数量
+     * @return 报表数据
+     */
+    public TencentApiResponse<TencentApiResponse.ListData<ReportDTO.DailyReportData>> getComponentReport(
+            String accessToken, Long accountId, String startDate, String endDate,
+            Integer page, Integer pageSize) {
+        ReportDTO.DailyReportGetRequest request = ReportDTO.DailyReportGetRequest.builder()
+                .accountId(accountId)
+                .level("REPORT_LEVEL_COMPONENT")
+                .dateRange(ReportDTO.DateRange.builder()
+                        .startDate(startDate)
+                        .endDate(endDate)
+                        .build())
+                .timeLine("REQUEST_TIME")
+                .page(page != null ? page : 1)
+                .pageSize(pageSize != null ? pageSize : 500)
+                .build();
+
+        return getDailyReport(accessToken, request);
+    }
+}

+ 59 - 0
core/src/main/java/com/tzld/ad/marketing/client/FundsClient.java

@@ -0,0 +1,59 @@
+package com.tzld.ad.marketing.client;
+
+import com.tzld.ad.marketing.model.TencentApiResponse;
+import com.tzld.ad.marketing.model.funds.FundsDTO;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.OkHttpClient;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 腾讯广告 资金模块客户端
+ * <p>
+ * 接口文档: https://developers.e.qq.com/v3.0/docs/api/funds_get
+ * <p>
+ * 包含接口:
+ * - funds/get: 获取账户资金
+ */
+@Slf4j
+@Component
+public class FundsClient extends AbstractTencentApiClient {
+
+    private static final String PATH_GET = "/funds/get";
+
+    public FundsClient(OkHttpClient tencentMarketingHttpClient, TencentMarketingApiConfig config) {
+        super(tencentMarketingHttpClient, config);
+    }
+
+    /**
+     * 获取账户资金信息
+     *
+     * @param accessToken 访问令牌
+     * @param request     查询请求
+     * @return 资金信息
+     */
+    public TencentApiResponse<FundsDTO.FundsInfo> getFunds(String accessToken, FundsDTO.GetRequest request) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("account_id", request.getAccountId());
+
+        String response = doGet(PATH_GET, accessToken, params);
+        return parseResponse(response, FundsDTO.FundsInfo.class);
+    }
+
+    /**
+     * 获取账户资金信息
+     *
+     * @param accessToken 访问令牌
+     * @param accountId    广告主ID
+     * @return 资金信息
+     */
+    public TencentApiResponse<FundsDTO.FundsInfo> getFundsByAccountId(String accessToken, Long accountId) {
+        FundsDTO.GetRequest request = FundsDTO.GetRequest.builder()
+                .accountId(accountId)
+                .build();
+
+        return getFunds(accessToken, request);
+    }
+}

+ 100 - 0
core/src/main/java/com/tzld/ad/marketing/client/ImageClient.java

@@ -0,0 +1,100 @@
+package com.tzld.ad.marketing.client;
+
+import com.alibaba.fastjson.JSON;
+import com.tzld.ad.marketing.model.TencentApiResponse;
+import com.tzld.ad.marketing.model.image.ImageDTO;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.OkHttpClient;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 腾讯广告 图片客户端
+ * <p>
+ * 接口文档: https://developers.e.qq.com/v3.0/docs/api/images_get
+ * <p>
+ * 包含接口:
+ * - images/get: 获取图片
+ * - images/add: 创建图片(上传图片)
+ * - images/update: 更新图片
+ * - images/delete: 删除图片
+ */
+@Slf4j
+@Component
+public class ImageClient extends AbstractTencentApiClient {
+
+    private static final String PATH_GET = "/images/get";
+    private static final String PATH_ADD = "/images/add";
+    private static final String PATH_UPDATE = "/images/update";
+    private static final String PATH_DELETE = "/images/delete";
+
+    public ImageClient(OkHttpClient tencentMarketingHttpClient, TencentMarketingApiConfig config) {
+        super(tencentMarketingHttpClient, config);
+    }
+
+    /**
+     * 查询图片列表
+     *
+     * @param accessToken 访问令牌
+     * @param request     查询请求
+     * @return 图片列表
+     */
+    public TencentApiResponse<TencentApiResponse.ListData<ImageDTO.ImageInfo>> getImages(
+            String accessToken, ImageDTO.GetRequest request) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("account_id", request.getAccountId());
+
+        if (request.getFiltering() != null) {
+            params.put("filtering", JSON.toJSONString(request.getFiltering()));
+        }
+        if (request.getPage() != null) {
+            params.put("page", request.getPage());
+        }
+        if (request.getPageSize() != null) {
+            params.put("page_size", request.getPageSize());
+        }
+
+        String response = doGet(PATH_GET, accessToken, params);
+        return parseListResponse(response, ImageDTO.ImageInfo.class);
+    }
+
+    /**
+     * 上传图片
+     *
+     * @param accessToken 访问令牌
+     * @param request     上传请求
+     * @return 上传结果
+     */
+    public TencentApiResponse<ImageDTO.AddResponse> addImage(String accessToken, ImageDTO.AddRequest request) {
+        Map<String, String> params = new HashMap<>();
+        params.put("account_id", String.valueOf(request.getAccountId()));
+        params.put("signature", request.getSignature());
+        if (request.getImageName() != null) {
+            params.put("image_name", request.getImageName());
+        }
+
+        String response = doUpload(PATH_ADD, accessToken, params,
+                "image_file", request.getImageName() != null ? request.getImageName() : "image.jpg",
+                request.getImageData());
+        return parseResponse(response, ImageDTO.AddResponse.class);
+    }
+
+    /**
+     * 删除图片
+     *
+     * @param accessToken 访问令牌
+     * @param accountId   广告主ID
+     * @param imageId     图片ID
+     * @return 响应
+     */
+    public TencentApiResponse<Void> deleteImage(String accessToken, Long accountId, String imageId) {
+        Map<String, Object> body = new HashMap<>();
+        body.put("account_id", accountId);
+        body.put("image_id", imageId);
+
+        String response = doPost(PATH_DELETE, accessToken, body);
+        return parseResponse(response, Void.class);
+    }
+}

+ 138 - 0
core/src/main/java/com/tzld/ad/marketing/client/OAuthClient.java

@@ -0,0 +1,138 @@
+package com.tzld.ad.marketing.client;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.ad.marketing.exception.TencentApiException;
+import com.tzld.ad.marketing.model.oauth.OAuthDTO;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.OkHttpClient;
+import org.springframework.stereotype.Component;
+
+/**
+ * 腾讯广告 OAuth 授权客户端
+ * <p>
+ * 接口文档: https://developers.e.qq.com/v3.0/docs/api/oauth_authorize
+ * <p>
+ * 包含接口:
+ * - oauth/authorize: 获取 Authorization Code
+ * - oauth/token: 获取 Token
+ * - oauth/refresh_token: 刷新 Refresh Token
+ */
+@Slf4j
+@Component
+public class OAuthClient extends AbstractTencentApiClient {
+
+    private static final String PATH_AUTHORIZE = "/oauth/authorize";
+    private static final String PATH_TOKEN = "/oauth/token";
+    private static final String PATH_REFRESH_TOKEN = "/oauth/refresh_token";
+
+    public OAuthClient(OkHttpClient tencentMarketingHttpClient, TencentMarketingApiConfig config) {
+        super(tencentMarketingHttpClient, config);
+    }
+
+    /**
+     * 构建授权URL
+     * <p>
+     * 引导用户访问该URL进行授权,授权后会回调到redirectUri并带上authorization_code
+     *
+     * @param state      状态码(防CSRF攻击)
+     * @param scope      权限范围,多个用逗号分隔
+     *                  - ads_management: 广告管理
+     *                  - account_management: 账号管理
+     *                  - ads_insights: 数据洞察
+     *                  - audience_management: 人群管理
+     *                  - user_actions: 用户行为
+     * @return 授权URL
+     */
+    public String buildAuthorizeUrl(String state, String scope) {
+        StringBuilder url = new StringBuilder(TencentMarketingApiConfig.BASE_URL);
+        url.append(PATH_AUTHORIZE);
+        url.append("?client_id=").append(getConfig().getClientId());
+        url.append("&redirect_uri=").append(urlEncode(getConfig().getRedirectUri()));
+        url.append("&response_type=code");
+        if (scope != null && !scope.isEmpty()) {
+            url.append("&scope=").append(urlEncode(scope));
+        }
+        if (state != null && !state.isEmpty()) {
+            url.append("&state=").append(state);
+        }
+        return url.toString();
+    }
+
+    /**
+     * 构建授权URL(使用默认权限范围)
+     */
+    public String buildAuthorizeUrl(String state) {
+        return buildAuthorizeUrl(state, "ads_management,account_management,ads_insights,audience_management,user_actions");
+    }
+
+    /**
+     * 通过 Authorization Code 获取 Access Token
+     *
+     * @param authorizationCode 授权码
+     * @return Token响应
+     */
+    public OAuthDTO.TokenResponse getToken(String authorizationCode) {
+        StringBuilder url = new StringBuilder(TencentMarketingApiConfig.BASE_URL);
+        url.append(PATH_TOKEN);
+        url.append("?client_id=").append(getConfig().getClientId());
+        url.append("&client_secret=").append(getConfig().getClientSecret());
+        url.append("&grant_type=authorization_code");
+        url.append("&authorization_code=").append(authorizationCode);
+        url.append("&redirect_uri=").append(urlEncode(getConfig().getRedirectUri()));
+
+        log.info("[OAuthClient] Getting token with authorization code");
+        String response = executeRequest(new okhttp3.Request.Builder()
+                .url(url.toString())
+                .get()
+                .build());
+
+        return parseTokenResponse(response);
+    }
+
+    /**
+     * 刷新 Access Token
+     *
+     * @param refreshToken 刷新令牌
+     * @return Token响应
+     */
+    public OAuthDTO.TokenResponse refreshToken(String refreshToken) {
+        StringBuilder url = new StringBuilder(TencentMarketingApiConfig.BASE_URL);
+        url.append(PATH_REFRESH_TOKEN);
+        url.append("?client_id=").append(getConfig().getClientId());
+        url.append("&client_secret=").append(getConfig().getClientSecret());
+        url.append("&grant_type=refresh_token");
+        url.append("&refresh_token=").append(refreshToken);
+
+        log.info("[OAuthClient] Refreshing token");
+        String response = executeRequest(new okhttp3.Request.Builder()
+                .url(url.toString())
+                .get()
+                .build());
+
+        return parseTokenResponse(response);
+    }
+
+    /**
+     * 解析Token响应
+     */
+    private OAuthDTO.TokenResponse parseTokenResponse(String jsonStr) {
+        JSONObject json = JSON.parseObject(jsonStr);
+        Integer code = json.getInteger("code");
+        if (code == null || code != 0) {
+            String message = json.getString("message");
+            String messageCn = json.getString("message_cn");
+            throw new TencentApiException(code, message, messageCn);
+        }
+
+        JSONObject data = json.getJSONObject("data");
+        return OAuthDTO.TokenResponse.builder()
+                .accessToken(data.getString("access_token"))
+                .refreshToken(data.getString("refresh_token"))
+                .expiresIn(data.getLong("access_token_expires_in"))
+                .refreshTokenExpiresIn(data.getLong("refresh_token_expires_in"))
+                .accountId(data.getLong("account_id"))
+                .scope(data.getJSONArray("scope").toJavaList(String.class))
+                .build();
+    }
+}

+ 58 - 0
core/src/main/java/com/tzld/ad/marketing/client/PageClient.java

@@ -0,0 +1,58 @@
+package com.tzld.ad.marketing.client;
+
+import com.alibaba.fastjson.JSON;
+import com.tzld.ad.marketing.model.TencentApiResponse;
+import com.tzld.ad.marketing.model.page.PageDTO;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.OkHttpClient;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 腾讯广告 落地页客户端
+ * <p>
+ * 接口文档: https://developers.e.qq.com/v3.0/docs/api/pages_get
+ * <p>
+ * 包含接口:
+ * - pages/get: 获取落地页列表
+ * - wechat_pages/get: 原生推广页列表
+ */
+@Slf4j
+@Component
+public class PageClient extends AbstractTencentApiClient {
+
+    private static final String PATH_GET = "/pages/get";
+    private static final String PATH_WECHAT_PAGES_GET = "/wechat_pages/get";
+
+    public PageClient(OkHttpClient tencentMarketingHttpClient, TencentMarketingApiConfig config) {
+        super(tencentMarketingHttpClient, config);
+    }
+
+    /**
+     * 查询落地页列表
+     *
+     * @param accessToken 访问令牌
+     * @param request     查询请求
+     * @return 落地页列表
+     */
+    public TencentApiResponse<TencentApiResponse.ListData<PageDTO.PageInfo>> getPages(
+            String accessToken, PageDTO.GetRequest request) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("account_id", request.getAccountId());
+
+        if (request.getFiltering() != null) {
+            params.put("filtering", JSON.toJSONString(request.getFiltering()));
+        }
+        if (request.getPage() != null) {
+            params.put("page", request.getPage());
+        }
+        if (request.getPageSize() != null) {
+            params.put("page_size", request.getPageSize());
+        }
+
+        String response = doGet(PATH_GET, accessToken, params);
+        return parseListResponse(response, PageDTO.PageInfo.class);
+    }
+}

+ 272 - 0
core/src/main/java/com/tzld/ad/marketing/client/RtaStrategyClient.java

@@ -0,0 +1,272 @@
+package com.tzld.ad.marketing.client;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.ad.marketing.model.TencentApiResponse;
+import com.tzld.ad.marketing.model.rta.RtaDTO;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.OkHttpClient;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 腾讯广告 RTA 策略管理客户端
+ * <p>
+ * API 域名: https://api.rta.qq.com/
+ * 鉴权方式: Authorization = md5(RtaId + Token + Time)
+ * <p>
+ * 包含接口:
+ * - /api/v1/RtaInfo: 查询RTA基本信息
+ * - /api/v1/target/get: 查询策略列表
+ * - /api/v1/target/set: 新增策略
+ * - /api/v1/target/delete: 删除策略
+ * - /api/v1/target/bind: 绑定策略
+ * - /api/v1/target/bind/delete: 解绑策略
+ * - /api/v1/target/bind/status: 查询绑定状态
+ */
+@Slf4j
+@Component
+public class RtaStrategyClient extends AbstractTencentApiClient {
+
+    private static final String RTA_BASE_URL = "https://api.rta.qq.com";
+    private static final String PATH_RTA_INFO = "/api/v1/RtaInfo";
+    private static final String PATH_TARGET_GET = "/api/v1/target/get";
+    private static final String PATH_TARGET_SET = "/api/v1/target/set";
+    private static final String PATH_TARGET_DELETE = "/api/v1/target/delete";
+    private static final String PATH_BIND = "/api/v1/target/bind";
+    private static final String PATH_BIND_DELETE = "/api/v1/target/bind/delete";
+    private static final String PATH_BIND_STATUS = "/api/v1/target/bind/status";
+
+    @Value("${rta.manage.rta-id:}")
+    private String rtaId;
+
+    @Value("${rta.manage.token:}")
+    private String rtaToken;
+
+    public RtaStrategyClient(OkHttpClient tencentMarketingHttpClient, TencentMarketingApiConfig config) {
+        super(tencentMarketingHttpClient, config);
+    }
+
+    /**
+     * 查询RTA基本信息
+     *
+     * @return RTA基本信息
+     */
+    public TencentApiResponse<RtaDTO.RtaInfo> getRtaInfo() {
+        String url = RTA_BASE_URL + PATH_RTA_INFO;
+        String time = String.valueOf(System.currentTimeMillis() / 1000);
+
+        okhttp3.Request request = new okhttp3.Request.Builder()
+                .url(url)
+                .get()
+                .addHeader("RtaId", rtaId)
+                .addHeader("Authorization", buildAuthorization(time))
+                .addHeader("Time", time)
+                .addHeader("Content-Type", "application/json")
+                .build();
+
+        String response = executeRequest(request);
+        return parseRtaResponse(response, RtaDTO.RtaInfo.class);
+    }
+
+    /**
+     * 查询策略列表
+     *
+     * @param rtaRequest 查询请求
+     * @return 策略列表
+     */
+    public TencentApiResponse<TencentApiResponse.ListData<RtaDTO.TargetInfo>> getTargets(RtaDTO.TargetGetRequest rtaRequest) {
+        String url = RTA_BASE_URL + PATH_TARGET_GET;
+        String time = String.valueOf(System.currentTimeMillis() / 1000);
+        String jsonBody = JSON.toJSONString(rtaRequest);
+
+        okhttp3.RequestBody body = okhttp3.RequestBody.create(
+                okhttp3.MediaType.parse("application/json; charset=utf-8"), jsonBody);
+        okhttp3.Request request = new okhttp3.Request.Builder()
+                .url(url)
+                .post(body)
+                .addHeader("RtaId", rtaId)
+                .addHeader("Authorization", buildAuthorization(time))
+                .addHeader("Time", time)
+                .addHeader("Content-Type", "application/json")
+                .build();
+
+        String response = executeRequest(request);
+        return parseRtaListResponse(response, RtaDTO.TargetInfo.class);
+    }
+
+    /**
+     * 新增策略
+     *
+     * @param request 新增请求
+     * @return 响应
+     */
+    public TencentApiResponse<Void> addTarget(RtaDTO.TargetSetRequest request) {
+        String url = RTA_BASE_URL + PATH_TARGET_SET;
+        String time = String.valueOf(System.currentTimeMillis() / 1000);
+        String jsonBody = JSON.toJSONString(request);
+
+        okhttp3.RequestBody body = okhttp3.RequestBody.create(
+                okhttp3.MediaType.parse("application/json; charset=utf-8"), jsonBody);
+        okhttp3.Request httpRequest = new okhttp3.Request.Builder()
+                .url(url)
+                .post(body)
+                .addHeader("RtaId", rtaId)
+                .addHeader("Authorization", buildAuthorization(time))
+                .addHeader("Time", time)
+                .addHeader("Content-Type", "application/json")
+                .build();
+
+        String response = executeRequest(httpRequest);
+        return parseRtaResponse(response, Void.class);
+    }
+
+    /**
+     * 删除策略
+     *
+     * @param request 删除请求
+     * @return 响应
+     */
+    public TencentApiResponse<Void> deleteTarget(RtaDTO.TargetDeleteRequest request) {
+        String url = RTA_BASE_URL + PATH_TARGET_DELETE;
+        String time = String.valueOf(System.currentTimeMillis() / 1000);
+        String jsonBody = JSON.toJSONString(request);
+
+        okhttp3.RequestBody body = okhttp3.RequestBody.create(
+                okhttp3.MediaType.parse("application/json; charset=utf-8"), jsonBody);
+        okhttp3.Request httpRequest = new okhttp3.Request.Builder()
+                .url(url)
+                .post(body)
+                .addHeader("RtaId", rtaId)
+                .addHeader("Authorization", buildAuthorization(time))
+                .addHeader("Time", time)
+                .addHeader("Content-Type", "application/json")
+                .build();
+
+        String response = executeRequest(httpRequest);
+        return parseRtaResponse(response, Void.class);
+    }
+
+    /**
+     * 绑定策略
+     *
+     * @param request 绑定请求
+     * @return 响应
+     */
+    public TencentApiResponse<Void> bindTarget(RtaDTO.TargetBindRequest request) {
+        String url = RTA_BASE_URL + PATH_BIND;
+        String time = String.valueOf(System.currentTimeMillis() / 1000);
+        String jsonBody = JSON.toJSONString(request);
+
+        okhttp3.RequestBody body = okhttp3.RequestBody.create(
+                okhttp3.MediaType.parse("application/json; charset=utf-8"), jsonBody);
+        okhttp3.Request httpRequest = new okhttp3.Request.Builder()
+                .url(url)
+                .post(body)
+                .addHeader("RtaId", rtaId)
+                .addHeader("Authorization", buildAuthorization(time))
+                .addHeader("Time", time)
+                .addHeader("Content-Type", "application/json")
+                .build();
+
+        String response = executeRequest(httpRequest);
+        return parseRtaResponse(response, Void.class);
+    }
+
+    /**
+     * 解绑策略
+     *
+     * @param request 解绑请求
+     * @return 响应
+     */
+    public TencentApiResponse<Void> unbindTarget(RtaDTO.TargetBindRequest request) {
+        String url = RTA_BASE_URL + PATH_BIND_DELETE;
+        String time = String.valueOf(System.currentTimeMillis() / 1000);
+        String jsonBody = JSON.toJSONString(request);
+
+        okhttp3.RequestBody body = okhttp3.RequestBody.create(
+                okhttp3.MediaType.parse("application/json; charset=utf-8"), jsonBody);
+        okhttp3.Request httpRequest = new okhttp3.Request.Builder()
+                .url(url)
+                .post(body)
+                .addHeader("RtaId", rtaId)
+                .addHeader("Authorization", buildAuthorization(time))
+                .addHeader("Time", time)
+                .addHeader("Content-Type", "application/json")
+                .build();
+
+        String response = executeRequest(httpRequest);
+        return parseRtaResponse(response, Void.class);
+    }
+
+    /**
+     * 构建Authorization
+     * Authorization = md5(RtaId + Token + Time)
+     */
+    private String buildAuthorization(String time) {
+        String raw = rtaId + rtaToken + time;
+        return md5(raw);
+    }
+
+    private String md5(String input) {
+        try {
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8));
+            StringBuilder sb = new StringBuilder(32);
+            for (byte b : digest) {
+                sb.append(String.format("%02x", b & 0xff));
+            }
+            return sb.toString();
+        } catch (Exception e) {
+            throw new RuntimeException("MD5 failed", e);
+        }
+    }
+
+    /**
+     * 解析RTA API响应
+     */
+    private <T> TencentApiResponse<T> parseRtaResponse(String jsonStr, Class<T> clazz) {
+        JSONObject json = JSON.parseObject(jsonStr);
+        TencentApiResponse<T> response = new TencentApiResponse<>();
+        response.setCode(json.getInteger("code"));
+        
+        // RTA API返回格式: {"status":"success","code":0,"data":{...}}
+        if ("success".equals(json.getString("status"))) {
+            response.setCode(0);
+        }
+        
+        Object data = json.get("data");
+        if (data != null && clazz != Void.class) {
+            response.setData(JSON.parseObject(JSON.toJSONString(data), clazz));
+        }
+        return response;
+    }
+
+    private <T> TencentApiResponse<TencentApiResponse.ListData<T>> parseRtaListResponse(String jsonStr, Class<T> clazz) {
+        JSONObject json = JSON.parseObject(jsonStr);
+        TencentApiResponse<TencentApiResponse.ListData<T>> response = new TencentApiResponse<>();
+        response.setCode(json.getInteger("code"));
+        
+        if ("success".equals(json.getString("status"))) {
+            response.setCode(0);
+        }
+        
+        Object data = json.get("data");
+        if (data != null) {
+            TencentApiResponse.ListData<T> listData = new TencentApiResponse.ListData<>();
+            if (data instanceof java.util.List) {
+                java.util.List<T> list = ((java.util.List<?>) data).stream()
+                        .map(item -> JSON.parseObject(JSON.toJSONString(item), clazz))
+                        .collect(java.util.stream.Collectors.toList());
+                listData.setList(list);
+            }
+            response.setData(listData);
+        }
+        return response;
+    }
+}

+ 60 - 0
core/src/main/java/com/tzld/ad/marketing/client/TencentMarketingApiConfig.java

@@ -0,0 +1,60 @@
+package com.tzld.ad.marketing.client;
+
+import lombok.Data;
+import okhttp3.ConnectionPool;
+import okhttp3.OkHttpClient;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 腾讯广告 Marketing API 统一配置类
+ * <p>
+ * API 域名: https://api.e.qq.com
+ * 文档地址: https://developers.e.qq.com/v3.0/docs/apilist
+ */
+@Data
+@Configuration
+public class TencentMarketingApiConfig {
+
+    public static final String BASE_URL = "https://api.e.qq.com";
+    public static final String API_VERSION = "v3.0";
+    public static final String FULL_BASE_URL = BASE_URL + "/" + API_VERSION;
+
+    @Value("${tencent.marketing.client-id:}")
+    private String clientId;
+
+    @Value("${tencent.marketing.client-secret:}")
+    private String clientSecret;
+
+    @Value("${tencent.marketing.redirect-uri:}")
+    private String redirectUri;
+
+    @Value("${tencent.marketing.connect-timeout:5000}")
+    private int connectTimeout;
+
+    @Value("${tencent.marketing.read-timeout:30000}")
+    private int readTimeout;
+
+    @Value("${tencent.marketing.write-timeout:30000}")
+    private int writeTimeout;
+
+    @Value("${tencent.marketing.max-connections:100}")
+    private int maxConnections;
+
+    @Value("${tencent.marketing.retry-count:3}")
+    private int retryCount;
+
+    @Bean("tencentMarketingHttpClient")
+    public OkHttpClient tencentMarketingHttpClient() {
+        return new OkHttpClient.Builder()
+                .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
+                .readTimeout(readTimeout, TimeUnit.MILLISECONDS)
+                .writeTimeout(writeTimeout, TimeUnit.MILLISECONDS)
+                .connectionPool(new ConnectionPool(maxConnections, 5, TimeUnit.MINUTES))
+                .retryOnConnectionFailure(retryCount > 0)
+                .build();
+    }
+}

+ 75 - 0
core/src/main/java/com/tzld/ad/marketing/client/UserActionClient.java

@@ -0,0 +1,75 @@
+package com.tzld.ad.marketing.client;
+
+import com.alibaba.fastjson.JSON;
+import com.tzld.ad.marketing.model.TencentApiResponse;
+import com.tzld.ad.marketing.model.useraction.UserActionDTO;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 腾讯广告 用户行为数据客户端
+ * <p>
+ * 接口文档: https://developers.e.qq.com/v3.0/docs/api/user_actions_add
+ * <p>
+ * 包含接口:
+ * - user_actions/add: 添加用户行为数据
+ */
+@Slf4j
+@Component
+public class UserActionClient extends AbstractTencentApiClient {
+
+    private static final String PATH_ADD = "/user_actions/add";
+
+    public UserActionClient(OkHttpClient tencentMarketingHttpClient, TencentMarketingApiConfig config) {
+        super(tencentMarketingHttpClient, config);
+    }
+
+    /**
+     * 添加用户行为数据
+     *
+     * @param accessToken 访问令牌
+     * @param request     添加请求
+     * @return 响应
+     */
+    public TencentApiResponse<Void> addUserActions(String accessToken, UserActionDTO.UserActionAddRequest request) {
+        String response = doPost(PATH_ADD, accessToken, request);
+        return parseResponse(response, Void.class);
+    }
+
+    /**
+     * 添加用户行为数据(使用原始 JSON 字符串)
+     *
+     * @param accessToken 访问令牌
+     * @param jsonBody    JSON 请求体
+     * @return 原始响应字符串
+     */
+    public String addUserActionsRaw(String accessToken, String jsonBody) {
+        return doPostRaw(PATH_ADD, accessToken, jsonBody);
+    }
+
+    /**
+     * 添加单个用户行为
+     *
+     * @param accessToken     访问令牌
+     * @param accountId       广告主ID
+     * @param userActionSetId 数据源ID
+     * @param action          用户行为
+     * @return 响应
+     */
+    public TencentApiResponse<Void> addSingleUserAction(String accessToken, Long accountId,
+                                                         Long userActionSetId, UserActionDTO.UserAction action) {
+        UserActionDTO.UserActionAddRequest request = UserActionDTO.UserActionAddRequest.builder()
+                .accountId(accountId)
+                .userActionSetId(userActionSetId)
+                .actions(java.util.Collections.singletonList(action))
+                .build();
+
+        return addUserActions(accessToken, request);
+    }
+}

+ 73 - 0
core/src/main/java/com/tzld/ad/marketing/client/UserActionSetClient.java

@@ -0,0 +1,73 @@
+package com.tzld.ad.marketing.client;
+
+import com.alibaba.fastjson.JSON;
+import com.tzld.ad.marketing.model.TencentApiResponse;
+import com.tzld.ad.marketing.model.useraction.UserActionDTO;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.OkHttpClient;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 腾讯广告 用户行为数据源客户端
+ * <p>
+ * 接口文档: https://developers.e.qq.com/v3.0/docs/api/user_action_sets_add
+ * <p>
+ * 包含接口:
+ * - user_action_sets/get: 获取用户行为数据源
+ * - user_action_sets/add: 创建用户行为数据源
+ */
+@Slf4j
+@Component
+public class UserActionSetClient extends AbstractTencentApiClient {
+
+    private static final String PATH_GET = "/user_action_sets/get";
+    private static final String PATH_ADD = "/user_action_sets/add";
+
+    public UserActionSetClient(OkHttpClient tencentMarketingHttpClient, TencentMarketingApiConfig config) {
+        super(tencentMarketingHttpClient, config);
+    }
+
+    /**
+     * 获取用户行为数据源
+     *
+     * @param accessToken 访问令牌
+     * @param request     查询请求
+     * @return 数据源列表
+     */
+    public TencentApiResponse<TencentApiResponse.ListData<UserActionDTO.UserActionSetInfo>> getUserActionSets(
+            String accessToken, UserActionDTO.UserActionSetGetRequest request) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("account_id", request.getAccountId());
+
+        if (request.getUserActionSetId() != null) {
+            params.put("user_action_set_id", request.getUserActionSetId());
+        }
+        if (request.getTypeList() != null && !request.getTypeList().isEmpty()) {
+            params.put("type", JSON.toJSONString(request.getTypeList()));
+        }
+        if (request.getMobileAppId() != null) {
+            params.put("mobile_app_id", request.getMobileAppId());
+        }
+        if (request.getWechatAppId() != null) {
+            params.put("wechat_app_id", request.getWechatAppId());
+        }
+
+        String response = doGet(PATH_GET, accessToken, params);
+        return parseListResponse(response, UserActionDTO.UserActionSetInfo.class);
+    }
+
+    /**
+     * 创建用户行为数据源
+     *
+     * @param accessToken 访问令牌
+     * @param request     创建请求
+     * @return 创建结果,包含数据源ID
+     */
+    public TencentApiResponse<Long> addUserActionSet(String accessToken, UserActionDTO.UserActionSetAddRequest request) {
+        String response = doPost(PATH_ADD, accessToken, request);
+        return parseResponse(response, Long.class);
+    }
+}

+ 140 - 0
core/src/main/java/com/tzld/ad/marketing/client/VideoClient.java

@@ -0,0 +1,140 @@
+package com.tzld.ad.marketing.client;
+
+import com.alibaba.fastjson.JSON;
+import com.tzld.ad.marketing.model.TencentApiResponse;
+import com.tzld.ad.marketing.model.video.VideoDTO;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.MediaType;
+import okhttp3.MultipartBody;
+import okhttp3.OkHttpClient;
+import okhttp3.RequestBody;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 腾讯广告 视频模块客户端
+ * <p>
+ * 接口文档: https://developers.e.qq.com/v3.0/docs/api/videos_get
+ * <p>
+ * 包含接口:
+ * - videos/get: 获取视频列表
+ * - videos/add: 上传视频
+ */
+@Slf4j
+@Component
+public class VideoClient extends AbstractTencentApiClient {
+
+    private static final String PATH_GET = "/videos/get";
+    private static final String PATH_ADD = "/videos/add";
+
+    public VideoClient(OkHttpClient tencentMarketingHttpClient, TencentMarketingApiConfig config) {
+        super(tencentMarketingHttpClient, config);
+    }
+
+    /**
+     * 查询视频列表
+     *
+     * @param accessToken 访问令牌
+     * @param request     查询请求
+     * @return 视频列表
+     */
+    public TencentApiResponse<TencentApiResponse.ListData<VideoDTO.VideoInfo>> getVideos(
+            String accessToken, VideoDTO.GetRequest request) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("account_id", request.getAccountId());
+
+        if (request.getFiltering() != null) {
+            params.put("filtering", JSON.toJSONString(request.getFiltering()));
+        }
+        if (request.getPage() != null) {
+            params.put("page", request.getPage());
+        }
+        if (request.getPageSize() != null) {
+            params.put("page_size", request.getPageSize());
+        }
+
+        String response = doGet(PATH_GET, accessToken, params);
+        return parseListResponse(response, VideoDTO.VideoInfo.class);
+    }
+
+    /**
+     * 获取单个视频信息
+     *
+     * @param accessToken 访问令牌
+     * @param accountId    广告主ID
+     * @param videoId      视频ID
+     * @return 视频信息
+     */
+    public TencentApiResponse<VideoDTO.VideoInfo> getVideo(
+            String accessToken, Long accountId, Long videoId) {
+        VideoDTO.GetRequest request = VideoDTO.GetRequest.builder()
+                .accountId(accountId)
+                .filtering(VideoDTO.GetRequest.Filtering.builder()
+                        .videoIdList(java.util.Collections.singletonList(videoId))
+                        .build())
+                .build();
+
+        TencentApiResponse<TencentApiResponse.ListData<VideoDTO.VideoInfo>> response =
+                getVideos(accessToken, request);
+
+        TencentApiResponse<VideoDTO.VideoInfo> result = new TencentApiResponse<>();
+        result.setCode(response.getCode());
+        result.setMessage(response.getMessage());
+        result.setMessageCn(response.getMessageCn());
+
+        if (response.isSuccess() && response.getData() != null &&
+                !response.getData().getList().isEmpty()) {
+            result.setData(response.getData().getList().get(0));
+        }
+        return result;
+    }
+
+    /**
+     * 上传视频
+     *
+     * @param accessToken 访问令牌
+     * @param request     上传请求
+     * @return 上传结果
+     */
+    public TencentApiResponse<VideoDTO.AddResponse> addVideo(String accessToken, VideoDTO.AddRequest request) {
+        Map<String, String> params = new HashMap<>();
+        params.put("account_id", String.valueOf(request.getAccountId()));
+        params.put("signature", request.getSignature());
+        if (request.getDescription() != null) {
+            params.put("description", request.getDescription());
+        }
+
+        String fileName = request.getFileName() != null ? request.getFileName() : "video.mp4";
+        String response = doUpload(PATH_ADD, accessToken, params,
+                "video_file", fileName, request.getVideoData());
+        return parseResponse(response, VideoDTO.AddResponse.class);
+    }
+
+    /**
+     * 上传视频文件
+     *
+     * @param accessToken 访问令牌
+     * @param accountId    广告主ID
+     * @param signature    MD5签名
+     * @param description  视频描述
+     * @param videoData    视频数据
+     * @param fileName     文件名
+     * @return 上传结果
+     */
+    public TencentApiResponse<VideoDTO.AddResponse> uploadVideo(
+            String accessToken, Long accountId, String signature, String description,
+            byte[] videoData, String fileName) {
+        VideoDTO.AddRequest request = VideoDTO.AddRequest.builder()
+                .accountId(accountId)
+                .signature(signature)
+                .description(description)
+                .videoData(videoData)
+                .fileName(fileName)
+                .build();
+
+        return addVideo(accessToken, request);
+    }
+}

+ 88 - 0
core/src/main/java/com/tzld/ad/marketing/client/WechatClient.java

@@ -0,0 +1,88 @@
+package com.tzld.ad.marketing.client;
+
+import com.alibaba.fastjson.JSON;
+import com.tzld.ad.marketing.model.TencentApiResponse;
+import com.tzld.ad.marketing.model.wechat.WechatDTO;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.OkHttpClient;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 腾讯广告 公众号客户端
+ * <p>
+ * 接口文档: https://developers.e.qq.com/v3.0/docs/api/profiles_add
+ * <p>
+ * 包含接口:
+ * - profiles/add: 创建朋友圈头像昵称跳转页
+ * - profiles/delete: 删除朋友圈头像昵称跳转页
+ * - profiles/get: 获取朋友圈头像昵称跳转页
+ */
+@Slf4j
+@Component
+public class WechatClient extends AbstractTencentApiClient {
+
+    private static final String PATH_PROFILES_ADD = "/profiles/add";
+    private static final String PATH_PROFILES_DELETE = "/profiles/delete";
+    private static final String PATH_PROFILES_GET = "/profiles/get";
+
+    public WechatClient(OkHttpClient tencentMarketingHttpClient, TencentMarketingApiConfig config) {
+        super(tencentMarketingHttpClient, config);
+    }
+
+    /**
+     * 创建朋友圈头像昵称跳转页
+     *
+     * @param accessToken 访问令牌
+     * @param request     创建请求
+     * @return 创建结果,包含Profile ID
+     */
+    public TencentApiResponse<Long> addProfile(String accessToken, WechatDTO.ProfileAddRequest request) {
+        String response = doPost(PATH_PROFILES_ADD, accessToken, request);
+        return parseResponse(response, Long.class);
+    }
+
+    /**
+     * 删除朋友圈头像昵称跳转页
+     *
+     * @param accessToken 访问令牌
+     * @param accountId   广告主ID
+     * @param profileId   Profile ID
+     * @return 响应
+     */
+    public TencentApiResponse<Void> deleteProfile(String accessToken, Long accountId, Long profileId) {
+        Map<String, Object> body = new HashMap<>();
+        body.put("account_id", accountId);
+        body.put("profile_id", profileId);
+
+        String response = doPost(PATH_PROFILES_DELETE, accessToken, body);
+        return parseResponse(response, Void.class);
+    }
+
+    /**
+     * 获取朋友圈头像昵称跳转页列表
+     *
+     * @param accessToken 访问令牌
+     * @param accountId   广告主ID
+     * @param page        页码
+     * @param pageSize    每页数量
+     * @return Profile列表
+     */
+    public TencentApiResponse<TencentApiResponse.ListData<WechatDTO.ProfileInfo>> getProfiles(
+            String accessToken, Long accountId, Integer page, Integer pageSize) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("account_id", accountId);
+        if (page != null) {
+            params.put("page", page);
+        }
+        if (pageSize != null) {
+            params.put("page_size", pageSize);
+        }
+
+        String response = doGet(PATH_PROFILES_GET, accessToken, params);
+        return parseListResponse(response, WechatDTO.ProfileInfo.class);
+    }
+}

+ 84 - 0
core/src/main/java/com/tzld/ad/marketing/exception/TencentApiException.java

@@ -0,0 +1,84 @@
+package com.tzld.ad.marketing.exception;
+
+import lombok.Getter;
+
+/**
+ * 腾讯广告 API 异常
+ */
+@Getter
+public class TencentApiException extends RuntimeException {
+
+    /** 错误码 */
+    private final Integer code;
+
+    /** 英文错误信息 */
+    private final String message;
+
+    /** 中文错误信息 */
+    private final String messageCn;
+
+    /** 请求 ID */
+    private final String requestId;
+
+    public TencentApiException(Integer code, String message, String messageCn) {
+        super(messageCn != null ? messageCn : message);
+        this.code = code;
+        this.message = message;
+        this.messageCn = messageCn;
+        this.requestId = null;
+    }
+
+    public TencentApiException(Integer code, String message, String messageCn, String requestId) {
+        super(messageCn != null ? messageCn : message);
+        this.code = code;
+        this.message = message;
+        this.messageCn = messageCn;
+        this.requestId = requestId;
+    }
+
+    public TencentApiException(Integer code, String message, String messageCn, Throwable cause) {
+        super(messageCn != null ? messageCn : message, cause);
+        this.code = code;
+        this.message = message;
+        this.messageCn = messageCn;
+        this.requestId = null;
+    }
+
+    /**
+     * 创建通用错误异常
+     */
+    public static TencentApiException of(Integer code, String message) {
+        return new TencentApiException(code, message, null);
+    }
+
+    /**
+     * 创建带中英文错误信息的异常
+     */
+    public static TencentApiException of(Integer code, String message, String messageCn) {
+        return new TencentApiException(code, message, messageCn);
+    }
+
+    /**
+     * 常见错误码
+     */
+    public static class ErrorCode {
+        /** 成功 */
+        public static final int SUCCESS = 0;
+        /** 系统错误 */
+        public static final int SYSTEM_ERROR = 1;
+        /** 接口不存在 */
+        public static final int API_NOT_FOUND = 2;
+        /** 参数错误 */
+        public static final int PARAM_ERROR = 3;
+        /** Token 无效 */
+        public static final int TOKEN_INVALID = 4;
+        /** Token 过期 */
+        public static final int TOKEN_EXPIRED = 5;
+        /** 权限不足 */
+        public static final int PERMISSION_DENIED = 7;
+        /** 请求频率超限 */
+        public static final int RATE_LIMIT_EXCEEDED = 8;
+        /** 广告主数据错误 */
+        public static final int ADVERTISER_DATA_ERROR = 100;
+    }
+}

+ 98 - 0
core/src/main/java/com/tzld/ad/marketing/model/TencentApiResponse.java

@@ -0,0 +1,98 @@
+package com.tzld.ad.marketing.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 腾讯广告 Marketing API 统一响应封装
+ * <p>
+ * 响应格式示例:
+ * {
+ *   "code": 0,
+ *   "message": "",
+ *   "message_cn": "",
+ *   "data": {},
+ *   "page_info": {}
+ * }
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class TencentApiResponse<T> {
+
+    /** 响应码,0 表示成功 */
+    private Integer code;
+
+    /** 英文错误信息 */
+    private String message;
+
+    /** 中文错误信息 */
+    private String messageCn;
+
+    /** 响应数据 */
+    private T data;
+
+    /** 分页信息 */
+    private PageInfo pageInfo;
+
+    /** 请求 ID,用于问题排查 */
+    private String requestId;
+
+    /**
+     * 判断请求是否成功
+     */
+    public boolean isSuccess() {
+        return code != null && code == 0;
+    }
+
+    /**
+     * 获取错误信息(优先中文)
+     */
+    public String getErrorMessage() {
+        if (messageCn != null && !messageCn.isEmpty()) {
+            return messageCn;
+        }
+        return message;
+    }
+
+    /**
+     * 分页信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class PageInfo {
+        /** 当前页码 */
+        private Integer page;
+
+        /** 每页数量 */
+        private Integer pageSize;
+
+        /** 总数量 */
+        private Integer totalNum;
+
+        /** 总页数 */
+        private Integer totalPage;
+
+        /** 游标分页 - 下一页游标 */
+        private String nextCursor;
+    }
+
+    /**
+     * 带列表数据的响应
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class ListData<T> {
+        private List<T> list;
+        private PageInfo pageInfo;
+    }
+}

+ 231 - 0
core/src/main/java/com/tzld/ad/marketing/model/adgroup/AdGroupDTO.java

@@ -0,0 +1,231 @@
+package com.tzld.ad.marketing.model.adgroup;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 广告模块相关 DTO
+ */
+public class AdGroupDTO {
+
+    /**
+     * 广告信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class AdGroupInfo {
+        /** 广告ID */
+        private Long adgroupId;
+
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 广告组ID */
+        private Long campaignId;
+
+        /** 广告名称 */
+        private String adgroupName;
+
+        /** 广告状态 */
+        private String configuredStatus;
+
+        /** 系统状态 */
+        private String systemStatus;
+
+        /** 出价类型 */
+        private String bidType;
+
+        /** 出价金额(单位:分) */
+        private Integer bidAmount;
+
+        /** 日预算(单位:分) */
+        private Long dailyBudget;
+
+        /** 开始日期 */
+        private String startDate;
+
+        /** 结束日期 */
+        private String endDate;
+
+        /** 定向设置 */
+        private Targeting targeting;
+
+        /** 创建时间 */
+        private String createTime;
+
+        /** 最后修改时间 */
+        private String lastModifiedTime;
+    }
+
+    /**
+     * 定向设置
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class Targeting {
+        /** 年龄范围 */
+        private AgeRange age;
+
+        /** 性别 */
+        private List<Integer> gender;
+
+        /** 地域 */
+        private List<Location> location;
+
+        /** 自定义人群 */
+        private List<Long> customAudience;
+
+        /** 兴趣标签 */
+        private List<Integer> interest;
+
+        /** 行为标签 */
+        private List<Integer> behavior;
+
+        /** 操作系统 */
+        private List<String> operatingSystem;
+
+        /** 网络类型 */
+        private List<Integer> networkType;
+    }
+
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class AgeRange {
+        private Integer min;
+        private Integer max;
+    }
+
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class Location {
+        private Long locationId;
+        private String locationName;
+    }
+
+    /**
+     * 广告查询请求参数
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class GetRequest {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 过滤条件 */
+        private Filtering filtering;
+
+        /** 页码 */
+        private Integer page;
+
+        /** 每页数量 */
+        private Integer pageSize;
+
+        /** 游标分页模式 */
+        private String paginationMode;
+
+        /** 游标 */
+        private String cursor;
+
+        /** 返回字段 */
+        private List<String> fields;
+
+        @Data
+        @Builder
+        @NoArgsConstructor
+        @AllArgsConstructor
+        public static class Filtering {
+            private List<Long> adgroupIdList;
+            private String configuredStatus;
+            private String campaignId;
+        }
+    }
+
+    /**
+     * 广告创建请求参数
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class AddRequest {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 广告组ID */
+        private Long campaignId;
+
+        /** 广告名称 */
+        private String adgroupName;
+
+        /** 出价类型 */
+        private String bidType;
+
+        /** 出价金额 */
+        private Integer bidAmount;
+
+        /** 日预算 */
+        private Long dailyBudget;
+
+        /** 开始日期 */
+        private String startDate;
+
+        /** 结束日期 */
+        private String endDate;
+
+        /** 定向设置 */
+        private Targeting targeting;
+
+        /** 广告状态 */
+        private String configuredStatus;
+    }
+
+    /**
+     * 广告更新请求参数
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class UpdateRequest {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 广告ID */
+        private Long adgroupId;
+
+        /** 广告名称 */
+        private String adgroupName;
+
+        /** 出价金额 */
+        private Integer bidAmount;
+
+        /** 日预算 */
+        private Long dailyBudget;
+
+        /** 开始日期 */
+        private String startDate;
+
+        /** 结束日期 */
+        private String endDate;
+
+        /** 定向设置 */
+        private Targeting targeting;
+
+        /** 广告状态 */
+        private String configuredStatus;
+    }
+}

+ 152 - 0
core/src/main/java/com/tzld/ad/marketing/model/advertiser/AdvertiserDTO.java

@@ -0,0 +1,152 @@
+package com.tzld.ad.marketing.model.advertiser;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 广告账号相关 DTO
+ */
+public class AdvertiserDTO {
+
+    /**
+     * 广告主信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class AdvertiserInfo {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 广告主名称 */
+        private String accountName;
+
+        /** 广告主状态 */
+        private Integer accountStatus;
+
+        /** 营业执照名称 */
+        private String corporationName;
+
+        /** 联系人姓名 */
+        private String contactPersonName;
+
+        /** 联系人电话 */
+        private String contactPersonTelephone;
+
+        /** 联系人邮箱 */
+        private String contactPersonEmail;
+
+        /** 联系人地址 */
+        private String contactPersonAddress;
+
+        /** 系统状态 */
+        private Integer systemStatus;
+
+        /** 审核状态 */
+        private Integer auditStatus;
+
+        /** 是否是管家子客 */
+        private Boolean isManagerChildAccount;
+
+        /** 行业分类 */
+        private String industry;
+    }
+
+    /**
+     * 广告主查询请求参数
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class GetRequest {
+        /** 广告主ID列表,最多100个 */
+        private List<Long> accountIdList;
+
+        /** 过滤条件 */
+        private Filtering filtering;
+
+        /** 页码,默认1 */
+        private Integer page;
+
+        /** 每页数量,默认10,最大100 */
+        private Integer pageSize;
+
+        @Data
+        @Builder
+        @NoArgsConstructor
+        @AllArgsConstructor
+        public static class Filtering {
+            /** 广告主状态 */
+            private Integer accountStatus;
+        }
+    }
+
+    /**
+     * 广告主更新请求参数
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class UpdateRequest {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 广告主名称 */
+        private String accountName;
+
+        /** 联系人姓名 */
+        private String contactPersonName;
+
+        /** 联系人电话 */
+        private String contactPersonTelephone;
+
+        /** 联系人邮箱 */
+        private String contactPersonEmail;
+    }
+
+    /**
+     * 日预算信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class DailyBudget {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 日预算(单位:分) */
+        private Long dailyBudget;
+    }
+
+    /**
+     * 服务商信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class AgencyInfo {
+        /** 服务商ID */
+        private Long agencyId;
+
+        /** 服务商名称 */
+        private String agencyName;
+
+        /** 服务商状态 */
+        private Integer agencyStatus;
+
+        /** 联系人姓名 */
+        private String contactPersonName;
+
+        /** 联系人电话 */
+        private String contactPersonTelephone;
+    }
+}

+ 137 - 0
core/src/main/java/com/tzld/ad/marketing/model/audience/AudienceDTO.java

@@ -0,0 +1,137 @@
+package com.tzld.ad.marketing.model.audience;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 客户人群相关 DTO
+ */
+public class AudienceDTO {
+
+    /**
+     * 人群信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class AudienceInfo {
+        /** 人群ID */
+        private Long audienceId;
+
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 人群名称 */
+        private String name;
+
+        /** 人群类型 */
+        private String type;
+
+        /** 人群描述 */
+        private String description;
+
+        /** 人群状态 */
+        private Integer status;
+
+        /** 用户数量 */
+        private Long userCount;
+
+        /** 创建时间 */
+        private String createTime;
+
+        /** 最后修改时间 */
+        private String lastModifiedTime;
+    }
+
+    /**
+     * 人群查询请求参数
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class GetRequest {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 人群ID */
+        private Long audienceId;
+
+        /** 页码 */
+        private Integer page;
+
+        /** 每页数量 */
+        private Integer pageSize;
+    }
+
+    /**
+     * 人群创建请求参数
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class AddRequest {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 人群名称 */
+        private String name;
+
+        /** 人群类型 */
+        private String type;
+
+        /** 人群描述 */
+        private String description;
+    }
+
+    /**
+     * 人群文件上传请求
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class FileAddRequest {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 人群ID */
+        private Long audienceId;
+
+        /** 用户ID类型 */
+        private String userIdType;
+
+        /** 文件签名 */
+        private String signature;
+
+        /** 文件数据 */
+        private byte[] fileData;
+
+        /** Open App ID(可选) */
+        private String openAppId;
+    }
+
+    /**
+     * 人群推送请求
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class PushRequest {
+        /** 源广告主ID */
+        private Long fromAccountId;
+
+        /** 人群ID列表 */
+        private List<Long> audienceIdList;
+
+        /** 目标广告主ID */
+        private String toAccountId;
+    }
+}

+ 153 - 0
core/src/main/java/com/tzld/ad/marketing/model/batch/BatchDTO.java

@@ -0,0 +1,153 @@
+package com.tzld.ad.marketing.model.batch;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 批量操作模块相关 DTO
+ */
+public class BatchDTO {
+
+    /**
+     * 批量操作请求
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class BatchRequest {
+        /** 批量操作列表 */
+        private List<BatchOperation> operations;
+    }
+
+    /**
+     * 单个批量操作
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class BatchOperation {
+        /** 操作方法 (GET/POST) */
+        private String method;
+
+        /** API路径 */
+        private String path;
+
+        /** 请求参数(GET请求) */
+        private Map<String, Object> params;
+
+        /** 请求体(POST请求) */
+        private Object body;
+    }
+
+    /**
+     * 批量操作响应
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class BatchResponse {
+        /** 批量操作结果列表 */
+        private List<BatchResult> results;
+    }
+
+    /**
+     * 单个批量操作结果
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class BatchResult {
+        /** 操作序号 */
+        private Integer index;
+
+        /** 响应码 */
+        private Integer code;
+
+        /** 响应消息 */
+        private String message;
+
+        /** 响应数据 */
+        private Object data;
+    }
+
+    /**
+     * 批量修改广告日限额请求
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class UpdateDailyBudgetRequest {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 广告日限额列表 */
+        private List<AdGroupBudget> adgroupBudgets;
+    }
+
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class AdGroupBudget {
+        private Long adgroupId;
+        private Long dailyBudget;
+    }
+
+    /**
+     * 批量修改广告状态请求
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class UpdateStatusRequest {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 广告状态列表 */
+        private List<AdGroupStatus> adgroupStatusList;
+    }
+
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class AdGroupStatus {
+        private Long adgroupId;
+        private String configuredStatus;
+    }
+
+    /**
+     * 批量修改广告出价请求
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class UpdateBidAmountRequest {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 广告出价列表 */
+        private List<AdGroupBid> adgroupBids;
+    }
+
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class AdGroupBid {
+        private Long adgroupId;
+        private Integer bidAmount;
+    }
+}

+ 119 - 0
core/src/main/java/com/tzld/ad/marketing/model/component/ComponentDTO.java

@@ -0,0 +1,119 @@
+package com.tzld.ad.marketing.model.component;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 创意组件模块相关 DTO
+ */
+public class ComponentDTO {
+
+    /**
+     * 组件信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class ComponentInfo {
+        /** 组件ID */
+        private Long componentId;
+
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 组件类型 */
+        private String componentType;
+
+        /** 组件名称 */
+        private String componentName;
+
+        /** 组件内容 */
+        private ComponentContent content;
+
+        /** 组件状态 */
+        private String componentStatus;
+
+        /** 创建时间 */
+        private String createTime;
+
+        /** 最后修改时间 */
+        private String lastModifiedTime;
+    }
+
+    /**
+     * 组件内容
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class ComponentContent {
+        /** 文本内容 */
+        private String text;
+
+        /** 图片ID列表 */
+        private List<String> imageIdList;
+
+        /** 视频ID */
+        private String videoId;
+
+        /** 跳转链接 */
+        private String jumpUrl;
+    }
+
+    /**
+     * 组件查询请求参数
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class GetRequest {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 过滤条件 */
+        private Filtering filtering;
+
+        /** 页码 */
+        private Integer page;
+
+        /** 每页数量 */
+        private Integer pageSize;
+
+        @Data
+        @Builder
+        @NoArgsConstructor
+        @AllArgsConstructor
+        public static class Filtering {
+            private List<Long> componentIdList;
+            private String componentType;
+        }
+    }
+
+    /**
+     * 组件创建请求参数
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class AddRequest {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 组件类型 */
+        private String componentType;
+
+        /** 组件名称 */
+        private String componentName;
+
+        /** 组件内容 */
+        private ComponentContent content;
+    }
+}

+ 180 - 0
core/src/main/java/com/tzld/ad/marketing/model/creative/CreativeDTO.java

@@ -0,0 +1,180 @@
+package com.tzld.ad.marketing.model.creative;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 创意模块相关 DTO
+ */
+public class CreativeDTO {
+
+    /**
+     * 创意信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class CreativeInfo {
+        /** 创意ID */
+        private Long dynamicCreativeId;
+
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 广告ID */
+        private Long adgroupId;
+
+        /** 创意名称 */
+        private String creativeName;
+
+        /** 创意类型 */
+        private String creativeType;
+
+        /** 创意状态 */
+        private String configuredStatus;
+
+        /** 系统状态 */
+        private String systemStatus;
+
+        /** 审核状态 */
+        private Integer auditStatus;
+
+        /** 创意元素 */
+        private CreativeElements creativeElements;
+
+        /** 创建时间 */
+        private String createTime;
+
+        /** 最后修改时间 */
+        private String lastModifiedTime;
+    }
+
+    /**
+     * 创意元素
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class CreativeElements {
+        /** 标题 */
+        private String title;
+
+        /** 描述 */
+        private String description;
+
+        /** 图片ID列表 */
+        private List<String> imageIdList;
+
+        /** 视频ID */
+        private String videoId;
+
+        /** 落地页URL */
+        private String pageUrl;
+
+        /** 按钮文字 */
+        private String buttonText;
+
+        /** 小程序AppId */
+        private String wechatAppId;
+
+        /** 小程序页面路径 */
+        private String wechatPagePath;
+    }
+
+    /**
+     * 创意查询请求参数
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class GetRequest {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 过滤条件 */
+        private Filtering filtering;
+
+        /** 页码 */
+        private Integer page;
+
+        /** 每页数量 */
+        private Integer pageSize;
+
+        /** 游标分页模式 */
+        private String paginationMode;
+
+        /** 游标 */
+        private String cursor;
+
+        /** 返回字段 */
+        private List<String> fields;
+
+        @Data
+        @Builder
+        @NoArgsConstructor
+        @AllArgsConstructor
+        public static class Filtering {
+            private List<Long> dynamicCreativeIdList;
+            private List<Long> adgroupIdList;
+            private String configuredStatus;
+        }
+    }
+
+    /**
+     * 创意创建请求参数
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class AddRequest {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 广告ID */
+        private Long adgroupId;
+
+        /** 创意名称 */
+        private String creativeName;
+
+        /** 创意类型 */
+        private String creativeType;
+
+        /** 创意元素 */
+        private CreativeElements creativeElements;
+
+        /** 创意状态 */
+        private String configuredStatus;
+    }
+
+    /**
+     * 创意更新请求参数
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class UpdateRequest {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 创意ID */
+        private Long dynamicCreativeId;
+
+        /** 创意名称 */
+        private String creativeName;
+
+        /** 创意元素 */
+        private CreativeElements creativeElements;
+
+        /** 创意状态 */
+        private String configuredStatus;
+    }
+}

+ 60 - 0
core/src/main/java/com/tzld/ad/marketing/model/funds/FundsDTO.java

@@ -0,0 +1,60 @@
+package com.tzld.ad.marketing.model.funds;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 资金模块相关 DTO
+ */
+public class FundsDTO {
+
+    /**
+     * 账户资金信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class FundsInfo {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 账户余额(分) */
+        private Long balance;
+
+        /** 现金余额(分) */
+        private Long cashBalance;
+
+        /** 赠款余额(分) */
+        private Long fundBalance;
+
+        /** 总冻结金额(分) */
+        private Long frozenBudget;
+
+        /** 总欠款金额(分) */
+        private Long totalDebt;
+
+        /** 今日消耗(分) */
+        private Long todayCost;
+
+        /** 今日充值(分) */
+        private Long todayPayment;
+
+        /** 状态 */
+        private Integer status;
+    }
+
+    /**
+     * 资金查询请求参数
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class GetRequest {
+        /** 广告主ID */
+        private Long accountId;
+    }
+}

+ 118 - 0
core/src/main/java/com/tzld/ad/marketing/model/image/ImageDTO.java

@@ -0,0 +1,118 @@
+package com.tzld.ad.marketing.model.image;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 图片模块相关 DTO
+ */
+public class ImageDTO {
+
+    /**
+     * 图片信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class ImageInfo {
+        /** 图片ID */
+        private String imageId;
+
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 图片名称 */
+        private String imageName;
+
+        /** 图片URL */
+        private String imageUrl;
+
+        /** 图片宽度 */
+        private Integer width;
+
+        /** 图片高度 */
+        private Integer height;
+
+        /** 图片大小(字节) */
+        private Long size;
+
+        /** 图片格式 */
+        private String format;
+
+        /** 图片签名 */
+        private String signature;
+
+        /** 上传时间 */
+        private String createTime;
+    }
+
+    /**
+     * 图片查询请求参数
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class GetRequest {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 过滤条件 */
+        private Filtering filtering;
+
+        /** 页码 */
+        private Integer page;
+
+        /** 每页数量 */
+        private Integer pageSize;
+
+        @Data
+        @Builder
+        @NoArgsConstructor
+        @AllArgsConstructor
+        public static class Filtering {
+            private List<String> imageIdList;
+            private String signature;
+        }
+    }
+
+    /**
+     * 图片上传请求参数
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class AddRequest {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 图片签名(MD5) */
+        private String signature;
+
+        /** 图片文件字节 */
+        private byte[] imageData;
+
+        /** 图片名称 */
+        private String imageName;
+    }
+
+    /**
+     * 图片上传响应
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class AddResponse {
+        private String imageId;
+        private String imageUrl;
+        private Integer width;
+        private Integer height;
+    }
+}

+ 60 - 0
core/src/main/java/com/tzld/ad/marketing/model/oauth/OAuthDTO.java

@@ -0,0 +1,60 @@
+package com.tzld.ad.marketing.model.oauth;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * OAuth 授权相关 DTO
+ */
+public class OAuthDTO {
+
+    /**
+     * Token响应
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class TokenResponse {
+        /** 访问令牌 */
+        private String accessToken;
+
+        /** 刷新令牌 */
+        private String refreshToken;
+
+        /** 过期时间(秒) */
+        private Long expiresIn;
+
+        /** 刷新令牌过期时间(秒) */
+        private Long refreshTokenExpiresIn;
+
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 权限列表 */
+        private java.util.List<String> scope;
+    }
+
+    /**
+     * 授权URL构建参数
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class AuthorizeParams {
+        /** 客户端ID */
+        private String clientId;
+
+        /** 回调地址 */
+        private String redirectUri;
+
+        /** 权限范围 */
+        private String scope;
+
+        /** 状态码(防CSRF攻击) */
+        private String state;
+    }
+}

+ 78 - 0
core/src/main/java/com/tzld/ad/marketing/model/page/PageDTO.java

@@ -0,0 +1,78 @@
+package com.tzld.ad.marketing.model.page;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 落地页模块相关 DTO
+ */
+public class PageDTO {
+
+    /**
+     * 落地页信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class PageInfo {
+        /** 落地页ID */
+        private Long pageId;
+
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 落地页名称 */
+        private String pageName;
+
+        /** 落地页类型 */
+        private String pageType;
+
+        /** 落地页URL */
+        private String pageUrl;
+
+        /** 落地页状态 */
+        private Integer pageStatus;
+
+        /** 创建时间 */
+        private String createTime;
+
+        /** 最后修改时间 */
+        private String lastModifiedTime;
+    }
+
+    /**
+     * 落地页查询请求参数
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class GetRequest {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 过滤条件 */
+        private Filtering filtering;
+
+        /** 页码 */
+        private Integer page;
+
+        /** 每页数量 */
+        private Integer pageSize;
+
+        @Data
+        @Builder
+        @NoArgsConstructor
+        @AllArgsConstructor
+        public static class Filtering {
+            private List<Long> pageIdList;
+            private Integer pageStatus;
+            private String pageType;
+        }
+    }
+}

+ 121 - 0
core/src/main/java/com/tzld/ad/marketing/model/report/ReportDTO.java

@@ -0,0 +1,121 @@
+package com.tzld.ad.marketing.model.report;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 报表模块相关 DTO
+ */
+public class ReportDTO {
+
+    /**
+     * 每日报表查询请求参数
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class DailyReportGetRequest {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 报表级别 */
+        private String level;
+
+        /** 日期范围 */
+        private DateRange dateRange;
+
+        /** 时间线 */
+        private String timeLine;
+
+        /** 过滤条件 */
+        private Filtering filtering;
+
+        /** 分组方式 */
+        private List<String> groupBy;
+
+        /** 返回字段 */
+        private List<String> fields;
+
+        /** 页码 */
+        private Integer page;
+
+        /** 每页数量 */
+        private Integer pageSize;
+    }
+
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class DateRange {
+        private String startDate;
+        private String endDate;
+    }
+
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class Filtering {
+        private List<Long> adgroupIdList;
+        private List<Long> dynamicCreativeIdList;
+        private List<Long> componentIdList;
+        private String adgroupId;
+    }
+
+    /**
+     * 每日报表数据
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class DailyReportData {
+        /** 日期 */
+        private String date;
+
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 广告计划ID */
+        private Long campaignId;
+
+        /** 广告ID */
+        private Long adgroupId;
+
+        /** 创意ID */
+        private Long dynamicCreativeId;
+
+        /** 组件ID */
+        private Long componentId;
+
+        /** 消耗(分) */
+        private Long cost;
+
+        /** 展示数 */
+        private Long viewCount;
+
+        /** 点击数 */
+        private Long clickCount;
+
+        /** 点击率 */
+        private Double ctr;
+
+        /** 平均点击价格(分) */
+        private Long cpc;
+
+        /** 千次展示价格(分) */
+        private Long cpm;
+
+        /** 转化数 */
+        private Long convertCount;
+
+        /** 转化成本(分) */
+        private Long convertCost;
+    }
+}

+ 175 - 0
core/src/main/java/com/tzld/ad/marketing/model/rta/RtaDTO.java

@@ -0,0 +1,175 @@
+package com.tzld.ad.marketing.model.rta;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * RTA 策略管理相关 DTO
+ */
+public class RtaDTO {
+
+    /**
+     * RTA 基本信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class RtaInfo {
+        /** RTA ID */
+        private Long rtaId;
+
+        /** RTA 名称 */
+        private String rtaName;
+
+        /** 公司名称 */
+        private String rtaCompanyName;
+
+        /** Bid URL */
+        private String bidUrl;
+
+        /** 缓存时间(秒) */
+        private Integer cacheTime;
+
+        /** 是否开启:0=关闭, 1=开启 */
+        private Integer enable;
+
+        /** 规则配置列表 */
+        private List<RuleConfig> rules;
+
+        @Data
+        @Builder
+        @NoArgsConstructor
+        @AllArgsConstructor
+        public static class RuleConfig {
+            private Integer ruleType;
+            private Integer ruleValue;
+            private Integer ruleTypeNone;
+            private String ruleDese;
+        }
+    }
+
+    /**
+     * RTA 策略信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class TargetInfo {
+        /** 外部策略ID */
+        private String outerTargetId;
+
+        /** 策略名称 */
+        private String targetName;
+    }
+
+    /**
+     * RTA 策略查询请求
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class TargetGetRequest {
+        /** 页码 */
+        private Integer page;
+
+        /** 单页数量 */
+        private Integer size;
+
+        /** 外部策略ID列表 */
+        private List<String> outerTargetIds;
+    }
+
+    /**
+     * RTA 策略创建请求
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class TargetSetRequest {
+        /** 外部策略ID */
+        private String outerTargetId;
+
+        /** 策略名称 */
+        private String targetName;
+    }
+
+    /**
+     * RTA 策略删除请求
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class TargetDeleteRequest {
+        /** 外部策略ID */
+        private String outerTargetId;
+    }
+
+    /**
+     * RTA 策略绑定请求
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class TargetBindRequest {
+        /** 外部策略ID */
+        private String outerTargetId;
+
+        /** 广告主账号ID列表 */
+        private List<Long> advertiserIds;
+
+        /** 广告ID列表 */
+        private List<Long> adIds;
+    }
+
+    /**
+     * RTA 绑定状态信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class BindStatusInfo {
+        /** 外部策略ID */
+        private String outerTargetId;
+
+        /** 广告主账号ID */
+        private Long advertiserId;
+
+        /** 广告ID */
+        private Long adId;
+
+        /** 绑定状态:0=未绑定, 1=已绑定 */
+        private Integer status;
+    }
+
+    /**
+     * RTA 实验数据查询请求
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class ExperimentDataRequest {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 开始日期 */
+        private String startDate;
+
+        /** 结束日期 */
+        private String endDate;
+
+        /** 策略ID */
+        private String outerTargetId;
+    }
+}

+ 149 - 0
core/src/main/java/com/tzld/ad/marketing/model/useraction/UserActionDTO.java

@@ -0,0 +1,149 @@
+package com.tzld.ad.marketing.model.useraction;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 用户行为数据相关 DTO
+ */
+public class UserActionDTO {
+
+    /**
+     * 用户行为数据源信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class UserActionSetInfo {
+        /** 数据源ID */
+        private Long userActionSetId;
+
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 数据源名称 */
+        private String name;
+
+        /** 数据源类型 */
+        private String type;
+
+        /** 数据源描述 */
+        private String description;
+
+        /** 创建时间 */
+        private String createTime;
+    }
+
+    /**
+     * 用户行为数据源查询请求
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class UserActionSetGetRequest {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 数据源ID */
+        private Long userActionSetId;
+
+        /** 数据源类型列表 */
+        private List<String> typeList;
+
+        /** 移动应用ID */
+        private String mobileAppId;
+
+        /** 微信AppId */
+        private String wechatAppId;
+    }
+
+    /**
+     * 用户行为数据源创建请求
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class UserActionSetAddRequest {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 数据源名称 */
+        private String name;
+
+        /** 数据源类型 */
+        private String type;
+
+        /** 数据源描述 */
+        private String description;
+
+        /** 移动应用ID */
+        private String mobileAppId;
+
+        /** 微信AppId */
+        private String wechatAppId;
+    }
+
+    /**
+     * 用户行为数据添加请求
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class UserActionAddRequest {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 数据源ID */
+        private Long userActionSetId;
+
+        /** 用户行为列表 */
+        private List<UserAction> actions;
+    }
+
+    /**
+     * 用户行为
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class UserAction {
+        /** 用户ID类型 */
+        private String userIdType;
+
+        /** 用户ID */
+        private String userId;
+
+        /** 行为类型 */
+        private String actionType;
+
+        /** 行为时间(毫秒时间戳) */
+        private Long actionTime;
+
+        /** 商品ID */
+        private String productId;
+
+        /** 商品名称 */
+        private String productName;
+
+        /** 商品数量 */
+        private Integer productCount;
+
+        /** 商品价格(分) */
+        private Long productPrice;
+
+        /** 追踪参数 */
+        private String trackPath;
+
+        /** 应用ID */
+        private String appId;
+    }
+}

+ 127 - 0
core/src/main/java/com/tzld/ad/marketing/model/video/VideoDTO.java

@@ -0,0 +1,127 @@
+package com.tzld.ad.marketing.model.video;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 视频模块相关 DTO
+ */
+public class VideoDTO {
+
+    /**
+     * 视频信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class VideoInfo {
+        /** 视频ID */
+        private Long videoId;
+
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 视频名称 */
+        private String name;
+
+        /** 视频描述 */
+        private String description;
+
+        /** 视频格式 */
+        private String format;
+
+        /** 视频宽度 */
+        private Integer width;
+
+        /** 视频高度 */
+        private Integer height;
+
+        /** 视频时长(秒) */
+        private Integer duration;
+
+        /** 文件大小(字节) */
+        private Long fileSize;
+
+        /** MD5签名 */
+        private String signature;
+
+        /** 视频状态 */
+        private Integer status;
+
+        /** 创建时间 */
+        private String createTime;
+
+        /** 最后修改时间 */
+        private String lastModifiedTime;
+    }
+
+    /**
+     * 视频查询请求参数
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class GetRequest {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 过滤条件 */
+        private Filtering filtering;
+
+        /** 页码 */
+        private Integer page;
+
+        /** 每页数量 */
+        private Integer pageSize;
+
+        @Data
+        @Builder
+        @NoArgsConstructor
+        @AllArgsConstructor
+        public static class Filtering {
+            private List<Long> videoIdList;
+        }
+    }
+
+    /**
+     * 视频上传请求参数
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class AddRequest {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 视频描述 */
+        private String description;
+
+        /** MD5签名 */
+        private String signature;
+
+        /** 视频数据 */
+        private byte[] videoData;
+
+        /** 视频文件名 */
+        private String fileName;
+    }
+
+    /**
+     * 视频上传响应
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class AddResponse {
+        /** 视频ID */
+        private Long videoId;
+    }
+}

+ 107 - 0
core/src/main/java/com/tzld/ad/marketing/model/wechat/WechatDTO.java

@@ -0,0 +1,107 @@
+package com.tzld.ad.marketing.model.wechat;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 公众号模块相关 DTO
+ */
+public class WechatDTO {
+
+    /**
+     * 公众号信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class WechatInfo {
+        /** 公众号AppId */
+        private String wechatAppId;
+
+        /** 公众号名称 */
+        private String wechatName;
+
+        /** 公众号类型 */
+        private Integer wechatType;
+
+        /** 公众号头像 */
+        private String headImgUrl;
+
+        /** 认证状态 */
+        private Integer verifyStatus;
+    }
+
+    /**
+     * 公众号授权链接信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class WechatAuthUrl {
+        /** 授权链接 */
+        private String authUrl;
+
+        /** 二维码链接 */
+        private String qrcodeUrl;
+    }
+
+    /**
+     * 朋友圈头像昵称跳转页信息
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class ProfileInfo {
+        /** Profile ID */
+        private Long profileId;
+
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 公众号AppId */
+        private String wechatAppId;
+
+        /** 昵称 */
+        private String nickname;
+
+        /** 头像URL */
+        private String headImgUrl;
+
+        /** 跳转类型 */
+        private Integer jumpType;
+
+        /** 跳转链接 */
+        private String jumpUrl;
+
+        /** 创建时间 */
+        private String createTime;
+    }
+
+    /**
+     * 朋友圈头像昵称跳转页创建请求
+     */
+    @Data
+    @Builder
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class ProfileAddRequest {
+        /** 广告主ID */
+        private Long accountId;
+
+        /** 公众号AppId */
+        private String wechatAppId;
+
+        /** 跳转类型 */
+        private Integer jumpType;
+
+        /** 跳转链接 */
+        private String jumpUrl;
+    }
+}

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 466 - 387
core/src/main/java/com/tzld/ad/service/adput/impl/AdPutTencentCommonServiceImpl.java


+ 116 - 68
core/src/main/java/com/tzld/ad/service/adput/impl/AdPutTencentUserActionAddServiceImpl.java

@@ -1,14 +1,15 @@
 package com.tzld.ad.service.adput.impl;
 
 import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.PropertyNamingStrategy;
-import com.alibaba.fastjson.serializer.SerializeConfig;
-import com.alibaba.fastjson.serializer.SerializerFeature;
 import com.tzld.ad.dao.mapper.adPut.AdPutOfficialOpenIdCardIdMappingMapper;
 import com.tzld.ad.dao.mapper.adPut.AdPutOfficialOpenIdClickIdMappingMapper;
+import com.tzld.ad.marketing.client.UserActionClient;
+import com.tzld.ad.marketing.client.UserActionSetClient;
+import com.tzld.ad.marketing.model.TencentApiResponse;
+import com.tzld.ad.marketing.model.useraction.UserActionDTO;
+import com.tzld.ad.model.adPut.AdPutTencentUserAction;
 import com.tzld.ad.model.adPut.AdPutTencentUserActionReq;
 import com.tzld.ad.model.adPut.AdPutTencentUserActionSet;
-import com.tzld.ad.model.adPut.AdPutTencentUserActionSetCursor;
 import com.tzld.ad.model.po.adput.AdPutOfficialOpenIdCardIdMapping;
 import com.tzld.ad.model.po.adput.AdPutOfficialOpenIdCardIdMappingExample;
 import com.tzld.ad.model.po.adput.AdPutOfficialOpenIdClickIdMapping;
@@ -16,9 +17,6 @@ import com.tzld.ad.model.po.adput.AdPutOfficialOpenIdClickIdMappingExample;
 import com.tzld.ad.service.AdOwnRedisUtils;
 import com.tzld.ad.service.adput.AdPutTencentUserActionAddService;
 import com.tzld.ad.util.DateTimeUtils;
-import com.tzld.ad.util.HttpClientUtil;
-import com.tzld.ad.util.OkHttpPoolClient;
-import com.tzld.ad.util.adPut.AdPutTencentUtil;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang.StringUtils;
 import org.slf4j.Logger;
@@ -27,8 +25,6 @@ import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
 import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
 import java.util.*;
 
 @Service
@@ -36,10 +32,14 @@ public class AdPutTencentUserActionAddServiceImpl implements AdPutTencentUserAct
 
     private final static Logger log = LoggerFactory.getLogger(AdPutTencentUserActionAddServiceImpl.class);
 
-    private static final OkHttpPoolClient httpPoolClientDefault = HttpClientUtil.create(30000, 30000, 2000, 5000, 5, 30000);
-
     @Resource
     private AdPutTencentCommonServiceImpl adPutTencentCommonService;
+    
+    @Resource
+    private UserActionClient userActionClient;
+    
+    @Resource
+    private UserActionSetClient userActionSetClient;
 
     public static String OPENID_CARDID_KEY = "official.openid.cardId.{openId}";
 
@@ -60,26 +60,67 @@ public class AdPutTencentUserActionAddServiceImpl implements AdPutTencentUserAct
             log.error("userActionAdd param check fail, adPutTencentUserActionReq:{}", JSON.toJSON(adPutTencentUserActionReq));
             return null;
         }
-        SerializeConfig serializeConfig = new SerializeConfig();
-        serializeConfig.propertyNamingStrategy = PropertyNamingStrategy.SnakeCase;
-
-
+        
         String accessToken = adPutTencentCommonService.getAccessToken(adPutTencentUserActionReq.getAccountId().toString());
-        String timestamp = getTimestamp();
-        String nonce = getNonce();
-
-        StringBuilder url = new StringBuilder("https://api.e.qq.com/v3.0/user_actions/add?access_token=").append(accessToken)
-                .append("&timestamp=").append(timestamp)
-                .append("&nonce=").append(nonce);
-
-        Map<String, String> headers = new HashMap<>();
-        headers.put("access-token", accessToken);
-        headers.put("timestamp", timestamp);
-        headers.put("nonce", nonce);
-
-        Optional<String> optional = httpPoolClientDefault.postJson(url.toString(), JSON.toJSONString(adPutTencentUserActionReq, serializeConfig, SerializerFeature.DisableCircularReferenceDetect), headers);
-        log.info("userActionAdd req, url:{}, req:{}, headers: {}", url, JSON.toJSONString(adPutTencentUserActionReq, serializeConfig, SerializerFeature.DisableCircularReferenceDetect), JSON.toJSONString(headers));
-        return AdPutTencentUtil.getUserActionAddResponse(optional.get());
+        
+        // 转换为封装客户端所需的请求格式
+        UserActionDTO.UserActionAddRequest request = convertToUserActionAddRequest(adPutTencentUserActionReq);
+        
+        log.info("userActionAdd req, accountId:{}, userActionSetId:{}, actionsCount:{}", 
+                adPutTencentUserActionReq.getAccountId(), 
+                adPutTencentUserActionReq.getUserActionSetId(),
+                adPutTencentUserActionReq.getActions() != null ? adPutTencentUserActionReq.getActions().size() : 0);
+        
+        try {
+            TencentApiResponse<Void> response = userActionClient.addUserActions(accessToken, request);
+            if (response.isSuccess()) {
+                log.info("userActionAdd success, accountId:{}", adPutTencentUserActionReq.getAccountId());
+                return UUID.randomUUID().toString(); // 返回一个唯一标识
+            } else {
+                log.error("userActionAdd failed, code:{}, message:{}", response.getCode(), response.getErrorMessage());
+                return null;
+            }
+        } catch (Exception e) {
+            log.error("userActionAdd error, accountId:{}, error:", adPutTencentUserActionReq.getAccountId(), e);
+            return null;
+        }
+    }
+    
+    /**
+     * 转换为封装客户端所需的请求格式
+     */
+    private UserActionDTO.UserActionAddRequest convertToUserActionAddRequest(AdPutTencentUserActionReq req) {
+        List<UserActionDTO.UserAction> actions = new ArrayList<>();
+        if (req.getActions() != null) {
+            for (AdPutTencentUserAction action : req.getActions()) {
+                UserActionDTO.UserAction userAction = UserActionDTO.UserAction.builder()
+                        .actionTime(action.getActionTime())
+                        .actionType(action.getActionType())
+                        .build();
+                
+                // 设置用户ID
+                if (action.getUserId() != null) {
+                    userAction.setUserIdType("WECHAT_OPENID");
+                    userAction.setUserId(action.getUserId().getWechatOpenid());
+                    if (StringUtils.isNotBlank(action.getUserId().getWechatAppId())) {
+                        userAction.setAppId(action.getUserId().getWechatAppId());
+                    }
+                }
+                
+                // 设置追踪信息
+                if (action.getTrace() != null && StringUtils.isNotBlank(action.getTrace().getClickId())) {
+                    userAction.setTrackPath(action.getTrace().getClickId());
+                }
+                
+                actions.add(userAction);
+            }
+        }
+        
+        return UserActionDTO.UserActionAddRequest.builder()
+                .accountId(req.getAccountId())
+                .userActionSetId(req.getUserActionSetId())
+                .actions(actions)
+                .build();
     }
 
     @Override
@@ -124,38 +165,54 @@ public class AdPutTencentUserActionAddServiceImpl implements AdPutTencentUserAct
             return Collections.emptyList();
         }
         String accessToken = adPutTencentCommonService.getAccessToken(accountId.toString());
-        StringBuilder url = new StringBuilder("https://api.e.qq.com/v3.0/user_action_sets/get?access_token=" + accessToken +
-                "&timestamp=" + getTimestamp() +
-                "&nonce=" + getNonce());
-
-        url.append("&account_id=").append(accountId);
-        if (!org.springframework.util.CollectionUtils.isEmpty(types)) {
-            url.append("&");
-            for (int i = 0; i < types.size(); i++) {
-                url.append("type[]=").append(URLEncoder.encode(types.get(i), StandardCharsets.UTF_8.toString()));
-                if (i < types.size() - 1) {
-                    url.append("&");
-                }
-            }
-        }
+        
+        // 构建查询请求
+        UserActionDTO.UserActionSetGetRequest request = UserActionDTO.UserActionSetGetRequest.builder()
+                .accountId(accountId)
+                .typeList(types)
+                .mobileAppId(mobileAppId)
+                .wechatAppId(wechatAppId)
+                .build();
+        
         if (StringUtils.isNotBlank(userActionSetId)) {
-            url.append("&user_action_set_id=").append(userActionSetId);
-        }
-        if (StringUtils.isNotBlank(mobileAppId)) {
-            url.append("&mobile_app_id=").append(mobileAppId);
-        }
-        if (StringUtils.isNotBlank(wechatAppId)) {
-            url.append("&wechat_app_id=").append(wechatAppId);
+            try {
+                request.setUserActionSetId(Long.parseLong(userActionSetId));
+            } catch (NumberFormatException e) {
+                log.warn("Invalid userActionSetId: {}", userActionSetId);
+            }
         }
-        log.info("getUserActionSet req, url:{}", url);
-        Optional<String> optional = httpPoolClientDefault.get(url.toString());
-        AdPutTencentUserActionSetCursor adPutTencentUserActionSetCursor = AdPutTencentUtil.getAdPutTencentUserActionSetCursorByResponse(optional.get());
-        List<AdPutTencentUserActionSet> results = Collections.emptyList();
-        if (adPutTencentUserActionSetCursor != null) {
-            results = new ArrayList<>(adPutTencentUserActionSetCursor.getList());
+        
+        log.info("getUserActionSet req, accountId:{}, types:{}, wechatAppId:{}", accountId, types, wechatAppId);
+        
+        try {
+            TencentApiResponse<TencentApiResponse.ListData<UserActionDTO.UserActionSetInfo>> response = 
+                    userActionSetClient.getUserActionSets(accessToken, request);
+            
+            List<AdPutTencentUserActionSet> results = new ArrayList<>();
+            if (response.isSuccess() && response.getData() != null && response.getData().getList() != null) {
+                for (UserActionDTO.UserActionSetInfo info : response.getData().getList()) {
+                    results.add(convertToAdPutUserActionSet(info));
+                }
+            }
+            log.info("getUserActionSet result, size:{}", results.size());
+            return results;
+        } catch (Exception e) {
+            log.error("getUserActionSet error, accountId:{}", accountId, e);
+            return Collections.emptyList();
         }
-        log.info("getUserActionSet result, result:{}", JSON.toJSON(results));
-        return results;
+    }
+    
+    /**
+     * 转换为现有的数据模型
+     */
+    private AdPutTencentUserActionSet convertToAdPutUserActionSet(UserActionDTO.UserActionSetInfo info) {
+        AdPutTencentUserActionSet result = new AdPutTencentUserActionSet();
+        result.setUserActionSetId(info.getUserActionSetId());
+        result.setType(info.getType());
+        result.setName(info.getName());
+        result.setDescription(info.getDescription());
+        result.setCreateTime(info.getCreateTime());
+        return result;
     }
 
     @Override
@@ -250,13 +307,4 @@ public class AdPutTencentUserActionAddServiceImpl implements AdPutTencentUserAct
         return true;
     }
 
-    public String getTimestamp() {
-        String str = System.currentTimeMillis() + "";
-        return str.substring(0, str.length() - 3);
-    }
-
-    public String getNonce() {
-        return UUID.randomUUID().toString().replace("-", "");
-    }
-
 }

+ 663 - 0
server/src/main/java/com/tzld/ad/controller/marketing/TencentMarketingController.java

@@ -0,0 +1,663 @@
+package com.tzld.ad.controller.marketing;
+
+import com.tzld.ad.annotation.JwtIgnore;
+import com.tzld.ad.common.base.CommonResponse;
+import com.tzld.ad.marketing.client.*;
+import com.tzld.ad.marketing.model.TencentApiResponse;
+import com.tzld.ad.marketing.model.adgroup.AdGroupDTO;
+import com.tzld.ad.marketing.model.advertiser.AdvertiserDTO;
+import com.tzld.ad.marketing.model.audience.AudienceDTO;
+import com.tzld.ad.marketing.model.batch.BatchDTO;
+import com.tzld.ad.marketing.model.component.ComponentDTO;
+import com.tzld.ad.marketing.model.creative.CreativeDTO;
+import com.tzld.ad.marketing.model.funds.FundsDTO;
+import com.tzld.ad.marketing.model.image.ImageDTO;
+import com.tzld.ad.marketing.model.oauth.OAuthDTO;
+import com.tzld.ad.marketing.model.page.PageDTO;
+import com.tzld.ad.marketing.model.report.ReportDTO;
+import com.tzld.ad.marketing.model.useraction.UserActionDTO;
+import com.tzld.ad.marketing.model.video.VideoDTO;
+import com.tzld.ad.marketing.model.wechat.WechatDTO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 腾讯广告 Marketing API 统一调用入口
+ * <p>
+ * 通过 path 区分不同的 client 模块:
+ * - /oauth/*: OAuth 授权
+ * - /agency/*: 服务商账号
+ * - /advertiser/*: 广告账号
+ * - /funds/*: 资金
+ * - /adgroup/*: 广告
+ * - /creative/*: 创意
+ * - /component/*: 创意组件
+ * - /image/*: 图片
+ * - /video/*: 视频
+ * - /page/*: 落地页
+ * - /audience/*: 客户人群
+ * - /useraction/*: 用户行为数据
+ * - /useractionset/*: 用户行为数据源
+ * - /batch/*: 批量操作
+ * - /report/*: 报表
+ * - /wechat/*: 公众号
+ */
+@RestController
+@RequestMapping("/marketing")
+public class TencentMarketingController {
+
+    @Autowired
+    private OAuthClient oauthClient;
+
+    @Autowired
+    private AgencyClient agencyClient;
+
+    @Autowired
+    private AdvertiserClient advertiserClient;
+
+    @Autowired
+    private FundsClient fundsClient;
+
+    @Autowired
+    private AdGroupClient adGroupClient;
+
+    @Autowired
+    private CreativeClient creativeClient;
+
+    @Autowired
+    private ComponentClient componentClient;
+
+    @Autowired
+    private ImageClient imageClient;
+
+    @Autowired
+    private VideoClient videoClient;
+
+    @Autowired
+    private PageClient pageClient;
+
+    @Autowired
+    private CustomAudienceClient customAudienceClient;
+
+    @Autowired
+    private UserActionClient userActionClient;
+
+    @Autowired
+    private UserActionSetClient userActionSetClient;
+
+    @Autowired
+    private BatchClient batchClient;
+
+    @Autowired
+    private DailyReportClient dailyReportClient;
+
+    @Autowired
+    private WechatClient wechatClient;
+
+    // ==================== OAuth 授权 ====================
+
+    @JwtIgnore
+    @GetMapping("/oauth/authorize")
+    public CommonResponse<String> buildAuthorizeUrl(@RequestParam String state,
+                                                     @RequestParam(required = false) String scope) {
+        String url = oauthClient.buildAuthorizeUrl(state, scope);
+        return CommonResponse.success(url);
+    }
+
+    @JwtIgnore
+    @GetMapping("/oauth/token")
+    public CommonResponse<OAuthDTO.TokenResponse> getToken(@RequestParam String authorizationCode) {
+        OAuthDTO.TokenResponse response = oauthClient.getToken(authorizationCode);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @GetMapping("/oauth/refresh")
+    public CommonResponse<OAuthDTO.TokenResponse> refreshToken(@RequestParam String refreshToken) {
+        OAuthDTO.TokenResponse response = oauthClient.refreshToken(refreshToken);
+        return CommonResponse.success(response);
+    }
+
+    // ==================== 服务商账号 ====================
+
+    @JwtIgnore
+    @GetMapping("/agency/get")
+    public CommonResponse<TencentApiResponse<AdvertiserDTO.AgencyInfo>> getAgency(@RequestParam String accessToken) {
+        TencentApiResponse<AdvertiserDTO.AgencyInfo> response = agencyClient.getAgency(accessToken);
+        return CommonResponse.success(response);
+    }
+
+    // ==================== 广告账号 ====================
+
+    @JwtIgnore
+    @GetMapping("/advertiser/get")
+    public CommonResponse<TencentApiResponse<TencentApiResponse.ListData<AdvertiserDTO.AdvertiserInfo>>> getAdvertisers(
+            @RequestParam String accessToken,
+            @RequestParam Long accountId,
+            @RequestParam(required = false) List<Long> accountIdList) {
+        TencentApiResponse<TencentApiResponse.ListData<AdvertiserDTO.AdvertiserInfo>> response =
+                advertiserClient.getAdvertisers(accessToken, accountId, accountIdList);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @GetMapping("/advertiser/getOne")
+    public CommonResponse<TencentApiResponse<AdvertiserDTO.AdvertiserInfo>> getAdvertiser(
+            @RequestParam String accessToken,
+            @RequestParam Long accountId) {
+        TencentApiResponse<AdvertiserDTO.AdvertiserInfo> response =
+                advertiserClient.getAdvertiser(accessToken, accountId);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @PostMapping("/advertiser/update")
+    public CommonResponse<TencentApiResponse<Void>> updateAdvertiser(
+            @RequestParam String accessToken,
+            @RequestBody AdvertiserDTO.UpdateRequest request) {
+        TencentApiResponse<Void> response = advertiserClient.updateAdvertiser(accessToken, request);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @PostMapping("/advertiser/dailyBudget/update")
+    public CommonResponse<TencentApiResponse<Void>> updateDailyBudget(
+            @RequestParam String accessToken,
+            @RequestParam Long accountId,
+            @RequestParam Long dailyBudget) {
+        TencentApiResponse<Void> response = advertiserClient.updateDailyBudget(accessToken, accountId, dailyBudget);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @GetMapping("/advertiser/dailyBudget/get")
+    public CommonResponse<TencentApiResponse<AdvertiserDTO.DailyBudget>> getDailyBudget(
+            @RequestParam String accessToken,
+            @RequestParam Long accountId) {
+        TencentApiResponse<AdvertiserDTO.DailyBudget> response = advertiserClient.getDailyBudget(accessToken, accountId);
+        return CommonResponse.success(response);
+    }
+
+    // ==================== 资金 ====================
+
+    @JwtIgnore
+    @GetMapping("/funds/get")
+    public CommonResponse<TencentApiResponse<FundsDTO.FundsInfo>> getFunds(
+            @RequestParam String accessToken,
+            @RequestParam Long accountId) {
+        TencentApiResponse<FundsDTO.FundsInfo> response = fundsClient.getFundsByAccountId(accessToken, accountId);
+        return CommonResponse.success(response);
+    }
+
+    // ==================== 广告 ====================
+
+    @JwtIgnore
+    @PostMapping("/adgroup/get")
+    public CommonResponse<TencentApiResponse<TencentApiResponse.ListData<AdGroupDTO.AdGroupInfo>>> getAdGroups(
+            @RequestParam String accessToken,
+            @RequestBody AdGroupDTO.GetRequest request) {
+        TencentApiResponse<TencentApiResponse.ListData<AdGroupDTO.AdGroupInfo>> response =
+                adGroupClient.getAdGroups(accessToken, request);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @GetMapping("/adgroup/getOne")
+    public CommonResponse<TencentApiResponse<AdGroupDTO.AdGroupInfo>> getAdGroup(
+            @RequestParam String accessToken,
+            @RequestParam Long accountId,
+            @RequestParam Long adgroupId) {
+        TencentApiResponse<AdGroupDTO.AdGroupInfo> response =
+                adGroupClient.getAdGroup(accessToken, accountId, adgroupId);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @PostMapping("/adgroup/add")
+    public CommonResponse<TencentApiResponse<Long>> addAdGroup(
+            @RequestParam String accessToken,
+            @RequestBody AdGroupDTO.AddRequest request) {
+        TencentApiResponse<Long> response = adGroupClient.addAdGroup(accessToken, request);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @PostMapping("/adgroup/update")
+    public CommonResponse<TencentApiResponse<Void>> updateAdGroup(
+            @RequestParam String accessToken,
+            @RequestBody AdGroupDTO.UpdateRequest request) {
+        TencentApiResponse<Void> response = adGroupClient.updateAdGroup(accessToken, request);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @PostMapping("/adgroup/updateBid")
+    public CommonResponse<TencentApiResponse<Void>> updateBidAmount(
+            @RequestParam String accessToken,
+            @RequestParam Long accountId,
+            @RequestParam Long adgroupId,
+            @RequestParam Integer bidAmount) {
+        TencentApiResponse<Void> response = adGroupClient.updateBidAmount(accessToken, accountId, adgroupId, bidAmount);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @PostMapping("/adgroup/updateStatus")
+    public CommonResponse<TencentApiResponse<Void>> updateAdGroupStatus(
+            @RequestParam String accessToken,
+            @RequestParam Long accountId,
+            @RequestParam Long adgroupId,
+            @RequestParam String configuredStatus) {
+        TencentApiResponse<Void> response = adGroupClient.updateStatus(accessToken, accountId, adgroupId, configuredStatus);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @PostMapping("/adgroup/delete")
+    public CommonResponse<TencentApiResponse<Void>> deleteAdGroup(
+            @RequestParam String accessToken,
+            @RequestParam Long accountId,
+            @RequestParam Long adgroupId) {
+        TencentApiResponse<Void> response = adGroupClient.deleteAdGroup(accessToken, accountId, adgroupId);
+        return CommonResponse.success(response);
+    }
+
+    // ==================== 创意 ====================
+
+    @JwtIgnore
+    @PostMapping("/creative/get")
+    public CommonResponse<TencentApiResponse<TencentApiResponse.ListData<CreativeDTO.CreativeInfo>>> getCreatives(
+            @RequestParam String accessToken,
+            @RequestBody CreativeDTO.GetRequest request) {
+        TencentApiResponse<TencentApiResponse.ListData<CreativeDTO.CreativeInfo>> response =
+                creativeClient.getCreatives(accessToken, request);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @GetMapping("/creative/getOne")
+    public CommonResponse<TencentApiResponse<CreativeDTO.CreativeInfo>> getCreative(
+            @RequestParam String accessToken,
+            @RequestParam Long accountId,
+            @RequestParam Long dynamicCreativeId) {
+        TencentApiResponse<CreativeDTO.CreativeInfo> response =
+                creativeClient.getCreative(accessToken, accountId, dynamicCreativeId);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @GetMapping("/creative/getByAdGroup")
+    public CommonResponse<TencentApiResponse<TencentApiResponse.ListData<CreativeDTO.CreativeInfo>>> getCreativesByAdGroup(
+            @RequestParam String accessToken,
+            @RequestParam Long accountId,
+            @RequestParam Long adgroupId) {
+        TencentApiResponse<TencentApiResponse.ListData<CreativeDTO.CreativeInfo>> response =
+                creativeClient.getCreativesByAdGroup(accessToken, accountId, adgroupId);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @PostMapping("/creative/add")
+    public CommonResponse<TencentApiResponse<Long>> addCreative(
+            @RequestParam String accessToken,
+            @RequestBody CreativeDTO.AddRequest request) {
+        TencentApiResponse<Long> response = creativeClient.addCreative(accessToken, request);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @PostMapping("/creative/update")
+    public CommonResponse<TencentApiResponse<Void>> updateCreative(
+            @RequestParam String accessToken,
+            @RequestBody CreativeDTO.UpdateRequest request) {
+        TencentApiResponse<Void> response = creativeClient.updateCreative(accessToken, request);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @PostMapping("/creative/updateStatus")
+    public CommonResponse<TencentApiResponse<Void>> updateCreativeStatus(
+            @RequestParam String accessToken,
+            @RequestParam Long accountId,
+            @RequestParam Long dynamicCreativeId,
+            @RequestParam String configuredStatus) {
+        TencentApiResponse<Void> response = creativeClient.updateCreativeStatus(accessToken, accountId, dynamicCreativeId, configuredStatus);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @PostMapping("/creative/delete")
+    public CommonResponse<TencentApiResponse<Void>> deleteCreative(
+            @RequestParam String accessToken,
+            @RequestParam Long accountId,
+            @RequestParam Long dynamicCreativeId) {
+        TencentApiResponse<Void> response = creativeClient.deleteCreative(accessToken, accountId, dynamicCreativeId);
+        return CommonResponse.success(response);
+    }
+
+    // ==================== 创意组件 ====================
+
+    @JwtIgnore
+    @PostMapping("/component/get")
+    public CommonResponse<TencentApiResponse<TencentApiResponse.ListData<ComponentDTO.ComponentInfo>>> getComponents(
+            @RequestParam String accessToken,
+            @RequestBody ComponentDTO.GetRequest request) {
+        TencentApiResponse<TencentApiResponse.ListData<ComponentDTO.ComponentInfo>> response =
+                componentClient.getComponents(accessToken, request);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @PostMapping("/component/add")
+    public CommonResponse<TencentApiResponse<Long>> addComponent(
+            @RequestParam String accessToken,
+            @RequestBody ComponentDTO.AddRequest request) {
+        TencentApiResponse<Long> response = componentClient.addComponent(accessToken, request);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @PostMapping("/component/delete")
+    public CommonResponse<TencentApiResponse<Void>> deleteComponent(
+            @RequestParam String accessToken,
+            @RequestParam Long accountId,
+            @RequestParam Long componentId) {
+        TencentApiResponse<Void> response = componentClient.deleteComponent(accessToken, accountId, componentId);
+        return CommonResponse.success(response);
+    }
+
+    // ==================== 图片 ====================
+
+    @JwtIgnore
+    @PostMapping("/image/get")
+    public CommonResponse<TencentApiResponse<TencentApiResponse.ListData<ImageDTO.ImageInfo>>> getImages(
+            @RequestParam String accessToken,
+            @RequestBody ImageDTO.GetRequest request) {
+        TencentApiResponse<TencentApiResponse.ListData<ImageDTO.ImageInfo>> response =
+                imageClient.getImages(accessToken, request);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @PostMapping("/image/add")
+    public CommonResponse<TencentApiResponse<ImageDTO.AddResponse>> addImage(
+            @RequestParam String accessToken,
+            @RequestBody ImageDTO.AddRequest request) {
+        TencentApiResponse<ImageDTO.AddResponse> response = imageClient.addImage(accessToken, request);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @PostMapping("/image/delete")
+    public CommonResponse<TencentApiResponse<Void>> deleteImage(
+            @RequestParam String accessToken,
+            @RequestParam Long accountId,
+            @RequestParam String imageId) {
+        TencentApiResponse<Void> response = imageClient.deleteImage(accessToken, accountId, imageId);
+        return CommonResponse.success(response);
+    }
+
+    // ==================== 视频 ====================
+
+    @JwtIgnore
+    @PostMapping("/video/get")
+    public CommonResponse<TencentApiResponse<TencentApiResponse.ListData<VideoDTO.VideoInfo>>> getVideos(
+            @RequestParam String accessToken,
+            @RequestBody VideoDTO.GetRequest request) {
+        TencentApiResponse<TencentApiResponse.ListData<VideoDTO.VideoInfo>> response =
+                videoClient.getVideos(accessToken, request);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @GetMapping("/video/getOne")
+    public CommonResponse<TencentApiResponse<VideoDTO.VideoInfo>> getVideo(
+            @RequestParam String accessToken,
+            @RequestParam Long accountId,
+            @RequestParam Long videoId) {
+        TencentApiResponse<VideoDTO.VideoInfo> response = videoClient.getVideo(accessToken, accountId, videoId);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @PostMapping("/video/add")
+    public CommonResponse<TencentApiResponse<VideoDTO.AddResponse>> addVideo(
+            @RequestParam String accessToken,
+            @RequestBody VideoDTO.AddRequest request) {
+        TencentApiResponse<VideoDTO.AddResponse> response = videoClient.addVideo(accessToken, request);
+        return CommonResponse.success(response);
+    }
+
+    // ==================== 落地页 ====================
+
+    @JwtIgnore
+    @PostMapping("/page/get")
+    public CommonResponse<TencentApiResponse<TencentApiResponse.ListData<PageDTO.PageInfo>>> getPages(
+            @RequestParam String accessToken,
+            @RequestBody PageDTO.GetRequest request) {
+        TencentApiResponse<TencentApiResponse.ListData<PageDTO.PageInfo>> response =
+                pageClient.getPages(accessToken, request);
+        return CommonResponse.success(response);
+    }
+
+    // ==================== 客户人群 ====================
+
+    @JwtIgnore
+    @PostMapping("/audience/get")
+    public CommonResponse<TencentApiResponse<TencentApiResponse.ListData<AudienceDTO.AudienceInfo>>> getAudiences(
+            @RequestParam String accessToken,
+            @RequestBody AudienceDTO.GetRequest request) {
+        TencentApiResponse<TencentApiResponse.ListData<AudienceDTO.AudienceInfo>> response =
+                customAudienceClient.getAudiences(accessToken, request);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @PostMapping("/audience/add")
+    public CommonResponse<TencentApiResponse<Long>> addAudience(
+            @RequestParam String accessToken,
+            @RequestBody AudienceDTO.AddRequest request) {
+        TencentApiResponse<Long> response = customAudienceClient.addAudience(accessToken, request);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @PostMapping("/audience/file/upload")
+    public CommonResponse<TencentApiResponse<String>> uploadAudienceFile(
+            @RequestParam String accessToken,
+            @RequestBody AudienceDTO.FileAddRequest request) {
+        TencentApiResponse<String> response = customAudienceClient.uploadAudienceFile(accessToken, request);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @GetMapping("/audience/file/get")
+    public CommonResponse<TencentApiResponse<String>> getAudienceFiles(
+            @RequestParam String accessToken,
+            @RequestParam Long accountId,
+            @RequestParam(required = false) Long audienceId,
+            @RequestParam(required = false) Integer page,
+            @RequestParam(required = false) Integer pageSize) {
+        TencentApiResponse<String> response = customAudienceClient.getAudienceFiles(accessToken, accountId, audienceId, page, pageSize);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @PostMapping("/audience/push")
+    public CommonResponse<TencentApiResponse<Void>> pushAudience(
+            @RequestParam String accessToken,
+            @RequestBody AudienceDTO.PushRequest request) {
+        TencentApiResponse<Void> response = customAudienceClient.pushAudience(accessToken, request);
+        return CommonResponse.success(response);
+    }
+
+    // ==================== 用户行为数据 ====================
+
+    @JwtIgnore
+    @PostMapping("/useraction/add")
+    public CommonResponse<TencentApiResponse<Void>> addUserActions(
+            @RequestParam String accessToken,
+            @RequestBody UserActionDTO.UserActionAddRequest request) {
+        TencentApiResponse<Void> response = userActionClient.addUserActions(accessToken, request);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @PostMapping("/useraction/addRaw")
+    public CommonResponse<String> addUserActionsRaw(
+            @RequestParam String accessToken,
+            @RequestBody String jsonBody) {
+        String response = userActionClient.addUserActionsRaw(accessToken, jsonBody);
+        return CommonResponse.success(response);
+    }
+
+    // ==================== 用户行为数据源 ====================
+
+    @JwtIgnore
+    @PostMapping("/useractionset/get")
+    public CommonResponse<TencentApiResponse<TencentApiResponse.ListData<UserActionDTO.UserActionSetInfo>>> getUserActionSets(
+            @RequestParam String accessToken,
+            @RequestBody UserActionDTO.UserActionSetGetRequest request) {
+        TencentApiResponse<TencentApiResponse.ListData<UserActionDTO.UserActionSetInfo>> response =
+                userActionSetClient.getUserActionSets(accessToken, request);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @PostMapping("/useractionset/add")
+    public CommonResponse<TencentApiResponse<Long>> addUserActionSet(
+            @RequestParam String accessToken,
+            @RequestBody UserActionDTO.UserActionSetAddRequest request) {
+        TencentApiResponse<Long> response = userActionSetClient.addUserActionSet(accessToken, request);
+        return CommonResponse.success(response);
+    }
+
+    // ==================== 批量操作 ====================
+
+    @JwtIgnore
+    @PostMapping("/batch/requests")
+    public CommonResponse<TencentApiResponse<BatchDTO.BatchResponse>> batchRequests(
+            @RequestParam String accessToken,
+            @RequestBody BatchDTO.BatchRequest request) {
+        TencentApiResponse<BatchDTO.BatchResponse> response = batchClient.batchRequests(accessToken, request);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @PostMapping("/batch/updateDailyBudget")
+    public CommonResponse<TencentApiResponse<Void>> batchUpdateDailyBudget(
+            @RequestParam String accessToken,
+            @RequestBody BatchDTO.UpdateDailyBudgetRequest request) {
+        TencentApiResponse<Void> response = batchClient.updateDailyBudget(accessToken, request);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @PostMapping("/batch/updateStatus")
+    public CommonResponse<TencentApiResponse<Void>> batchUpdateStatus(
+            @RequestParam String accessToken,
+            @RequestBody BatchDTO.UpdateStatusRequest request) {
+        TencentApiResponse<Void> response = batchClient.updateStatus(accessToken, request);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @PostMapping("/batch/updateBidAmount")
+    public CommonResponse<TencentApiResponse<Void>> batchUpdateBidAmount(
+            @RequestParam String accessToken,
+            @RequestBody BatchDTO.UpdateBidAmountRequest request) {
+        TencentApiResponse<Void> response = batchClient.updateBidAmount(accessToken, request);
+        return CommonResponse.success(response);
+    }
+
+    // ==================== 报表 ====================
+
+    @JwtIgnore
+    @PostMapping("/report/daily/get")
+    public CommonResponse<TencentApiResponse<TencentApiResponse.ListData<ReportDTO.DailyReportData>>> getDailyReport(
+            @RequestParam String accessToken,
+            @RequestBody ReportDTO.DailyReportGetRequest request) {
+        TencentApiResponse<TencentApiResponse.ListData<ReportDTO.DailyReportData>> response =
+                dailyReportClient.getDailyReport(accessToken, request);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @GetMapping("/report/adgroup/get")
+    public CommonResponse<TencentApiResponse<TencentApiResponse.ListData<ReportDTO.DailyReportData>>> getAdGroupReport(
+            @RequestParam String accessToken,
+            @RequestParam Long accountId,
+            @RequestParam(required = false) Long adgroupId,
+            @RequestParam String startDate,
+            @RequestParam String endDate,
+            @RequestParam(required = false) Integer page,
+            @RequestParam(required = false) Integer pageSize) {
+        TencentApiResponse<TencentApiResponse.ListData<ReportDTO.DailyReportData>> response =
+                dailyReportClient.getAdGroupReport(accessToken, accountId, adgroupId, startDate, endDate, page, pageSize);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @GetMapping("/report/creative/get")
+    public CommonResponse<TencentApiResponse<TencentApiResponse.ListData<ReportDTO.DailyReportData>>> getCreativeReport(
+            @RequestParam String accessToken,
+            @RequestParam Long accountId,
+            @RequestParam(required = false) Long creativeId,
+            @RequestParam String startDate,
+            @RequestParam String endDate,
+            @RequestParam(required = false) Integer page,
+            @RequestParam(required = false) Integer pageSize) {
+        TencentApiResponse<TencentApiResponse.ListData<ReportDTO.DailyReportData>> response =
+                dailyReportClient.getCreativeReport(accessToken, accountId, creativeId, startDate, endDate, page, pageSize);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @GetMapping("/report/component/get")
+    public CommonResponse<TencentApiResponse<TencentApiResponse.ListData<ReportDTO.DailyReportData>>> getComponentReport(
+            @RequestParam String accessToken,
+            @RequestParam Long accountId,
+            @RequestParam String startDate,
+            @RequestParam String endDate,
+            @RequestParam(required = false) Integer page,
+            @RequestParam(required = false) Integer pageSize) {
+        TencentApiResponse<TencentApiResponse.ListData<ReportDTO.DailyReportData>> response =
+                dailyReportClient.getComponentReport(accessToken, accountId, startDate, endDate, page, pageSize);
+        return CommonResponse.success(response);
+    }
+
+    // ==================== 公众号 ====================
+
+    @JwtIgnore
+    @PostMapping("/wechat/profile/add")
+    public CommonResponse<TencentApiResponse<Long>> addProfile(
+            @RequestParam String accessToken,
+            @RequestBody WechatDTO.ProfileAddRequest request) {
+        TencentApiResponse<Long> response = wechatClient.addProfile(accessToken, request);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @PostMapping("/wechat/profile/delete")
+    public CommonResponse<TencentApiResponse<Void>> deleteProfile(
+            @RequestParam String accessToken,
+            @RequestParam Long accountId,
+            @RequestParam Long profileId) {
+        TencentApiResponse<Void> response = wechatClient.deleteProfile(accessToken, accountId, profileId);
+        return CommonResponse.success(response);
+    }
+
+    @JwtIgnore
+    @GetMapping("/wechat/profile/get")
+    public CommonResponse<TencentApiResponse<TencentApiResponse.ListData<WechatDTO.ProfileInfo>>> getProfiles(
+            @RequestParam String accessToken,
+            @RequestParam Long accountId,
+            @RequestParam(required = false) Integer page,
+            @RequestParam(required = false) Integer pageSize) {
+        TencentApiResponse<TencentApiResponse.ListData<WechatDTO.ProfileInfo>> response =
+                wechatClient.getProfiles(accessToken, accountId, page, pageSize);
+        return CommonResponse.success(response);
+    }
+}

+ 19 - 0
server/src/main/resources/application.yml

@@ -66,3 +66,22 @@ logging:
 cdn:
   upload:
     domain: https://weappupload.piaoquantv.com/
+
+# 腾讯广告 Marketing API 配置
+tencent:
+  marketing:
+    client-id: 1112023813
+    client-secret: 2990fdc7c1518ddd9ff9e18e0dd76afe
+    redirect-uri: https://api.piaoquantv.com/ad/put/tencent/authGetAccessToken
+    connect-timeout: 5000
+    read-timeout: 30000
+    write-timeout: 30000
+    max-connections: 100
+    retry-count: 3
+
+# RTA 管理 API 配置
+rta:
+  manage:
+    rta-id: ${RTA_ID:}
+    token: ${RTA_TOKEN:}
+    timeout-ms: 5000

+ 0 - 126
server/src/test/java/GeminiTest.java

@@ -1,126 +0,0 @@
-import com.alibaba.fastjson.JSONObject;
-import com.tzld.rta.Application;
-import com.tzld.rta.api.google.GeminiApiService;
-import com.tzld.rta.api.google.ModelEnum;
-import lombok.extern.slf4j.Slf4j;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-
-import java.io.IOException;
-
-@SpringBootTest(classes = Application.class)
-@Slf4j
-public class GeminiTest {
-    @Autowired
-    private GeminiApiService geminiApiService;
-
-//    @Value("title.analyse.systemprompt:")
-//    private String systemPrompt;
-
-    @Test
-    public void testGemini() {
-        String systemPrompt = "# 角色\n" +
-                "你是一名严格的,为中国50岁以上中老年群体做热点内容筛选的内容专家。你非常严格的判断内容是否“适老”,你需要逐个分析给到的标题信息,根据目标用户画像的特征,剔除所有属于城市年轻群体、中产阶级焦虑、高认知门槛、语义模糊或与老年人生活脱节的“噪音”,保留符合「中国 50 岁以上中老年人」用户画像的内容。\n" +
-                "\n" +
-                "# 一、基础定义(严格遵守,不可修改)\n" +
-                "## 用户画像:中国50岁以上老年人\n" +
-                "1. **认知特点**: 追求“确定性”和“安全感”。偏好简单直白的内容,拒绝烧脑、逻辑复杂或需要推理的内容。拒绝一切“盲盒式”标题(如“这件事千万别做”但没说什么事)。不关注新事物,不关注抽象的宏观经济指标、复杂的金融博弈、枯燥的行政程序\n" +
-                "2. **文化背景**: 成长于上世纪50~70年代,传统观念根深蒂固。对网络梗、亚文化、职场黑话、微短剧逻辑不敏感甚至反感。深受儒家文化影响,拥有强烈的孝道观念和集体主义倾向,对投资及房产等与年轻人生活相关度高的信息不感兴趣, 处于“安享期”而非“奋斗期”。关注“保命”(三高、心脏、防骗)而非“塑形”(减肥、发际线);关注“存量财产安全”而非“增量资产博弈”。更倾向于追求身心安宁及正能量内容,不喜欢高强度、信息量大的娱乐内容。\n" +
-                "3. **情感需求**: “安逸”、“从容”、“被尊重”。倾向于追求身心安宁、正能量、民族自豪感。反感贩卖焦虑、激烈的矛盾冲突、血腥暴力或过于悲惨的负面新闻。偏好娱乐放松、激发民族自豪感的国家大事、与中国相关的重大国际形式、民生生活、弘扬社会正能量、家庭生活、传统文化、时事政务、同龄人出镜的内容,需要被“认同感”及“群体认同感”,偏好接地气、贴近生活、贴近自身的内容,叙事风格\n" +
-                "4. **场景偏好**: 菜市场、公园、家庭、医院、老友聚会等怀旧场景。排斥写字楼、夜店、高端滑雪场、极限运动场所。\n" +
-                "\n" +
-                "## 适老品类库(白名单)\n" +
-                "只有核心内容属于以下分类,才具备“品类适老系数=1”的基础条件:\n" +
-                "*   **国家力量/统一**: 阅兵、基建狂魔、外交胜利、撤侨、领土主权、两岸统一(不含晦涩的地缘政治分析),以非具体正能量人物、非科学、技术为主要内容;反映中国人文、文化、基建、人民生活、国内外对比中强大的内容\n" +
-                "*   **知识科普**:非生活技巧、非科技自然 的 文化、历史、人文、健康等社科知识类科普视\n" +
-                "*   **惠民/民生政策**: 养老金调整、医保报销、现金支付保障、菜篮子物价、天气预警(需具体利民,非枯燥公文)。\n" +
-                "*   **人财诈骗/防骗**: 电诈案例、新型毒品伪装、保健品骗局(极高优先级)。\n" +
-                "*   **老年健康**: 三高管理、心脑血管、养生食疗、长寿知识(**严格剔除**减肥、塑形、医美、脱发焦虑)。\n" +
-                "*   **怀念时光**: 70年代及以前的老照片、老电影切片(非影评)、经典红歌。\n" +
-                "*   **家庭/亲子**: 隔辈亲、孝道故事、家庭邻里互助(**严格剔除**婆媳恶斗、剧烈伦理冲突)。\n" +
-                "*   **传统文化/习俗**: 节气、民俗、戏曲、国学、非遗。\n" +
-                "*   **正能量/社会风气**: 见义勇为、拾金不昧、反腐倡廉、平凡人的善举,以具体中国当代正能量人物为主要描述对象的内容\n" +
-                "*   **惊奇/罕见画面**: 自然奇观、动物趣闻(**严格剔除**血腥、恐怖、猎奇阴暗面)。\n" +
-                "\n" +
-                "# 二、红灯拦截标准(触犯即死,优先级最高)\n" +
-                "**不仅要检查负面词汇,更要检查“逻辑不适老”,凡触犯以下任意一条,分值直接归零,:**\n" +
-                "\n" +
-                "1.  **信源模糊/标题党拦截**:\n" +
-                "    *   标题缺乏具体的**人名、地名、机构名**,无法作为搜索关键词的(如“最爱发钱的老板招工了”、“我看这地儿不错”)。\n" +
-                "    *   主语不明、指代不清的(如“注意!这东西不能吃”、“他竟然这样做”)。\n" +
-                "    *   个人Vlog式、无公共信息价值的感悟(如“如果我在冬天失业了”)。\n" +
-                "2.  **生命阶段错位拦截**:\n" +
-                "    *   **职场焦虑**:涉及“月薪、大厂、内卷、求职(非银发专岗)、降薪、裁员、年终奖”。\n" +
-                "    *   **中年健康焦虑**:涉及“减肥、瘦身、代谢变慢、发际线、抗初老、精力管理”。\n" +
-                "    *   **高危活动**:涉及“滑雪、潜水、马拉松、蹦极、赛车”等不适合老年人生理机能的活动(即使是免费票也要拦截)。\n" +
-                "3.  **低质娱乐/亚文化拦截**:\n" +
-                "    *   **微短剧/网剧**:标题含“微短剧、网剧、霸总、逆袭、重生”等,此类内容多为虚构浮夸且含诱导付费。\n" +
-                "    *   **网络热梗**:含“破防、yyds、绝绝子、CP感、谐音梗营销”,网络热梗段子、恶搞视频、二次元的游戏黑话等\n" +
-                "    *   **专业/小众体育**:NBA交易、欧洲足球战术、球星转会费、网球/斯诺克小众赛事(除非是**国家队/为国争光**)。\n" +
-                "    *   **长辈无关娱乐圈**:年轻流量偶像八卦、饭圈互撕、国外网红动态。\n" +
-                "4.  **金融与商业噪音拦截**:\n" +
-                "    *   涉及“板块、指数、美联储、加息、IPO、资产配置、套利”的金融博弈。\n" +
-                "    *   涉及“金银价格剧烈波动分析”(如“K线、点位、抄底”),只保留单纯的实物金价涨跌。\n" +
-                "    *   明显的商业软文、带货广告、陌生APP公测。\n" +
-                "5.  **负面与恐慌拦截**:\n" +
-                "    *   过于血腥、暴力、违背伦理的案件细节(如“强奸、分尸、虐待”)。\n" +
-                "    *   纯粹贩卖焦虑而无解决方案的内容。\n" +
-                "6.行政公文拦截:没有具体利民事件的,一律不通过。\n" +
-                "7.宏观自豪感拦截: 只有国家领导人会面而无具体实惠或震撼性视觉成果(如大型阅兵、卫星发射成功)的,一律不通过。\n" +
-                "\n" +
-                "# 三、核心评分与计算逻辑\n" +
-                "## 第一步:红灯熔断审查\n" +
-                "检查标题是否触犯【二、红灯拦截标准】。\n" +
-                "*   **若触犯任意一条**:最终综合得分直接为 0.00,判定为“不通过”,reason中明确指出触犯了哪条红灯。\n" +
-                "*   **若未触犯**:进入第二步。\n" +
-                "\n" +
-                "## 第二步:用户画像维度打分(0.00 - 1.00)\n" +
-                "请根据【一、用户画像】对以下四个维度进行最严格的独立打分,如果信息模糊则全部用最低分值判断:\n" +
-                "1.  **S1 认知特点 (权重 20%)**:对应用户画像进行判断(模糊不清/专业术语/黑话=0分)\n" +
-                "2.  **S2 文化背景 (权重 30%)**:对应用户画像进行判断(个人主义/挑战传统/崇洋媚外=0分)\n" +
-                "3.  **S3 情感需求 (权重 30%)**:对应用户画像进行判断(焦虑/恐惧/悲惨/激烈冲突=0分)\n" +
-                "4.  **S4 场景偏好 (权重 20%)**:对应用户画像进行判断(CBD/夜店/极限运动/国外陌生场景=0分)\n" +
-                "\n" +
-                "## 第三步:短板效应与画像分计算\n" +
-                "*   **公式**:\n" +
-                "    IF (S1 < 0.6 OR S2 < 0.6 OR S3 < 0.6):\n" +
-                "        Persona_Score = Min(S1, S2, S3)  (任意核心维度不及格,则整体不及格)\n" +
-                "    ELSE:\n" +
-                "        Persona_Score = (S1 * 0.2) + (S2 * 0.3) + (S3 * 0.3) + (S4 * 0.2)\n" +
-                "\n" +
-                "## 第四步:最终判定\n" +
-                "*   **品类系数 K**:\n" +
-                "    *   内容核心属于白名单品类:K = 1\n" +
-                "    *   内容核心不属于白名单(如科技、游戏、职场、二次元等):K = 0\n" +
-                "*   **最终综合得分** = Persona_Score * K\n" +
-                "\n" +
-                "\n" +
-                "# 四、输出格式\n" +
-                "仅输出 JSON 格式,无多余解释。\n" +
-                "\n" +
-                "{\n" +
-                "  \"标题内容\": \"原标题\",\n" +
-                "  \"最终综合得分\": 0.00,\n" +
-                "  \"画像维度得分\": {\n" +
-                "    \"S1_认知\": 0.00,\n" +
-                "    \"S2_文化\": 0.00,\n" +
-                "    \"S3_情感\": 0.00,\n" +
-                "    \"S4_场景\": 0.00\n" +
-                "  },\n" +
-                "  \"用户画像匹配分(Persona_Score)\": 0.00,\n" +
-                "  \"品类系数(K)\": 1或0,\n" +
-                "  \"匹配品类\": \"最匹配的白名单分类或'无'\",\n" +
-                "  \"reason\": \"简要说明理由,若拦截需指出具体原因(如:信源模糊、生命阶段错位、红灯拦截等)\"\n" +
-                "}";
-        String content = "郑建成:中国现代文学评论中的金岳霖";
-
-        try {
-            JSONObject jsonObject = geminiApiService.callGemini(ModelEnum.GEMINI_3_FLASH_PREVIEW, systemPrompt,content);
-            log.info(jsonObject.toString());
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-
-    }
-}

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů