Преглед изворни кода

端原生分销商品库上传

zhaotx пре 1 година
родитељ
комит
06284aec55

+ 10 - 2
job-bytedance/src/main/java/cn/com/ctop/job/bytedance/controller/FeishuController.java

@@ -48,6 +48,8 @@ public class FeishuController {
     private IDistributionPushService distributionPushService;
     @Autowired
     private IMovieRankPushService movieRankPushService;
+    @Autowired
+    private IFeishuDocSyncService feishuDocSyncService;
 
     @Autowired
     IUserLoginRecordService userLoginRecordService;
@@ -80,10 +82,16 @@ public class FeishuController {
 //            cpsReportService.runCPSSettlementData(date);
 //            System.out.println("\n\n");
 //        }
-
 //        distributionPushService.sendToday();
 //        distributionPushService.sendYesterday();
-        movieRankPushService.send();
+//        movieRankPushService.send();
+
+//        bytedanceProductUpdateService.pullFanqiePlaylet();
+
+
+//        bytedanceProductUpdateService.runDysDistribute();
+//        bytedanceProductUpdateService.clearAuditFailProduct();
+//        feishuDocSyncService.syncBytedanceDysDistributeMovies();
     }
 
 

+ 7 - 0
job-bytedance/src/main/java/cn/com/ctop/job/bytedance/data/entity/AigcProductAlbum.java

@@ -36,4 +36,11 @@ public class AigcProductAlbum {
     private Integer startingUnlockEpisode;
 
     private Integer source;
+
+    private String albumLink;
+    private String playletId;
+    private String playletPurchasePanelId;
+    private String businessMethod;
+
+    private String productId;
 }

+ 3 - 1
job-bytedance/src/main/java/cn/com/ctop/job/bytedance/data/entity/AigcProductPlatform.java

@@ -7,7 +7,7 @@ public class AigcProductPlatform {
     private String platformId;//商品库ID
     private String advertiserId;//所属主账户ID
     private Integer type;//商品库类型 1:免费 2:付费
-    private Integer platformType;//商品库定位分类 1:抖音小程序(+微信小程序) 2:仅微信小程序
+    private Integer platformType;//商品库定位分类 1:抖音小程序(+微信小程序) 2:仅微信小程序 3:端原生
     private String platformName;//商品库名称
     private String miniInstanceId;//字节小程序实例ID
     private String updateTime;//更新时间
@@ -29,5 +29,7 @@ public class AigcProductPlatform {
 
     private String miniAppAdvertiserId;//小程序主账户ID
 
+    private String copywriteOwner;
+
 
 }

+ 10 - 0
job-bytedance/src/main/java/cn/com/ctop/job/bytedance/data/mapper/BytedanceProductUpdateMapper.java

@@ -19,6 +19,11 @@ public interface BytedanceProductUpdateMapper {
     List<AigcProductAlbum> getIAAVideoList();
     List<JSONObject> getIAAProductTypeEnums();
 
+
+    List<AigcProductPlatform> getDysIaaPlatformList();
+    List<AigcProductAlbum> getDysDistributorVideo(@Param("businessMethod") String businessMethod);
+    List<String> randomFanqieMovieImgs(@Param("count") Integer count);
+
     int updateInstanceId(@Param("advertiserId") String advertiserId, @Param("instanceId") String instanceId, @Param("source") Integer source);
 
     JSONObject getVideoInfo(@Param("videoId") Long videoId);
@@ -42,4 +47,9 @@ public interface BytedanceProductUpdateMapper {
 
     int updateMovieAuthStatus(@Param("platformId") String platformId);
 
+
+    int batchInsertFanqiePlayletInfo(@Param("list") List<JSONObject> list);
+
+    int batchInsertDysDistributePlayletInfo(List<AigcProductAlbum> list);
+
 }

+ 43 - 0
job-bytedance/src/main/java/cn/com/ctop/job/bytedance/data/mapper/xml/BytedanceProductUpdateMapper.xml

@@ -124,4 +124,47 @@
         from deepthink.bytedance_product_main_advertiser
         where type=1 and _status=0 and movie_auth_status=0
     </select>
+    <select id="getDysDistributorVideo" resultType="cn.com.ctop.job.bytedance.data.entity.AigcProductAlbum">
+        select tb1.promote_link as albumLink,
+               3 as platformType,
+               tb1.movie_name as name,
+               3 as playletGender,
+               100 as playletNum,
+               1 as playletDuration,
+               10 as startingUnlockEpisode,
+               tb1.movie_source as source,
+               tb2.movie_url as img
+        from deepthink.dys_distribute_promote_link tb1
+        left join deepthink.changdu_movie_config tb2
+        on tb1.movie_source=24 and tb1.movie_name=tb2.movie_name
+        where tb1.business_method=#{businessMethod}
+    </select>
+    <select id="getDysIaaPlatformList" resultType="cn.com.ctop.job.bytedance.data.entity.AigcProductPlatform">
+        select t1.platform_id, t1.advertiser_id, t1.type, t1.platform_type, t1.platform_name, t1.mini_instance_id, t1.update_time, t1.create_time, t1.mi_appid, t1.membership_types, t1.max_charge, t1.min_charge, t1.recommended_charge, t1.single_price, t1._status, t1.social_credit_code, t1.movie_auth_status, t1.is_bytedance, t1.is_wechat, t1.is_dys, t1.source, ifnull(t1.mini_app_advertiser_id,t1.advertiser_id) as mini_app_advertiser_id, t3.access_token
+        FROM deepthink.bytedance_product_main_advertiser t1
+                 left join deepthink.bytedance_advertiser_list t2 on t1.advertiser_id=t2.advertiser_id
+                 left join deepthink.bytedance_auth_list t3 on t2.admin_advertiser_id=t3.admin_advertiser_id
+        where t1.`_status`=0 and platform_type=3 and t1.is_dys=1
+    </select>
+    <select id="randomFanqieMovieImgs" resultType="java.lang.String">
+        select movie_url
+        from deepthink.changdu_movie_config
+        limit #{count}
+    </select>
+    <insert id="batchInsertFanqiePlayletInfo" >
+        replace into deepthink.dys_distribute_promote_link
+        (playlet_id, playlet_purchase_panel_id, movie_name, promote_link, business_method, movie_source)
+        values
+        <foreach collection="list" item="item" separator=",">
+            (#{item.playletId}, #{item.playletPurchasePanelId}, #{item.movieName}, #{item.promoteLink}, 'IAA',24)
+        </foreach>
+    </insert>
+    <insert id="batchInsertDysDistributePlayletInfo">
+        replace into deepthink.dys_distribute_promote_link
+        (playlet_id, playlet_purchase_panel_id, movie_name, promote_link, business_method, movie_source)
+        values
+        <foreach collection="list" item="item" separator=",">
+            (#{item.playletId}, #{item.playletPurchasePanelId}, #{item.name}, #{item.albumLink}, #{item.businessMethod}, #{item.source})
+        </foreach>
+    </insert>
 </mapper>

+ 7 - 1
job-bytedance/src/main/java/cn/com/ctop/job/bytedance/data/service/IBytedanceProductUpdateService.java

@@ -4,7 +4,9 @@ import java.io.IOException;
 
 public interface IBytedanceProductUpdateService {
 
-    void run() throws Exception;
+    void runMp() throws Exception;
+
+    void runDysDistribute() throws IOException;
 
     void authMovieResource();
 
@@ -16,4 +18,8 @@ public interface IBytedanceProductUpdateService {
     void uploadAlbum() throws IOException;
 
     void refreshAlbumStatus() throws IOException;
+
+    void pullFanqiePlaylet();
+
+    void clearAuditFailProduct();
 }

+ 17 - 0
job-bytedance/src/main/java/cn/com/ctop/job/bytedance/data/service/IFeishuDocSyncService.java

@@ -0,0 +1,17 @@
+package cn.com.ctop.job.bytedance.data.service;
+
+public interface IFeishuDocSyncService {
+
+    /**
+     * 同步巨量 IAA 商品库
+     */
+    void syncBytedanceIaaProductPlatform();
+    /**
+     * 同步巨量 IAP 商品库
+     */
+    void syncBytedanceIapProductPlatform();
+    /**
+     * 同步巨量 端原生分销剧目
+     */
+    void syncBytedanceDysDistributeMovies();
+}

+ 314 - 14
job-bytedance/src/main/java/cn/com/ctop/job/bytedance/data/service/impl/BytedanceProductUpdateServiceImpl.java

@@ -6,14 +6,17 @@ import cn.com.ctop.job.bytedance.data.mapper.BytedanceProductUpdateMapper;
 import cn.com.ctop.job.bytedance.data.service.IBytedanceProductUpdateService;
 import cn.com.ctop.job.bytedance.data.utils.Check;
 import cn.com.ctop.job.bytedance.data.utils.HttpUtils;
+import cn.com.ctop.job.bytedance.utils.BytedanceProductDocProperties;
 import cn.com.ctop.job.bytedance.utils.FeishuRobotUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
+import org.springframework.web.util.UriComponentsBuilder;
 
 import javax.annotation.Resource;
 import java.io.IOException;
@@ -26,6 +29,9 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
     BytedanceProductUpdateMapper bytedanceProductUpdateMapper;
     @Resource
     private RedisTemplate redis2Template_db5;
+    @Autowired
+    BytedanceProductDocProperties productDocProperties;
+
 
     public static final String DOUYIN_MOVIE_SOURCE_APP="ttd652a0ef496310bd01";
 
@@ -40,7 +46,7 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
     public static final String HOOK_URL = "https://open.feishu.cn/open-apis/bot/v2/hook/92e2445e-76df-44fb-8f61-f55ac94f49bd";
 
     @Override
-    public void run() throws Exception {
+    public void runMp() throws Exception {
         Set<String> errors = new HashSet<>();
         log.info("付费商品库更新中...");
         List<AigcProductPlatform> iapPlatformList = bytedanceProductUpdateMapper.getIAPPlatformList(); //商品库列表
@@ -51,7 +57,7 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
                 obj -> obj,
                 (existing, replacement) -> replacement
         ));
-        runProductTask(iapPlatformList, iapVideoList, iapProductTypeMap, errors, BusinessType.IAP);
+        runMpProductTask(iapPlatformList, iapVideoList, iapProductTypeMap, errors, BusinessType.IAP);
         log.info("付费商品库更新完成");
 
 
@@ -65,7 +71,7 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
                 obj -> obj,
                 (existing, replacement) -> replacement
         ));
-        runProductTask(iaaPlatformList, iaaVideoList, iaaProductTypeMap, errors, BusinessType.IAA);
+        runMpProductTask(iaaPlatformList, iaaVideoList, iaaProductTypeMap, errors, BusinessType.IAA);
         log.info("免费商品库更新完成");
 
 
@@ -78,10 +84,43 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
         }
     }
 
-    private void runProductTask(List<AigcProductPlatform> platformList, List<AigcProductAlbum> albums, Map<String, JSONObject> productTypeMap, Set<String> errors, BusinessType businessType) throws IOException {
+    @Override
+    public void runDysDistribute() throws IOException {
+        Set<String> errors = new HashSet<>();
+        List<AigcProductPlatform> iaaPlatformList = bytedanceProductUpdateMapper.getDysIaaPlatformList(); //商品库列表
+        Map<String, String> sourceCopywriteOwner = new HashMap<>();
+        for (BytedanceProductDocProperties.DysDistributorLinkTable table : productDocProperties.getDysMovies()){
+            sourceCopywriteOwner.put(table.getMovieSource(), table.getCopyrightOwner());
+        }
+        iaaPlatformList.forEach(p -> {
+            p.setCopywriteOwner(sourceCopywriteOwner.get(String.valueOf(p.getSource())));
+        });
+
+        List<AigcProductAlbum> iaaVideos = bytedanceProductUpdateMapper.getDysDistributorVideo("IAA");
+        iaaVideos.forEach(v -> {
+            v.setPlayletId(getQueryParam(v.getAlbumLink(), "playlet_id"));
+            if (StringUtils.isEmpty(v.getImg())){
+                v.setImg("https://oss.artlinkworld.cn/imgsource/20251110-144804.png");//兜底
+            }
+        });
+
+        runDysProductTask(iaaPlatformList, iaaVideos, new HashMap<>(), errors, BusinessType.IAA);
+        System.out.println("端原生分销商品库更新完成");
+
+        errors.removeIf(s -> s.contains("剧目不可用"));
+        if (!Check.isNull(errors)){
+            List<String> info = new ArrayList<>();
+            info.add("端原生分销商品上传报错通知");
+            info.addAll(errors);
+            FeishuRobotUtil.sendTextToWebhook(StringUtils.join(info, "\n"), HOOK_URL);
+        }
+    }
+
+    private void runMpProductTask(List<AigcProductPlatform> platformList, List<AigcProductAlbum> albums, Map<String, JSONObject> productTypeMap, Set<String> errors, BusinessType businessType) throws IOException {
         for (AigcProductPlatform platform : platformList){
             // 拉取已上传剧目,对比,得到未上传剧目列表
-            List<String> hasUpload = hasUploadProductList(platform);
+            List<AigcProductAlbum> hasUploadProductList = hasUploadProductList(platform);
+            List<String> hasUpload = hasUploadProductList.stream().map(AigcProductAlbum::getProductId).collect(Collectors.toList());
             System.out.println("已上传剧目数: "+hasUpload.size() + ", 商品库ID: "+platform.getPlatformId()+", 来源:"+platform.getSource());
             List<AigcProductAlbum> toUpload = albums.stream()
                     .filter(o -> Objects.equals(o.getSource(), platform.getSource()))
@@ -121,16 +160,49 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
         }
     }
 
+
+    private void runDysProductTask(List<AigcProductPlatform> platformList, List<AigcProductAlbum> albums, Map<String, JSONObject> productTypeMap, Set<String> errors, BusinessType businessType) throws IOException {
+        for (AigcProductPlatform platform : platformList){
+            // 拉取已上传剧目,对比,得到未上传剧目列表
+            List<AigcProductAlbum> hasUploadAlbums = hasUploadProductList(platform);
+            List<String> hasUploadPlayletIds = hasUploadAlbums.stream().map(AigcProductAlbum::getPlayletId).collect(Collectors.toList());
+//            System.out.println("已上传剧目数: "+hasUpload.size() + ", 商品库ID: "+platform.getPlatformId()+", 来源:"+platform.getSource());
+            List<AigcProductAlbum> toUpload = albums.stream()
+                    .filter(o -> !hasUploadPlayletIds.contains(o.getPlayletId()))
+                    .filter(o -> Objects.equals(o.getPlatformType(), String.valueOf(platform.getPlatformType())))
+                    .filter(o -> Objects.equals(o.getSource(), platform.getSource()))
+                    // 根据link里的参数过滤已上传的
+                    .collect(Collectors.toList());
+            System.out.println("待上传剧目数: "+toUpload.size() + ", 商品库ID: "+platform.getPlatformId()+", 来源:"+platform.getSource());
+
+//            if (1==1){
+//                return;
+//            }
+            if (toUpload.isEmpty()){
+                continue;
+            }
+
+            //未上传剧目:对比平台类型;确定商品分类;获取推广链接
+            for (AigcProductAlbum album : toUpload){
+                JSONObject defaultTheme = JSONObject.parseObject("{\"first_type_id\":\"2038\",\"first_type_name\":\"单剧目-短剧\",\"second_type_id\":\"203807\",\"second_type_name\":\"其他剧情\",\"third_type_name\":\"其他\",\"third_type_id\":\"20380701\"}");
+                uploadProduct(platform, album, null, errors, defaultTheme, businessType);
+                try {
+                    Thread.sleep(80);
+                }catch (InterruptedException ignored){}
+            }
+        }
+    }
+
     /**
      * 当前商品库中,已经上传的商品列表
      * @param platform
      * @return
      */
-    private List<String> hasUploadProductList(AigcProductPlatform platform){
-        return hasUploadProductPageList(platform, 1, 100);
+    private List<AigcProductAlbum> hasUploadProductList(AigcProductPlatform platform){
+        return hasUploadProductPageList(platform, 1, 100, null);
     }
 
-    private List<String> hasUploadProductPageList(AigcProductPlatform platform, int page, int pageSize){
+    private List<AigcProductAlbum> hasUploadProductPageList(AigcProductPlatform platform, int page, int pageSize, Integer auditStatus){
         String url = "https://api.oceanengine.com/open_api/2/dpa/detail/get/";
         Map<String, String> headers = new HashMap<>();
         headers.put("Access-Token", platform.getAccessToken());
@@ -139,6 +211,13 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
         params.put("product_platform_id", Long.valueOf(platform.getPlatformId()));
         params.put("page", String.valueOf(page));
         params.put("page_size", String.valueOf(pageSize));
+        JSONObject filtering = new JSONObject();
+        if (auditStatus != null){
+            filtering.put("audit_status", auditStatus);
+        }
+        if (!filtering.isEmpty()){
+            params.put("filtering", filtering);
+        }
         String res = HttpUtils.httpGet(url, params, headers);
         if (res == null || res.isEmpty()) {
             System.out.println("获取已上传商品列表失败, 广告账号ID:" + platform.getAdvertiserId() + ", 商品库ID:" + platform.getPlatformId());
@@ -154,16 +233,24 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
                 if (list == null || list.isEmpty()){
                     return new ArrayList<>();
                 }
-                List<String> resList = new ArrayList<>(list.size());
+                List<AigcProductAlbum> resList = new ArrayList<>(list.size());
                 for (int i = 0; i < list.size(); i++) {
                     JSONObject obj = list.getJSONObject(i);
-                    resList.add(String.valueOf(obj.getLongValue("product_id")));
+                    AigcProductAlbum album = new AigcProductAlbum();
+                    album.setProductId(String.valueOf(obj.getLongValue("product_id")));
+                    album.setName(obj.getString("name"));
+                    JSONObject profession = obj.getJSONObject("profession");
+                    if (profession!=null && profession.containsKey("album_link")){
+                        album.setAlbumLink(profession.getString("album_link"));
+                        album.setPlayletId(getQueryParam(album.getAlbumLink(), "playlet_id"));
+                    }
+                    resList.add(album);
                 }
                 JSONObject page_info = jsonObject.getJSONObject("data").getJSONObject("page_info");
                 int total_page = page_info.getIntValue("total_page");
                 if (total_page > page){
                     //递归获取下一页
-                    List<String> nextPage = hasUploadProductPageList(platform, page + 1, pageSize);
+                    List<AigcProductAlbum> nextPage = hasUploadProductPageList(platform, page + 1, pageSize, auditStatus);
                     resList.addAll(nextPage);
                 }
                 return resList;
@@ -299,14 +386,21 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
     private void uploadProduct(AigcProductPlatform platform, AigcProductAlbum album,
                                   String schemaLink, Set<String> errorBlanket, JSONObject productType,
                               BusinessType businessType){
-        String url = "https://ad.oceanengine.com/open_api/2/dpa/product/update/";
+        String url;
+        if (StringUtils.isEmpty(album.getAlbumId())){
+            url = "https://ad.oceanengine.com/open_api/2/dpa/product/create/";
+        }else {
+            url = "https://ad.oceanengine.com/open_api/2/dpa/product/update/";
+        }
         Map<String, String> headers = new HashMap<>();
         headers.put("Access-Token", platform.getAccessToken());
         headers.put("Content-Type", "application/json");
 
         JSONObject request = new JSONObject();
         String album_id = album.getAlbumId();
-        request.put("product_id", Long.valueOf(album_id));
+        if (StringUtils.isNotEmpty(album_id)){
+            request.put("product_id", Long.valueOf(album_id));
+        }
         JSONObject product_info = new JSONObject();
         product_info.put("name", album.getName());
         product_info.put("image_url", album.getImg());
@@ -363,8 +457,10 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
         }
         if (platform.getIsBytedance() == 1){
             profession.put("micro_app_link", schemaLink);
-        }else {
+        }else if (platform.getIsWechat() == 1){
             profession.put("album_id", album_id);
+        }else if (platform.getIsDys() == 1){
+            profession.put("album_link", album.getAlbumLink());
         }
         profession.put("ad_carrier", JSON.toJSONString(ad_carrier));
         product_info.put("profession", profession);
@@ -376,6 +472,7 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
 //        System.out.println("请求参数:" + request.toJSONString());
         String res = HttpUtils.httpPostRequest(url, request, headers);
         JSONObject btRes = JSONObject.parseObject(res);
+//        System.out.println(btRes.toJSONString());
         int code = btRes.getIntValue("code");
         if (code != 0){
             String payName = businessType==BusinessType.IAP?"付费":"免费";
@@ -647,6 +744,202 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
     }
 
 
+    public static final String FEISHU_EXCEL_TOKEN = "HGZYw1YKRiidYgkpzRocokyPnUd";
+    public static final String FEISHU_EXCEL_SHEETID = "add053";
+    public static final String FEISHU_EXCEL_OLD_SHEETID = "krjaDd";
+    @Override
+    public void pullFanqiePlaylet() {
+        String userToken = "u-eQOevKV793.WrHbW0yAQVl517s14h0UhjG20g1OGyx6u";
+        int startRow = 2;
+        int batchSize = 100;
+        int endRow = startRow + batchSize - 1;
+
+        while(!pullFanqieNewPlayletSheet(null, userToken, startRow, endRow)){
+            startRow = endRow + 1;
+            endRow = startRow + batchSize - 1;
+        }
+
+        while(!pullFanqieOldPlayletSheet(null, userToken, startRow, endRow)){
+            startRow = endRow + 1;
+            endRow = startRow + batchSize - 1;
+        }
+
+    }
+
+    @Override
+    public void clearAuditFailProduct() {
+        List<AigcProductPlatform> iaaPlatformList = bytedanceProductUpdateMapper.getDysIaaPlatformList(); //商品库列表
+        for (AigcProductPlatform platform : iaaPlatformList){
+            List<AigcProductAlbum> albums = hasUploadProductPageList(platform, 1, 100, 2); //审核失败的商品列表
+//            albums = albums.stream().filter(al -> StringUtils.isEmpty(al.getName())).collect(Collectors.toList());
+//            System.out.println(albums);
+//            if (1==1){
+//                return;
+//            }
+            if (Check.isNull(albums)) {
+                continue;
+            }
+            //删除审核失败的商品
+            for (AigcProductAlbum album : albums){
+                String url = "https://ad.oceanengine.com/open_api/2/dpa/product/delete/";
+                Map<String, String> headers = new HashMap<>();
+                headers.put("Access-Token", platform.getAccessToken());
+                Map<String, Object> params = new HashMap<>();
+                params.put("advertiser_id", Long.valueOf(platform.getAdvertiserId()));
+                params.put("platform_id", Long.valueOf(platform.getPlatformId()));
+                params.put("product_id", Long.valueOf(album.getProductId()));
+                String res = HttpUtils.httpPostRequest(url, params, headers);
+                if (res == null || res.isEmpty()){
+                    System.out.println("删除审核失败商品失败, 广告账号ID:" + platform.getAdvertiserId() + ", 商品库ID:" + platform.getPlatformId() + ", 商品ID:" + album.getProductId());
+                }else {
+                    JSONObject jsonObject = JSONObject.parseObject(res);
+                    int code = jsonObject.getIntValue("code");
+                    if (code != 0) {
+                        System.out.println("删除审核失败商品失败,code: " + code + ", msg: " + jsonObject.getString("message") + ", 广告账号ID:" + platform.getAdvertiserId() + ", 商品库ID:" + platform.getPlatformId() + ", 商品ID:" + album.getProductId());
+                    }else {
+                        System.out.println("删除审核失败商品成功,广告账号ID:" + platform.getAdvertiserId() + ", 商品库ID:" + platform.getPlatformId() + ", 商品ID:" + album.getProductId());
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * @param tenantToken 当前页签token
+     * @param userToken 用户token
+     * @param startRow 起始行
+     * @param endRow 结束行
+     * @return
+     */
+    private boolean pullFanqieNewPlayletSheet(String tenantToken, String userToken,
+                                        int startRow,
+                                        int endRow){
+        String url = "https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{spreadsheetToken}/values_batch_get?ranges={ranges}";
+        url = url.replace("{spreadsheetToken}", FEISHU_EXCEL_TOKEN);
+        String ranges = FEISHU_EXCEL_SHEETID + "!A" + startRow + ":D" + endRow;
+        url = url.replace("{ranges}", ranges);
+
+        Map<String, String> headers = new HashMap<>();
+        headers.put("Authorization", "Bearer " + userToken);
+
+        String res = HttpUtils.httpGet(url, null, headers);
+        JSONObject resultObj = JSON.parseObject(res);
+        int code = resultObj.getIntValue("code");
+        boolean endFlag = false;
+        if (code != 0){
+            System.out.println("获取飞书表格数据失败,code: " + code + ", msg: " + resultObj.getString("msg"));
+            return true;
+        }else {
+            JSONObject data = resultObj.getJSONObject("data");
+            JSONObject valueRanges = data.getJSONArray("valueRanges").getJSONObject(0);
+            JSONArray values = valueRanges.getJSONArray("values");
+
+            List<JSONObject> insertList = new ArrayList<>();
+            for (int i = 0; i < values.size(); i++) {
+                JSONArray row = values.getJSONArray(i);
+                String r1 = row.getString(0);
+                String r2 = row.getString(1);
+                String r3 = row.getString(2);
+                String r4 = row.getString(3);
+                if (r1 == null && r2 == null && r3 == null && r4 == null){
+                    endFlag = true;
+                    break;
+                }
+                JSONObject obj = new JSONObject();
+                obj.put("movieName", r1);
+                obj.put("promoteLink", r4);
+
+                String[] urlParts = r4.split("\\?");
+                if (urlParts.length > 1){
+                    String[] queryParams = urlParts[1].split("&");
+                    for (String param : queryParams){
+                        String[] keyValue = param.split("=");
+                        if (keyValue.length == 2 && "playlet_id".equals(keyValue[0])) {
+                            obj.put("playletId", keyValue[1]);
+                        }
+                        if (keyValue.length == 2 && "playlet_purchase_panel_id".equals(keyValue[0])) {
+                            String purchasePanelId = keyValue[1];
+                            obj.put("playletPurchasePanelId", purchasePanelId);
+                            if ("0".equals(purchasePanelId)){
+                                obj.put("isPay", "0");
+                            }else {
+                                obj.put("isPay", "1");
+                            }
+                        }
+                    }
+                }
+                insertList.add(obj);
+            }
+            if (!insertList.isEmpty()){
+                bytedanceProductUpdateMapper.batchInsertFanqiePlayletInfo(insertList);
+            }
+            return endFlag;
+        }
+    }
+
+    /**
+     * @param tenantToken 当前页签token
+     * @param userToken 用户token
+     * @param startRow 起始行
+     * @param endRow 结束行
+     * @return
+     */
+    private boolean pullFanqieOldPlayletSheet(String tenantToken, String userToken,
+                                              int startRow,
+                                              int endRow){
+        String url = "https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{spreadsheetToken}/values_batch_get?ranges={ranges}";
+        url = url.replace("{spreadsheetToken}", FEISHU_EXCEL_TOKEN);
+        String ranges = FEISHU_EXCEL_OLD_SHEETID + "!A" + startRow + ":C" + endRow;
+        url = url.replace("{ranges}", ranges);
+
+        Map<String, String> headers = new HashMap<>();
+        headers.put("Authorization", "Bearer " + userToken);
+
+        String res = HttpUtils.httpGet(url, null, headers);
+        JSONObject resultObj = JSON.parseObject(res);
+        int code = resultObj.getIntValue("code");
+        boolean endFlag = false;
+        if (code != 0){
+            System.out.println("获取飞书表格数据失败,code: " + code + ", msg: " + resultObj.getString("msg"));
+            return true;
+        }else {
+            JSONObject data = resultObj.getJSONObject("data");
+            JSONObject valueRanges = data.getJSONArray("valueRanges").getJSONObject(0);
+            JSONArray values = valueRanges.getJSONArray("values");
+
+            List<JSONObject> insertList = new ArrayList<>();
+            for (int i = 0; i < values.size(); i++) {
+                JSONArray row = values.getJSONArray(i);
+                String r1 = row.getString(0);
+                String r2 = row.getString(1);
+                String r3 = row.getString(2);
+                if (r1 == null && r2 == null && r3 == null){
+                    endFlag = true;
+                    break;
+                }
+                JSONObject obj = new JSONObject();
+                obj.put("movieName", r1.replace("\\r", "").replace("\\n", ""));
+                String link = r3.replace("\\r", "").replace("\\n", "");
+                obj.put("promoteLink", link);
+                String playletId = getQueryParam(link, "playlet_id");
+                String purchasePanelId = getQueryParam(link, "playlet_purchase_panel_id");
+                obj.put("playletId", playletId);
+                obj.put("playletPurchasePanelId", purchasePanelId);
+                if ("0".equals(purchasePanelId)){
+                    obj.put("isPay", "0");
+                }else {
+                    obj.put("isPay", "1");
+                }
+                insertList.add(obj);
+            }
+            if (!insertList.isEmpty()){
+                bytedanceProductUpdateMapper.batchInsertFanqiePlayletInfo(insertList);
+            }
+            return endFlag;
+        }
+    }
+
+
     /**
      * 上传短剧
      * @param videoId from ly_videos
@@ -710,6 +1003,13 @@ public class BytedanceProductUpdateServiceImpl implements IBytedanceProductUpdat
         }
     }
 
+    private static String getQueryParam(String url, String paramName){
+        return UriComponentsBuilder.fromUriString(url)
+                .build()
+                .getQueryParams()
+                .getFirst(paramName);
+    }
+
 
     private String getAppAccessToken(Long app_id, String app_secret){
         String url = "https://open.oceanengine.com/open_api/oauth2/app_access_token/";

+ 251 - 0
job-bytedance/src/main/java/cn/com/ctop/job/bytedance/data/service/impl/FeishuDocSyncServiceImpl.java

@@ -0,0 +1,251 @@
+package cn.com.ctop.job.bytedance.data.service.impl;
+
+import cn.com.ctop.job.bytedance.data.entity.AigcProductAlbum;
+import cn.com.ctop.job.bytedance.data.entity.AigcProductPlatform;
+import cn.com.ctop.job.bytedance.data.mapper.BytedanceProductUpdateMapper;
+import cn.com.ctop.job.bytedance.data.service.IFeishuDocSyncService;
+import cn.com.ctop.job.bytedance.data.utils.HttpUtils;
+import cn.com.ctop.job.bytedance.utils.BytedanceProductDocProperties;
+import cn.com.ctop.job.bytedance.utils.FeishuRobotUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import javax.annotation.Resource;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+@Slf4j
+public class FeishuDocSyncServiceImpl implements IFeishuDocSyncService {
+    @Resource
+    BytedanceProductUpdateMapper bytedanceProductUpdateMapper;
+
+    @Autowired
+    BytedanceProductDocProperties productDocProperties;
+    @Override
+    public void syncBytedanceIaaProductPlatform() {
+        String readToken;
+        try {
+            readToken = getReadToken();
+        }catch (Exception e){
+            log.error("获取飞书文档读权限token失败",e);
+            return;
+        }
+
+
+    }
+
+    @Override
+    public void syncBytedanceIapProductPlatform() {
+        String readToken;
+        try {
+            readToken = getReadToken();
+        }catch (Exception e){
+            log.error("获取飞书文档读权限token失败",e);
+            return;
+        }
+    }
+
+    @Override
+    public void syncBytedanceDysDistributeMovies() {
+        if (productDocProperties.getDysMovies()==null|| productDocProperties.getDysMovies().isEmpty()){
+            return;
+        }
+
+        String readToken;
+        try {
+            readToken = getReadToken();
+        }catch (Exception e){
+            log.error("获取飞书文档读权限token失败",e);
+            return;
+        }
+        for (BytedanceProductDocProperties.DysDistributorLinkTable table : productDocProperties.getDysMovies()) {
+            System.out.println(table.getTableId()+table.getName());
+            List<AigcProductAlbum> iaaMmovies = readMovieFromDoc(productDocProperties.getObjToken(), table.getTableId(), table.getIaaViewId(), readToken, null);
+
+            if (iaaMmovies != null && !iaaMmovies.isEmpty()){
+                iaaMmovies.forEach(m -> {
+                    m.setSource(Integer.valueOf(table.getMovieSource()));
+                    m.setBusinessMethod("IAA");
+                    m.setPlayletId(getQueryParam(m.getAlbumLink(), "playlet_id"));
+                    m.setPlayletPurchasePanelId(getQueryParam(m.getAlbumLink(), "playlet_purchase_panel_id"));
+                });
+                bytedanceProductUpdateMapper.batchInsertDysDistributePlayletInfo(iaaMmovies);
+            }
+
+
+            //iap
+            List<AigcProductAlbum> iapMovies = readMovieFromDoc(productDocProperties.getObjToken(), table.getTableId(), table.getIapViewId(), readToken, null);
+            if (iapMovies != null && !iapMovies.isEmpty()){
+                iapMovies.forEach(m -> {
+                    m.setSource(Integer.valueOf(table.getMovieSource()));
+                    m.setBusinessMethod("IAP");
+                    m.setPlayletId(getQueryParam(m.getAlbumLink(), "playlet_id"));
+                    m.setPlayletPurchasePanelId(getQueryParam(m.getAlbumLink(), "playlet_purchase_panel_id"));
+                });
+                bytedanceProductUpdateMapper.batchInsertDysDistributePlayletInfo(iapMovies);
+            }
+        }
+    }
+
+    private List<AigcProductAlbum> readMovieFromDoc(String appToken,
+                                                    String tableId,
+                                                    String viewId,
+                                                    String accessToken,
+                                                    String pageToken){
+        String url = "https://open.feishu.cn/open-apis/bitable/v1/apps/:app_token/tables/:table_id/records/search";
+        url = url.replace(":app_token",appToken).replace(":table_id",tableId);
+        Map<String, Object> param = new HashMap<>();
+        if (StringUtils.isNotEmpty(viewId)){
+            param.put("view_id",viewId);
+        }
+        if (StringUtils.isNotEmpty(pageToken)){
+            url = url+"?page_token="+pageToken;
+        }
+        Map<String, String> headers = new HashMap<>();
+        headers.put("Authorization", "Bearer "+accessToken);
+        String res = HttpUtils.httpPostRequest(url, param, headers);
+        JSONObject resJson = JSONObject.parseObject(res);
+        if (resJson.getInteger("code")!=0){
+            log.error("调用飞书文档接口失败,res={}",res);
+            return null;
+        }
+        JSONObject data = resJson.getJSONObject("data");
+        JSONArray items = data.getJSONArray("items");
+        if (items==null||items.isEmpty()){
+            return null;
+        }
+
+        List<AigcProductAlbum> movies = new ArrayList<>();
+        for (int i = 0; i < items.size(); i++) {
+            JSONObject record = items.getJSONObject(i);
+            JSONObject fields = record.getJSONObject("fields");
+            AigcProductAlbum album = new AigcProductAlbum();
+            album.setName(getDocText(fields, "剧名"));
+            album.setAlbumLink(getDocText(fields, "推广链"));
+            if (StringUtils.isEmpty(album.getName()) || StringUtils.isEmpty(album.getAlbumLink())){
+                log.error("飞书文档剧目行数据不完整,record={}", fields.toJSONString());
+                continue;
+            }
+            movies.add(album);
+        }
+        boolean hasMore = data.getBooleanValue("has_more");
+        if (hasMore){
+            String nextPageToken = data.getString("page_token");
+            List<AigcProductAlbum> nextMovies = readMovieFromDoc(appToken, tableId, viewId, accessToken, nextPageToken);
+            if (nextMovies!=null&&!nextMovies.isEmpty()){
+                movies.addAll(nextMovies);
+            }
+        }
+        return movies;
+
+    }
+
+    /**
+     * 从飞书文档中读取商品库平台数据
+     * @param appToken 飞书表格appToken
+     * @param tableId 飞书表格ID
+     * @param viewId 飞书表格视图ID,可能没有
+     * @param accessToken 飞书机器人accessToken
+     * @param pageToken 第一次调用填null
+     * @return
+     */
+    private List<AigcProductPlatform> readPlatformFromDoc(String appToken, String tableId, String viewId, String accessToken, String pageToken){
+        String url = "https://open.feishu.cn/open-apis/bitable/v1/apps/:app_token/tables/:table_id/records/search";
+        url = url.replace(":app_token",appToken).replace(":table_id",tableId);
+        Map<String, Object> param = new HashMap<>();
+        if (StringUtils.isNotEmpty(viewId)){
+            param.put("view_id",viewId);
+        }
+        if (StringUtils.isNotEmpty(pageToken)){
+            url = url+"?page_token="+pageToken;
+        }
+        Map<String, String> headers = new HashMap<>();
+        headers.put("Authorization", "Bearer "+accessToken);
+        String res = HttpUtils.httpPostRequest(url, param, headers);
+        JSONObject resJson = JSONObject.parseObject(res);
+        if (resJson.getInteger("code")!=0){
+            log.error("调用飞书文档接口失败,res={}",res);
+            return null;
+        }
+        JSONObject data = resJson.getJSONObject("data");
+        JSONArray items = data.getJSONArray("items");
+        if (items==null||items.isEmpty()){
+            return null;
+        }
+        List<AigcProductPlatform> platforms = new ArrayList<>();
+        for (int i = 0; i < items.size(); i++) {
+            JSONObject item = items.getJSONObject(i);
+            JSONObject fields = item.getJSONObject("fields");
+            AigcProductPlatform platform = new AigcProductPlatform();
+            platform.setPlatformId(getDocText(fields, "商品库ID"));
+            platform.setAdvertiserId(getDocText(fields, "授权的广告主账户"));
+            platform.setType(1);
+            platform.setPlatformType(3);
+            platform.setPlatformName("欢瑞免费");
+            //todo
+
+            platforms.add(platform);
+        }
+
+
+        boolean hasMore = data.getBooleanValue("has_more");
+        if (hasMore){
+            String nextPageToken = data.getString("page_token");
+            List<AigcProductPlatform> nextPlatforms = readPlatformFromDoc(appToken, tableId, viewId, accessToken, nextPageToken);
+            if (nextPlatforms!=null&&!nextPlatforms.isEmpty()){
+                platforms.addAll(nextPlatforms);
+            }
+        }
+
+        return platforms;
+    }
+
+    private String getDocText(JSONObject fields, String columnName){
+        JSONArray jsonArray = fields.getJSONArray(columnName);
+        if (jsonArray==null||jsonArray.isEmpty()){
+            return null;
+        }
+        JSONObject field = jsonArray.getJSONObject(0);
+        return field.getString("text");
+    }
+
+    private String getDocFx(JSONObject fields, String columnName){
+        JSONObject field = fields.getJSONObject(columnName);
+        JSONArray value = field.getJSONArray("value");
+        if (value==null||value.isEmpty()){
+            return null;
+        }
+        JSONObject fieldValue = value.getJSONObject(0);
+        return fieldValue.getString("text");
+    }
+
+    private List<String> getDocSelects(JSONObject fields, String columnName){
+        JSONArray jsonArray = fields.getJSONArray(columnName);
+        if (jsonArray==null||jsonArray.isEmpty()){
+            return null;
+        }
+        return jsonArray.toJavaList(String.class);
+
+    }
+
+    private static String getQueryParam(String url, String paramName){
+        return UriComponentsBuilder.fromUriString(url)
+                .build()
+                .getQueryParams()
+                .getFirst(paramName);
+    }
+
+    private String getReadToken() throws IOException {
+        return FeishuRobotUtil.getTenantToken(FeishuRobotUtil.getFsDocreadAuth());
+    }
+}

+ 10 - 1
job-bytedance/src/main/java/cn/com/ctop/job/bytedance/handler/BytedanceProductUploadJob.java

@@ -1,6 +1,7 @@
 package cn.com.ctop.job.bytedance.handler;
 
 import cn.com.ctop.job.bytedance.data.service.IBytedanceProductUpdateService;
+import cn.com.ctop.job.bytedance.data.service.IFeishuDocSyncService;
 import com.xxl.job.core.handler.annotation.XxlJob;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -11,6 +12,8 @@ import org.springframework.stereotype.Component;
 public class BytedanceProductUploadJob {
     @Autowired
     IBytedanceProductUpdateService bytedanceProductUpdateService;
+    @Autowired
+    IFeishuDocSyncService feishuDocSyncService;
 
     /**
      * 创建和更新推广链
@@ -18,7 +21,13 @@ public class BytedanceProductUploadJob {
      */
     @XxlJob("productUploadJob")
     public void productUploadJob() throws Exception {
-        bytedanceProductUpdateService.run();
+        bytedanceProductUpdateService.runMp();
+    }
+
+    @XxlJob("dysDistributeProductUploadJob")
+    public void dysDistributeProductUploadJob() throws Exception{
+        feishuDocSyncService.syncBytedanceDysDistributeMovies();
+        bytedanceProductUpdateService.runDysDistribute();
     }
 
     /**

+ 39 - 0
job-bytedance/src/main/java/cn/com/ctop/job/bytedance/utils/BytedanceProductDocProperties.java

@@ -0,0 +1,39 @@
+package cn.com.ctop.job.bytedance.utils;
+
+import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+@Lazy
+@Setter
+@Getter
+@Component
+@ConfigurationProperties(prefix = "feishu.doc.bytedance-platform")
+public class BytedanceProductDocProperties {
+
+    private String objToken;
+    private ProductPlatformTable iaa;
+    private ProductPlatformTable iap;
+    private List<DysDistributorLinkTable> dysMovies;
+
+    @Data
+    public static class ProductPlatformTable{
+        private String tableId;
+        private String viewId;
+    }
+
+    @Data
+    public static class DysDistributorLinkTable {
+        private String name;
+        private String tableId;
+        private String iaaViewId;
+        private String iapViewId;
+        private String movieSource;
+        private String copyrightOwner;
+    }
+}

+ 5 - 0
job-bytedance/src/main/java/cn/com/ctop/job/bytedance/utils/FeishuRobotUtil.java

@@ -29,6 +29,8 @@ public class FeishuRobotUtil {
     public static final String fs_adlb2_appkey = "NRclULz12k35lkAktx9FigX7oGh2kCw1";
     public static final String fs_adlb3_appid = "cli_a76e77711cce900e";//广告账号漏绑预警应用
     public static final String fs_adlb3_appkey = "eG6gExoSotb4gRlkB4PuvccgGdfBNTTS";
+    public static final String fs_docread_appid = "cli_a99e83cac117500b";
+    public static final String fs_docread_appkey = "xStPMP3IQSLbYxW3kpfo0epIwr0pSNrB";
 
     //"{\"app_id\":\"cli_a75d0ab0f538100e\",\"app_secret\":\"w7ZNwahiXkmh1xCVSjt8ccJsaleIL3Fc\"}"
     public static String getFsZtAuth() {
@@ -43,6 +45,9 @@ public class FeishuRobotUtil {
     public static String getFsAdlb3Auth() {
         return "{\"app_id\":\"" + fs_adlb3_appid + "\",\"app_secret\":\"" + fs_adlb3_appkey + "\"}";
     }
+    public  static String getFsDocreadAuth() {
+        return "{\"app_id\":\"" + fs_docread_appid + "\",\"app_secret\":\"" + fs_docread_appkey + "\"}";
+    }
 
     public static String getFsZtTenantToken() throws IOException {
         return getTenantToken(getFsZtAuth());

+ 46 - 2
job-bytedance/src/main/resources/application.yml

@@ -83,8 +83,8 @@ spring:
         connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
       datasource:
         master:
-          url: jdbc:mysql://172.16.0.117:3390/media_api?characterEncoding=UTF-8&useUnicode=true&useSSL=false
-          #url: jdbc:mysql://8.147.135.80:3390/media_api?characterEncoding=UTF-8&useUnicode=true&allowMultiQueries=true&useSSL=false
+#          url: jdbc:mysql://172.16.0.117:3390/media_api?characterEncoding=UTF-8&useUnicode=true&useSSL=false
+          url: jdbc:mysql://8.147.135.80:3390/media_api?characterEncoding=UTF-8&useUnicode=true&allowMultiQueries=true&useSSL=false
           username: yanfa
           password: hr@2025..
           driver-class-name: com.mysql.jdbc.Driver
@@ -190,3 +190,47 @@ playlet:
     bucketName: short-theatre-hr
     csjDomain: https://oss.artlinkworld.cn/
     csjFileDir: csj/
+
+# 飞书拉取的表格配置
+feishu:
+  doc:
+    bytedance-platform:
+      objToken: AfLHbNJ9uamORqsvxq8cy63jnPc
+      iaa:
+        tableId: tblsf6RT9HAamt8k
+      iap:
+        tableId: tbl5BPc8DHLIu4Ac
+      dysMovies[0]:
+        name: 番茄端原生推广链
+        tableId: tblPAu0mOPStWGfQ
+        iaaViewId: vewnjgPWGB
+        movieSource: 24
+        copyrightOwner: 抖音视界有限公司
+      dysMovies[1]:
+        name: 日新端原生推广链
+        tableId: tblstFPWYbx172ey
+        iaaViewId: vewnjgPWGB
+        iapViewId: vewbYuvx4z
+        movieSource: 18
+        copyrightOwner: 日新端
+      dysMovies[2]:
+        name: 胜事端原生推广链
+        tableId: tbl0IWs5PsvW3Q4q
+        iaaViewId: vewnjgPWGB
+        iapViewId: vewbYuvx4z
+        movieSource: 10
+        copyrightOwner: 胜事端
+      dysMovies[3]:
+        name: 友和端原生推广链
+        tableId: tblJWHRxZLtVEH6n
+        iaaViewId: vewnjgPWGB
+        iapViewId: vewbYuvx4z
+        movieSource: 22
+        copyrightOwner: 友和端
+      dysMovies[4]:
+        name: 百川端原生推广链
+        tableId: tblqN0gRgLBipgCd
+        iaaViewId: vewnjgPWGB
+        iapViewId: vewbYuvx4z
+        movieSource: 19
+        copyrightOwner: 百川端