Kaynağa Gözat

运营管理-新增AI审核

liyafei 1 gün önce
ebeveyn
işleme
586aa081f6

+ 1 - 1
alien-entity/src/main/java/shop/alien/entity/storePlatform/StoreOperationalActivity.java

@@ -67,7 +67,7 @@ public class StoreOperationalActivity {
     @TableField("coupon_quantity")
     private Integer couponQuantity;
 
-    @ApiModelProperty(value = "状态:1-待审核, 2-未开始, 3-审核拒绝, 4-已售罄, 5-进行中, 6-已下架, 7-已结束")
+    @ApiModelProperty(value = "状态:1-待审核, 2-未开始, 3-审核拒绝, 4-已售罄, 5-进行中, 6-已下架, 7-已结束, 8-审核成功")
     @TableField("status")
     private Integer status;
 

+ 2 - 0
alien-entity/src/main/java/shop/alien/entity/storePlatform/vo/StoreOperationalActivityDTO.java

@@ -80,5 +80,7 @@ public class StoreOperationalActivityDTO {
     @ApiModelProperty(value = "用户输入的AI描述")
     private JsonNode imgDescribe;
 
+    @ApiModelProperty(value = "AI审核的输入参数")
+    private JsonNode auditParam;
 }
 

+ 13 - 3
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/OperationalActivityController.java

@@ -12,6 +12,9 @@ import shop.alien.entity.storePlatform.vo.StoreOperationalActivityVO;
 import shop.alien.storeplatform.service.OperationalActivityService;
 
 import java.util.List;
+import java.util.Optional;
+
+import static shop.alien.storeplatform.service.impl.OperationalActivityServiceImpl.failureReasonHolder;
 
 /**
  * 运营活动管理控制器
@@ -37,12 +40,19 @@ public class OperationalActivityController {
         log.info("OperationalActivityController.createActivity: dto={}", dto);
         try {
             log.warn("用户输入的图片描述{}",dto.getImgDescribe());
-
             int result = activityService.createActivity(dto);
-            if (result > 0) {
+            if (1 == result) {
                 return R.success("活动创建成功");
+            } else if (2 == result) {
+                try {
+                    return R.fail("活动创建失败,原因:审核未通过"+
+                            Optional.ofNullable(failureReasonHolder.get()));
+                }finally {
+                    failureReasonHolder.remove();
+                }
+            }else {
+                return R.fail("活动创建失败");
             }
-            return R.fail("活动创建失败");
         } catch (Exception e) {
             log.error("OperationalActivityController.createActivity ERROR: {}", e.getMessage(), e);
             return R.fail(e.getMessage());

+ 24 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/feign/AlienAIFeign.java

@@ -32,4 +32,28 @@ public interface AlienAIFeign {
                  consumes = MediaType.APPLICATION_JSON_VALUE,
                  produces = MediaType.APPLICATION_JSON_VALUE)
     JsonNode generatePromotionImage(@RequestHeader("Authorization") String authorization, @RequestBody JsonNode requestBody);
+
+    /**
+     * 使用 JsonNode 灵活调用接口 - 发起AI审核(多模态)
+     *
+     * @param authorization Bearer token,格式为 "Bearer {access_token}"
+     * @param requestBody JsonNode 请求体,可以灵活构建
+     * @return JsonNode 响应体,可以灵活解析
+     */
+    @PostMapping(value = "/ai/auto-review/api/v1/multimodal_audit_task/submit",
+            consumes = MediaType.APPLICATION_JSON_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE)
+    JsonNode multiModelAudit(@RequestHeader("Authorization") String authorization, @RequestBody JsonNode requestBody);
+
+
+    /**
+     * 获取 AI审核(多模态) 结果
+     *
+     * @param authorization Bearer token,格式为 "Bearer {access_token}"
+     * @param taskId 任务ID
+     * @return JsonNode 响应体,包含审核结果
+     */
+    @GetMapping(value = "/ai/auto-review/api/v1/multimodal_audit_task/getResult",
+            produces = MediaType.APPLICATION_JSON_VALUE)
+    JsonNode getMultiModelAuditResult(@RequestHeader("Authorization") String authorization, @RequestParam("task_id") String taskId);
 }

+ 114 - 60
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivityServiceImpl.java

@@ -1,6 +1,5 @@
 package shop.alien.storeplatform.service.impl;
 
-import com.alibaba.excel.util.StringUtils;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -10,15 +9,15 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.redisson.api.RBucket;
+import org.redisson.api.RedissonClient;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.util.LinkedMultiValueMap;
 import org.springframework.util.MultiValueMap;
 import shop.alien.entity.store.LifeDiscountCoupon;
-import shop.alien.entity.store.LifeUser;
 import shop.alien.entity.store.StoreImg;
-import shop.alien.entity.store.StoreUser;
 import shop.alien.entity.storePlatform.StoreOperationalActivity;
 import shop.alien.entity.storePlatform.vo.StoreOperationalActivityDTO;
 import shop.alien.entity.storePlatform.vo.StoreOperationalActivityVO;
@@ -33,6 +32,7 @@ import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 
 /**
@@ -60,12 +60,32 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
 
     private final AlienAIFeign alienAIFeign;
 
+    private final RedissonClient redissonClient;
+
     @Value("${ai.aiAccount}")
     private String aiAccount;
 
     @Value("${ai.aiPassword}")
     private String aiPassword;
 
+    @Value("${ai.token-timeout:3}")
+    private Integer aiTokenTimeout;
+
+    // AI平台token Redis key
+    private static final String AI_TOKEN_KEY = "ai:platform:token";
+
+    ObjectMapper objectMapper = new ObjectMapper();
+
+    public static final ThreadLocal<String> failureReasonHolder = new ThreadLocal<>();
+
+    // AI参数模板
+    String tpl = "活动名称:%s\n"
+            + "活动时间:%s - %s,格式化时间为yyyy-mm-dd类型\n"
+            + "用户可参与次数:%s\n"
+            + "活动规则:%s\n"
+            + "优惠券发放数量:%s\n"
+            + "图片描述:%s";
+
     @Override
     public int createActivity(StoreOperationalActivityDTO dto) {
         log.info("OperationalActivityServiceImpl.createActivity: dto={}", dto);
@@ -80,38 +100,44 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
         if (activity.getStatus() == null) {
             activity.setStatus(1);
         }
-        Integer result = activityMapper.insert(activity);
-        if (result > 0) {
-            // 使用用户描述让AI生成图片。
-            if (dto.getUploadImgType()==2) {
-                try {
-                    // 先调用登录接口获取access_token
-                    MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
-                    formData.add("username", aiAccount);
-                    formData.add("password", aiPassword);
-                    JsonNode loginResponse = alienAIFeign.login(formData);
-
-                    String accessToken = null;
-                    if (loginResponse != null && loginResponse.has("data")) {
-                        JsonNode data = loginResponse.get("data");
-                        if (data.has("access_token")) {
-                            accessToken = data.get("access_token").asText();
+        Integer result =0;
+            try {
+                String accessToken = getToken();
+                if (accessToken == null || accessToken.isEmpty()) {
+                    log.error("获取AI服务access_token失败,无法生成促销图片");
+                } else {
+                    // AI登录成功
+                    // 先调用AI进行运营名称和图片的审核,同步调用
+                    String authorization = "Bearer " + accessToken;
+                    JsonNode audioResponse = alienAIFeign.multiModelAudit(authorization, dto.getAuditParam());
+                    // 如果审核失败,不进行文字生成海报图片,提前短路。
+                    if (audioResponse.has("data")) {
+                        String taskId = audioResponse.get("data").get("task_id").asText();
+                        JsonNode audioResResponse = alienAIFeign.getMultiModelAuditResult(authorization, taskId);
+                        String status=audioResResponse.get("data").get("status").asText();
+                        for (int i = 0; i < 60; i++) { // AI审核接口速度还在优化中 todo
+                            if (status.equals("completed")||status.equals("failed")) {
+                                break;
+                            }
+                            audioResResponse = alienAIFeign.getMultiModelAuditResult(authorization, taskId);
+                            status = audioResResponse.get("data").get("status").asText();
+                            Thread.sleep(1000);
+                        }
+                        String auditRes = audioResResponse.get("data").get("audit_result").asText();
+                        if (!status.equals("failed")&&auditRes != null && auditRes.equals("compliant")) {
+                            activity.setStatus(8);
+                            result = activityMapper.insert(activity);
+                        } else {
+                            String failureReason = audioResResponse.get("data").get("failure_reason").asText();
+                            failureReasonHolder.set(failureReason);
+                            // 审核不成功 不生成任何记录,返回原因
+                            return 2;
                         }
                     }
-                    
-                    if (accessToken == null || accessToken.isEmpty()) {
-                        log.error("获取AI服务access_token失败,无法生成促销图片");
-                    } else {
-                        ObjectMapper objectMapper = new ObjectMapper();
+                    // 使用用户描述和页面输入框其他信息,让AI生成海报图片。
+                    if (dto.getUploadImgType() == 2) {
+                        // 格式化输入AI参数
                         ObjectNode requestBody = objectMapper.createObjectNode();
-
-                        String tpl = "活动名称:%s\n"
-                                + "活动时间:%s - %s\n"
-                                + "用户可参与次数:%s\n"
-                                + "活动规则:%s\n"
-                                + "优惠券发放数量:%s\n"
-                                + "图片描述:%s";
-
                         String filled = String.format(
                                 tpl,
                                 dto.getActivityName(),
@@ -123,12 +149,10 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                                 dto.getImgDescribe()
                         );
                         requestBody.put("text", filled);
-                        // 调用接口,传递Bearer token
-                        String authorization = "Bearer " + accessToken;
-                        JsonNode response = alienAIFeign.generatePromotionImage(authorization, requestBody);
+                        JsonNode imgResponse = alienAIFeign.generatePromotionImage(authorization, requestBody);
                         // 解析响应
-                        if (response.has("data")) {
-                            JsonNode data = response.get("data");
+                        if (imgResponse.has("data")) {
+                            JsonNode data = imgResponse.get("data");
                             // 提取横向图(banner_image)的图片URL
                             if (data.has("banner_image")) {
                                 JsonNode bannerImage = data.get("banner_image");
@@ -147,9 +171,10 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                             }
                         }
                     }
-                } catch (Exception e) {
-                    log.error("调用AI服务生成促销图片失败", e);
                 }
+            } catch (Exception e) {
+                // AI调用失败,也可以添加数据
+                log.error("调用AI服务生成促销图片失败", e);
             }
             dto.getActivityTitleImg().setBusinessId(activity.getId());
             dto.getActivityTitleImg().setImgType(26);
@@ -158,19 +183,49 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
             dto.getActivityDetailImg().setBusinessId(activity.getId());
             dto.getActivityDetailImg().setImgType(27);
             imgMapper.insert(dto.getActivityDetailImg());
-        }
-        
         return result;
     }
 
+    /**
+     * 登录AI平台,获取token
+     */
+    private String getToken() {
+        // 1. 先从缓存获取
+        RBucket<String> tokenBucket = redissonClient.getBucket(AI_TOKEN_KEY);
+        String cachedToken = tokenBucket.get();
+        if (cachedToken != null && !cachedToken.isEmpty()) {
+            log.debug("从缓存获取 AI token");
+            return cachedToken;
+        }
+        // 2. 缓存未命中,调用登录接口
+        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
+        formData.add("username", aiAccount);
+        formData.add("password", aiPassword);
+        JsonNode loginResponse = alienAIFeign.login(formData);
+        String accessToken = null;
+        if (loginResponse != null && loginResponse.has("data")) {
+            JsonNode data = loginResponse.get("data");
+            if (data.has("access_token")) {
+                accessToken = data.get("access_token").asText();
+            }
+        }
+        // 3. 如果获取成功,存入缓存
+        if (accessToken != null && !accessToken.isEmpty()) {
+            tokenBucket.set(accessToken, aiTokenTimeout, TimeUnit.SECONDS);
+        } else {
+            log.error("获取 AI token 失败");
+        }
+        return accessToken;
+    }
+
     @Override
     public int updateActivity(StoreOperationalActivityDTO dto) {
         log.info("OperationalActivityServiceImpl.updateActivity: dto={}", dto);
-        
+
         if (dto.getId() == null) {
             throw new IllegalArgumentException("活动ID不能为空");
         }
-        
+
         StoreOperationalActivity activity = new StoreOperationalActivity();
         BeanUtils.copyProperties(dto, activity);
         Integer result = activityMapper.updateById(activity);
@@ -185,7 +240,6 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
             imgMapper.update(null, wrapper);
 
 
-
             // 插入新图片
             StoreImg activityTitleImg = new StoreImg();
             activityTitleImg.setStoreId(dto.getStoreId());
@@ -210,11 +264,11 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
     @Override
     public int deleteActivity(Integer id) {
         log.info("OperationalActivityServiceImpl.deleteActivity: id={}", id);
-        
+
         if (id == null) {
             throw new IllegalArgumentException("活动ID不能为空");
         }
-        
+
         // 逻辑删除
         return activityMapper.deleteById(id);
     }
@@ -222,11 +276,11 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
     @Override
     public StoreOperationalActivityVO queryActivityById(Integer id) {
         log.info("OperationalActivityServiceImpl.getActivityById: id={}", id);
-        
+
         if (id == null) {
             throw new IllegalArgumentException("活动ID不能为空");
         }
-        
+
         StoreOperationalActivity activity = activityMapper.selectById(id);
 
         if (activity == null) {
@@ -252,7 +306,7 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
         } else if (activity.getStatus() == 7) {
             vo.setStatusName("已结束");
         }
-        
+
         // 设置优惠券名称(判空处理)
         if (activity.getCouponId() != null) {
             LifeDiscountCoupon coupon = lifeDiscountCouponMapper.selectById(activity.getCouponId());
@@ -286,19 +340,19 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
 
     @Override
     public IPage<StoreOperationalActivityVO> queryActivityList(Integer storeId, Integer status, String activityName, Integer pageNum, Integer pageSize) {
-        log.info("OperationalActivityServiceImpl.queryActivityList: storeId={}, status={}, activityName={}, pageNum={}, pageSize={}", 
+        log.info("OperationalActivityServiceImpl.queryActivityList: storeId={}, status={}, activityName={}, pageNum={}, pageSize={}",
                 storeId, status, activityName, pageNum, pageSize);
-        
+
         LambdaQueryWrapper<StoreOperationalActivity> wrapper = new LambdaQueryWrapper<>();
         wrapper.eq(storeId != null, StoreOperationalActivity::getStoreId, storeId);
         wrapper.like(activityName != null && activityName != "", StoreOperationalActivity::getActivityName, activityName);
         wrapper.eq(status != null, StoreOperationalActivity::getStatus, status);
 
         IPage<StoreOperationalActivity> list = activityMapper.selectPage(new Page<>(pageNum, pageSize), wrapper);
-        
+
         // 将list复制到vo
         List<StoreOperationalActivityVO> voRecords = new ArrayList<>();
-        
+
         for (StoreOperationalActivity activity : list.getRecords()) {
             // 创建实体类
             StoreOperationalActivityVO vo = new StoreOperationalActivityVO();
@@ -324,11 +378,11 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
 
             voRecords.add(vo);
         }
-        
+
         // 创建分页结果对象
         Page<StoreOperationalActivityVO> voList = new Page<>(list.getCurrent(), list.getSize(), list.getTotal());
         voList.setRecords(voRecords);
-        
+
         return voList;
     }
 
@@ -338,7 +392,7 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
 
         Date now = new Date();
         System.out.println("当前时间: " + sdf.format(now));
-        
+
         // 获取当天零点零分零秒时间
         Calendar todayStart = Calendar.getInstance();
         todayStart.setTime(now);
@@ -348,7 +402,7 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
         todayStart.set(Calendar.MILLISECOND, 0);
         Date todayZero = todayStart.getTime();
         System.out.println("当天零点时间: " + sdf.format(todayZero));
-        
+
         // now + 1天(零点)
         Calendar calendarPlus = Calendar.getInstance();
         calendarPlus.setTime(now);
@@ -365,15 +419,15 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
     @Override
     public int updateActivityStatus(Integer id, Integer status) {
         log.info("OperationalActivityServiceImpl.updateActivityStatus: id={}, status={}", id, status);
-        
+
         if (id == null) {
             throw new IllegalArgumentException("活动ID不能为空");
         }
-        
+
         StoreOperationalActivity activity = new StoreOperationalActivity();
         activity.setId(id);
         activity.setStatus(status);
-        
+
         return activityMapper.updateById(activity);
     }
 }