Procházet zdrojové kódy

feat(activity): 新增AI生成运营活动海报图片功能

- 新增 generatePromotionImages 接口用于AI生成活动海报图片
- 将AI图片生成功能从创建
fcw před 3 týdny
rodič
revize
e0457c8b60

+ 19 - 5
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/OperationalActivityController.java

@@ -32,8 +32,22 @@ public class OperationalActivityController {
 
     private final OperationalActivityService activityService;
 
-    @ApiOperation(value = "创建运营活动", notes = "支持配置优惠券和代金券作为奖励,可同时配置或二选一")
+    @ApiOperation(value = "AI生成运营活动海报图片", notes = "仅调用 AI 生成横版/竖版图片 URL,不入库。uploadImgType 须为 2;入参与创建活动一致(活动名称、时间、规则、imgDescribe 等)。生成后请将返回的 activityTitleImg、activityDetailImg 中的 imgUrl 随创建接口一并提交。")
     @ApiOperationSupport(order = 1)
+    @PostMapping("/generatePromotionImages")
+    public R<StoreOperationalActivityDTO> generatePromotionImages(@RequestBody StoreOperationalActivityDTO dto) {
+        log.info("OperationalActivityController.generatePromotionImages: dto={}", dto);
+        try {
+            StoreOperationalActivityDTO result = activityService.generateActivityPromotionImages(dto);
+            return R.data(result, "图片生成成功");
+        } catch (Exception e) {
+            log.error("OperationalActivityController.generatePromotionImages ERROR: {}", e.getMessage(), e);
+            return R.fail(e.getMessage());
+        }
+    }
+
+    @ApiOperation(value = "创建运营活动(提交保存)", notes = "内容审核后入库;AI 生图请先发 /generatePromotionImages,再将返回的图片 URL 填入 dto 后调用本接口。支持配置优惠券和代金券作为奖励。")
+    @ApiOperationSupport(order = 2)
     @PostMapping("/create")
     public R<String> createActivity(@RequestBody StoreOperationalActivityDTO dto) {
         log.info("OperationalActivityController.createActivity: dto={}", dto);
@@ -59,7 +73,7 @@ public class OperationalActivityController {
     }
 
     @ApiOperation(value = "更新运营活动", notes = "支持更新优惠券和代金券配置")
-    @ApiOperationSupport(order = 2)
+    @ApiOperationSupport(order = 3)
     @PostMapping("/update")
     public R<OperationalActivityUpdateResultVo> updateActivity(@RequestBody StoreOperationalActivityDTO dto) {
         log.info("OperationalActivityController.updateActivity: dto={}", dto);
@@ -78,7 +92,7 @@ public class OperationalActivityController {
     }
 
     @ApiOperation("删除运营活动")
-    @ApiOperationSupport(order = 3)
+    @ApiOperationSupport(order = 4)
     @ApiImplicitParams({
             @ApiImplicitParam(name = "id", value = "活动ID", dataType = "Integer", paramType = "query", required = true)
     })
@@ -98,7 +112,7 @@ public class OperationalActivityController {
     }
 
     @ApiOperation(value = "根据ID获取活动详情", notes = "返回活动详细信息,包括优惠券和代金券的名称、ID、数量等信息")
-    @ApiOperationSupport(order = 4)
+    @ApiOperationSupport(order = 5)
     @ApiImplicitParams({
             @ApiImplicitParam(name = "id", value = "活动ID", dataType = "Integer", paramType = "query", required = true)
     })
@@ -115,7 +129,7 @@ public class OperationalActivityController {
     }
 
     @ApiOperation(value = "根据商户ID获取活动列表", notes = "分页查询活动列表,返回活动基本信息、优惠券和代金券信息、活动图片等")
-    @ApiOperationSupport(order = 5)
+    @ApiOperationSupport(order = 6)
     @ApiImplicitParams({
             @ApiImplicitParam(name = "storeId", value = "商户ID", dataType = "Integer", paramType = "query", required = true)
     })

+ 10 - 1
alien-store-platform/src/main/java/shop/alien/storeplatform/service/OperationalActivityService.java

@@ -15,7 +15,16 @@ import java.util.List;
 public interface OperationalActivityService {
 
     /**
-     * 创建运营活动
+     * AI 生成运营活动海报图片(仅调 AI,不入库)。
+     * 要求 uploadImgType=2,返回的 dto 中 activityTitleImg、activityDetailImg 的 imgUrl 会被填充。
+     *
+     * @param dto 活动文案与图片描述等(与创建时一致,用于拼 prompt)
+     * @return 回填图片 URL 后的 dto
+     */
+    StoreOperationalActivityDTO generateActivityPromotionImages(StoreOperationalActivityDTO dto);
+
+    /**
+     * 创建运营活动(提交保存,不再在本方法内调用 AI 生成图片,请事先调用 generateActivityPromotionImages 或本地上传)。
      *
      * @param dto 活动信息
      * @return 创建结果

+ 59 - 61
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivityServiceImpl.java

@@ -115,6 +115,58 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
             + "图片描述:%s";
 
     @Override
+    public StoreOperationalActivityDTO generateActivityPromotionImages(StoreOperationalActivityDTO dto) {
+        log.info("OperationalActivityServiceImpl.generateActivityPromotionImages: dto={}", dto);
+        if (dto.getUploadImgType() == null || dto.getUploadImgType() != 2) {
+            throw new IllegalArgumentException("AI 生成图片需 uploadImgType=2");
+        }
+        if (dto.getActivityTitleImg() == null) {
+            dto.setActivityTitleImg(new StoreImg());
+        }
+        if (dto.getActivityDetailImg() == null) {
+            dto.setActivityDetailImg(new StoreImg());
+        }
+        String accessToken = getToken();
+        if (accessToken == null || accessToken.isEmpty()) {
+            throw new IllegalStateException("获取AI服务access_token失败,无法生成促销图片");
+        }
+        String authorization = "Bearer " + accessToken;
+        String filled = String.format(
+                tpl,
+                dto.getActivityName(),
+                dto.getStartTime(),
+                dto.getEndTime(),
+                dto.getParticipationLimit(),
+                dto.getActivityRule(),
+                dto.getCouponQuantity() != null ? dto.getCouponQuantity() : 0,
+                dto.getVoucherQuantity() != null ? dto.getVoucherQuantity() : 0,
+                dto.getImgDescribe()
+        );
+        JsonNode imgResponse = alienAIFeign.generatePromotionImage(authorization, Collections.singletonMap("text", filled));
+        applyPromotionImageResponse(imgResponse, dto);
+        return dto;
+    }
+
+    private void applyPromotionImageResponse(JsonNode imgResponse, StoreOperationalActivityDTO dto) {
+        if (imgResponse == null || !imgResponse.has("data")) {
+            return;
+        }
+        JsonNode data = imgResponse.get("data");
+        if (data.has("banner_image")) {
+            JsonNode bannerImage = data.get("banner_image");
+            if (bannerImage.has("ali_url") && !bannerImage.get("ali_url").isNull()) {
+                dto.getActivityTitleImg().setImgUrl(bannerImage.get("ali_url").asText());
+            }
+        }
+        if (data.has("vertical_image")) {
+            JsonNode verticalImage = data.get("vertical_image");
+            if (verticalImage.has("ali_url") && !verticalImage.get("ali_url").isNull()) {
+                dto.getActivityDetailImg().setImgUrl(verticalImage.get("ali_url").asText());
+            }
+        }
+    }
+
+    @Override
     public int createActivity(StoreOperationalActivityDTO dto) {
         log.info("OperationalActivityServiceImpl.createActivity: dto={}", dto);
 
@@ -203,44 +255,6 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                             log.error("发送活动审核通知失败,activityId={}, error={}", activity.getId(), e.getMessage(), e);
                         }
                     }
-                    // 使用用户描述和页面输入框其他信息,让AI生成海报图片。
-                    if (dto.getUploadImgType() == 2) {
-                        // 格式化输入AI参数
-                        ObjectNode requestBody = objectMapper.createObjectNode();
-                        String filled = String.format(
-                                tpl,
-                                dto.getActivityName(),
-                                dto.getStartTime(),
-                                dto.getEndTime(),
-                                dto.getParticipationLimit(),
-                                dto.getActivityRule(),
-                                dto.getCouponQuantity() != null ? dto.getCouponQuantity() : 0,
-                                dto.getVoucherQuantity() != null ? dto.getVoucherQuantity() : 0,
-                                dto.getImgDescribe()
-                        );
-                        requestBody.put("text", filled);
-                        JsonNode imgResponse = alienAIFeign.generatePromotionImage(authorization, Collections.singletonMap("text", filled));
-                        // 解析响应
-                        if (imgResponse.has("data")) {
-                            JsonNode data = imgResponse.get("data");
-                            // 提取横向图(banner_image)的图片URL
-                            if (data.has("banner_image")) {
-                                JsonNode bannerImage = data.get("banner_image");
-                                if (bannerImage.has("ali_url") && !bannerImage.get("ali_url").isNull()) {
-                                    String bannerImageUrl = bannerImage.get("ali_url").asText();
-                                    dto.getActivityTitleImg().setImgUrl(bannerImageUrl);
-                                }
-                            }
-                            // 提取竖向图(vertical_image)的图片URL
-                            if (data.has("vertical_image")) {
-                                JsonNode verticalImage = data.get("vertical_image");
-                                if (verticalImage.has("ali_url") && !verticalImage.get("ali_url").isNull()) {
-                                    String verticalImageUrl = verticalImage.get("ali_url").asText();
-                                    dto.getActivityDetailImg().setImgUrl(verticalImageUrl);
-                                }
-                            }
-                        }
-                    }
                 }
             } catch (Exception e) {
                 // AI调用失败,也可以添加数据
@@ -367,8 +381,12 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                 sendActivityAuditNotice(activity, auditResult.isPassed(), auditResult.getFailureReason());
                 // 使用用户描述和页面输入框其他信息,让AI生成海报图片。
                 if (dto.getUploadImgType() == 2) {
-                    // 格式化输入AI参数
-                    ObjectNode requestBody = objectMapper.createObjectNode();
+                    if (dto.getActivityTitleImg() == null) {
+                        dto.setActivityTitleImg(new StoreImg());
+                    }
+                    if (dto.getActivityDetailImg() == null) {
+                        dto.setActivityDetailImg(new StoreImg());
+                    }
                     String filled = String.format(
                             tpl,
                             dto.getActivityName(),
@@ -380,28 +398,8 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                             dto.getVoucherQuantity() != null ? dto.getVoucherQuantity() : 0,
                             dto.getImgDescribe()
                     );
-                    requestBody.put("text", filled);
                     JsonNode imgResponse = alienAIFeign.generatePromotionImage(authorization, Collections.singletonMap("text", filled));
-                    // 解析响应
-                    if (imgResponse.has("data")) {
-                        JsonNode data = imgResponse.get("data");
-                        // 提取横向图(banner_image)的图片URL
-                        if (data.has("banner_image")) {
-                            JsonNode bannerImage = data.get("banner_image");
-                            if (bannerImage.has("ali_url") && !bannerImage.get("ali_url").isNull()) {
-                                String bannerImageUrl = bannerImage.get("ali_url").asText();
-                                dto.getActivityTitleImg().setImgUrl(bannerImageUrl);
-                            }
-                        }
-                        // 提取竖向图(vertical_image)的图片URL
-                        if (data.has("vertical_image")) {
-                            JsonNode verticalImage = data.get("vertical_image");
-                            if (verticalImage.has("ali_url") && !verticalImage.get("ali_url").isNull()) {
-                                String verticalImageUrl = verticalImage.get("ali_url").asText();
-                                dto.getActivityDetailImg().setImgUrl(verticalImageUrl);
-                            }
-                        }
-                    }
+                    applyPromotionImageResponse(imgResponse, dto);
                 }
             }
         } catch (Exception e) {