Browse Source

Merge remote-tracking branch 'origin/sit' into sit

lyx 1 ngày trước cách đây
mục cha
commit
97d647a4b8

+ 8 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreImg.java

@@ -46,6 +46,14 @@ public class StoreImg extends Model<StoreImg> {
     @TableField("img_url")
     private String imgUrl;
 
+    @ApiModelProperty(value = "是否提取图片颜色:0-未提取,1-已提取")
+    @TableField("is_extract")
+    private Integer isExtract;
+
+    @ApiModelProperty(value = "图片颜色码")
+    @TableField("color_code")
+    private String colorCode;
+
     @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
     @TableField("delete_flag")
     @TableLogic

+ 4 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreInfo.java

@@ -325,4 +325,8 @@ public class StoreInfo {
     @TableField("manual_review_reason")
     private String manualReviewReason;
 
+    @ApiModelProperty(value = "头图审核状态(1-审核通过, 2-审核不通过)")
+    @TableField("head_img_status")
+    private Integer headImgStatus;
+
 }

+ 6 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreMainInfoVo.java

@@ -83,6 +83,9 @@ public class StoreMainInfoVo extends StoreInfo {
     @ApiModelProperty(value = "经营许可证照片")
     private List<StoreImg> foodLicenceList;
 
+    @ApiModelProperty(value = "头图列表(包含is_extract字段)")
+    private List<StoreImg> headerImgList;
+
     @ApiModelProperty(value = "是否连锁转义")
     private String isChainStr;
 
@@ -105,4 +108,7 @@ public class StoreMainInfoVo extends StoreInfo {
 //    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
     private Date foodLicenceExpirationTime;
 
+    @ApiModelProperty(value = "头图审核状态(1—审核通过, 2—审核不通过)")
+    private Integer headImgStatus;
+
 }

+ 1 - 1
alien-gateway/src/main/java/shop/alien/gateway/controller/StoreUserController.java

@@ -58,7 +58,7 @@ public class StoreUserController {
                 return R.fail("验证码过期或未发送");
             }
             if (!cacheCode.trim().equals(code.trim())) {
-                return R.fail("验证码错误");
+                return R.fail("验证码错误,请重新输入");
             }
         }
         StoreUser storeUser = storeUserMapper.selectOne(new LambdaQueryWrapper<StoreUser>()

+ 135 - 0
alien-store/src/main/java/shop/alien/store/controller/StoreImgController.java

@@ -11,6 +11,8 @@ import shop.alien.entity.store.vo.StoreImgInfoVo;
 import shop.alien.entity.store.vo.StoreImgTypeVo;
 import shop.alien.store.service.StoreImgService;
 import shop.alien.store.service.StoreInfoService;
+import shop.alien.store.util.GroupConstant;
+import shop.alien.store.util.ai.AiImageColorExtractUtil;
 
 import java.util.List;
 
@@ -30,6 +32,7 @@ import java.util.List;
 public class StoreImgController {
     private final StoreImgService storeImgService;
     private final StoreInfoService storeInfoService;
+    private final AiImageColorExtractUtil aiImageColorExtractUtil;
 
     @ApiOperation("获取图片")
     @ApiOperationSupport(order = 1)
@@ -74,20 +77,110 @@ public class StoreImgController {
         if (storeImgInfoVo.getStoreId()==null || storeImgInfoVo.getImgType()==null) {
             return R.fail("失败");
         }
+        
+        // 判断是否是头图(20:单图模式, 21:多图模式)
+        Integer imgType = storeImgInfoVo.getImgType();
+        boolean isHeadImage = (imgType == 20 || imgType == 21);
+        
         // 清空storeid,imgType下图片
         int deleteCount = storeImgService.saveOrUpdateImg(storeImgInfoVo.getStoreId(),storeImgInfoVo.getImgType());
         log.info("StoreImgController.updateStoreImgModeInfo?deleteCount={}", deleteCount);
         int result = storeInfoService.updateStoreImgModeInfo(storeImgInfoVo);
         log.info("StoreImgController.updateStoreImgModeInfo?result={}", result);
+        
         if(!CollectionUtils.isEmpty(storeImgList)){
+            // 判断是否为多图模式
+            boolean isMultiMode = imgType != null && imgType.equals(GroupConstant.IMG_TYPE_MULTI_MODE);
+            
+            // 如果是多图模式,需要找到第一张图片(imgSort最小的)
+            StoreImg firstImg = null;
+            if (isMultiMode) {
+                firstImg = storeImgList.stream()
+                        .filter(img -> img != null && img.getImgUrl() != null)
+                        .min((img1, img2) -> {
+                            Integer sort1 = img1.getImgSort() != null ? img1.getImgSort() : Integer.MAX_VALUE;
+                            Integer sort2 = img2.getImgSort() != null ? img2.getImgSort() : Integer.MAX_VALUE;
+                            return sort1.compareTo(sort2);
+                        })
+                        .orElse(null);
+            }
+            
+            // 遍历图片列表,设置图片描述,并根据条件提取颜色
+            for (StoreImg storeImg : storeImgList) {
+                if (storeImg != null && storeImg.getImgUrl() != null) {
+                    // 根据图片类型设置图片描述
+                    String imgDescription = getImgDescriptionByType(imgType);
+                    if (imgDescription != null) {
+                        storeImg.setImgDescription(imgDescription);
+                    }
+                    
+                    // 只有当 is_extract=1 时才提取颜色
+                    Integer isExtract = storeImg.getIsExtract();
+                    if (isExtract != null && isExtract == 1) {
+                        // 如果是多图模式,只对第一张图片提取颜色
+                        if (isMultiMode) {
+                            // 判断是否为第一张图片(通过比较imgSort,如果imgSort相同则认为是同一张图片)
+                            boolean isFirstImg = firstImg != null && 
+                                    firstImg.getImgSort() != null && 
+                                    storeImg.getImgSort() != null && 
+                                    firstImg.getImgSort().equals(storeImg.getImgSort());
+                            
+                            if (isFirstImg) {
+                                log.info("多图模式,检测到需要提取第一张图片颜色,imgUrl: {}, imgSort: {}, isExtract: {}", 
+                                        storeImg.getImgUrl(), storeImg.getImgSort(), isExtract);
+                                extractColorForImg(storeImg);
+                            } else {
+                                log.info("多图模式,跳过非第一张图片的颜色提取,imgUrl: {}, imgSort: {}", 
+                                        storeImg.getImgUrl(), storeImg.getImgSort());
+                                // 非第一张图片,保持 is_extract 为 1,但不提取颜色
+                            }
+                        } else {
+                            // 单图模式,正常提取颜色
+                            log.info("单图模式,检测到需要提取图片颜色,imgUrl: {}, isExtract: {}", storeImg.getImgUrl(), isExtract);
+                            extractColorForImg(storeImg);
+                        }
+                    }
+                }
+            }
             Integer id = storeImgList.get(0).getId();
             if (storeImgService.saveOrUpdateBatch(storeImgList)) {
+                // 如果是头图,设置审核状态为通过(1),前端已完成门头图识别
+                if (isHeadImage) {
+                    // 有头图,设置为审核通过状态(1)
+                    try {
+                        storeInfoService.updateHeadImgStatus(storeImgInfoVo.getStoreId(), 1);
+                        log.info("头图保存成功,设置审核状态为通过,storeId={}", storeImgInfoVo.getStoreId());
+                    } catch (Exception e) {
+                        log.error("更新头图审核状态失败,storeId={}, error={}", 
+                                storeImgInfoVo.getStoreId(), e.getMessage(), e);
+                    }
+                }
                 if (null != id) {
                     return R.success("修改成功");
                 }
                 return R.success("新增成功");
+            } else {
+                // 图片保存失败,如果是头图,设置审核状态为不通过
+                if (isHeadImage) {
+                    try {
+                        storeInfoService.updateHeadImgStatus(storeImgInfoVo.getStoreId(), 2);
+                        log.info("图片保存失败,更新头图审核状态为不通过,storeId={}", storeImgInfoVo.getStoreId());
+                    } catch (Exception e) {
+                        log.error("更新头图审核状态失败,storeId={}, error={}", 
+                                storeImgInfoVo.getStoreId(), e.getMessage(), e);
+                    }
+                }
             }
         } else {
+            // 如果是头图且没有图片,设置审核状态为不通过
+            if (isHeadImage) {
+                try {
+                    storeInfoService.updateHeadImgStatus(storeImgInfoVo.getStoreId(), 2);
+                } catch (Exception e) {
+                    log.error("更新头图审核状态失败,storeId={}, error={}", 
+                            storeImgInfoVo.getStoreId(), e.getMessage(), e);
+                }
+            }
             return R.success("保存成功");
         }
         return R.fail("失败");
@@ -131,4 +224,46 @@ public class StoreImgController {
         log.info("StoreImgController.getByBusinessId?storeId={}&imgType={}&businessId={}", storeId, imgType, businessId);
         return R.data(storeImgService.getByBusinessId(storeId, imgType, businessId));
     }
+
+    /**
+     * 根据图片类型获取图片描述
+     *
+     * @param imgType 图片类型
+     * @return 图片描述
+     */
+    private String getImgDescriptionByType(Integer imgType) {
+        if (imgType == null) {
+            return null;
+        }
+        switch (imgType) {
+            case 20:
+                return "头图单图模式";
+            case 21:
+                return "头图多图模式";
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * 提取图片颜色
+     *
+     * @param storeImg 图片对象
+     */
+    private void extractColorForImg(StoreImg storeImg) {
+        try {
+            String colorCode = aiImageColorExtractUtil.extractCoverColor(storeImg.getImgUrl());
+            if (colorCode != null) {
+                storeImg.setColorCode(colorCode);
+                storeImg.setIsExtract(1); // 提取成功,设置为已提取
+                log.info("成功提取图片颜色码,imgUrl: {}, colorCode: {}", storeImg.getImgUrl(), colorCode);
+            } else {
+                log.warn("提取图片颜色失败,imgUrl: {}", storeImg.getImgUrl());
+                // 提取失败时,保持 is_extract 为 1(表示已尝试提取)
+            }
+        } catch (Exception e) {
+            log.error("提取图片颜色异常,imgUrl: {}", storeImg.getImgUrl(), e);
+            // 异常时,保持 is_extract 为 1(表示已尝试提取)
+        }
+    }
 }

+ 16 - 0
alien-store/src/main/java/shop/alien/store/service/StoreInfoService.java

@@ -499,4 +499,20 @@ public interface StoreInfoService extends IService<StoreInfo> {
                                                   String states,
                                                   String startSubmitDate,
                                                   String endSubmitDate);
+
+    /**
+     * 头图上传后AI审核并更新审核状态
+     *
+     * @param storeImgInfoVo 图片信息VO
+     * @return 审核结果 true-审核通过,false-审核不通过
+     */
+    boolean auditHeadImageAndUpdateStatus(StoreImgInfoVo storeImgInfoVo);
+
+    /**
+     * 更新门店头图审核状态
+     *
+     * @param storeId 门店ID
+     * @param headImgStatus 审核状态 1-审核通过,2-审核不通过
+     */
+    void updateHeadImgStatus(Integer storeId, Integer headImgStatus);
 }

+ 0 - 6
alien-store/src/main/java/shop/alien/store/service/impl/LifeUserViolationServiceImpl.java

@@ -7,7 +7,6 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import com.google.common.collect.Lists;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
@@ -20,7 +19,6 @@ import shop.alien.entity.second.SecondGoodsRecord;
 import shop.alien.entity.second.vo.SecondUserViolationDetailVo;
 import shop.alien.entity.store.*;
 import shop.alien.entity.store.dto.LifeUserViolationDto;
-import shop.alien.entity.store.excelVo.LifeUserOrderExcelVo;
 import shop.alien.entity.store.excelVo.LifeUserViolationExcelVO;
 import shop.alien.entity.store.excelVo.util.ExcelGenerator;
 import shop.alien.entity.store.vo.LifeUserViolationVo;
@@ -34,20 +32,16 @@ import shop.alien.store.util.AiUserViolationUtils;
 import shop.alien.store.util.FunctionMagic;
 import shop.alien.util.ali.AliOSSUtil;
 import shop.alien.util.common.EnumUtil;
-import shop.alien.util.common.JwtUtil;
 
 import java.io.File;
 import java.io.IOException;
 import java.text.SimpleDateFormat;
-import java.time.Instant;
 import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
 
-import static shop.alien.util.common.constant.Constant.*;
-
 
 /**
  * <p>

+ 177 - 5
alien-store/src/main/java/shop/alien/store/service/impl/StoreInfoServiceImpl.java

@@ -244,6 +244,70 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         LambdaQueryWrapper<StoreImg> eq = new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getImgType, 10).eq(StoreImg::getStoreId, id);
         StoreImg storeImg = storeImgMapper.selectOne(eq);
         storeMainInfoVo.setHeadImgUrl(storeImg != null ? storeImg.getImgUrl() : "");
+
+        // 查询头图列表(包含is_extract字段)
+        Integer imgMode = storeInfo.getImgMode();
+        Integer headerImgType = null;
+        if (imgMode != null && imgMode == 1) {
+            // 多图模式
+            headerImgType = GroupConstant.IMG_TYPE_MULTI_MODE;
+        } else {
+            // 单图模式(默认)
+            headerImgType = GroupConstant.IMG_TYPE_SINGLE_MODE;
+        }
+        LambdaQueryWrapper<StoreImg> headerImgQueryWrapper = new LambdaQueryWrapper<>();
+        headerImgQueryWrapper.eq(StoreImg::getImgType, headerImgType);
+        headerImgQueryWrapper.eq(StoreImg::getStoreId, id);
+        headerImgQueryWrapper.orderByAsc(StoreImg::getImgSort);
+        List<StoreImg> headerImgList = storeImgMapper.selectList(headerImgQueryWrapper);
+        storeMainInfoVo.setHeaderImgList(headerImgList);
+
+        //门店头图(20:头图单图模式, 21:头图多图模式)
+        LambdaQueryWrapper<StoreImg> headImgWrapper = new LambdaQueryWrapper<StoreImg>()
+                .in(StoreImg::getImgType, 20, 21)
+                .eq(StoreImg::getStoreId, id)
+                .eq(StoreImg::getDeleteFlag, 0)
+                .orderByAsc(StoreImg::getImgSort);
+        List<StoreImg> headImgList = storeImgMapper.selectList(headImgWrapper);
+        // 设置头图URL(取第一张图片,如果是多图模式可能有多个)
+        if (headImgList != null && !headImgList.isEmpty()) {
+            StoreImg firstHeadImg = headImgList.get(0);
+            storeMainInfoVo.setHeadImgUrl(firstHeadImg.getImgUrl() != null ? firstHeadImg.getImgUrl() : "");
+        } else {
+            storeMainInfoVo.setHeadImgUrl("");
+        }
+        // 设置头图审核状态
+        boolean hasHeadImg = false;
+        if (headImgList != null && !headImgList.isEmpty()) {
+            // 检查头图列表中是否有有效的图片地址
+            for (StoreImg headImg : headImgList) {
+                String imgUrl = headImg.getImgUrl();
+                if (imgUrl != null && !imgUrl.trim().isEmpty()) {
+                    hasHeadImg = true;
+                    log.debug("门店头图数据检查,storeId={}, imgType={}, imgUrl={}, 判断为有头图", 
+                            id, headImg.getImgType(), imgUrl);
+                    break;
+                }
+            }
+        }
+        // 根据是否有头图数据确定审核状态:有数据返回1(审核通过),没有数据返回2(审核不通过)
+        Integer headImgStatus = hasHeadImg ? 1 : 2;
+        // 获取数据库中的head_img_status状态
+        Integer headImgStatusFromDb = storeInfo.getHeadImgStatus();
+        // 记录查询结果日志
+        log.info("门店头图审核状态判断,storeId={}, 查询到头图数量={}, 是否有有效头图={}, 审核状态={}, 数据库原状态={}", 
+                id, headImgList != null ? headImgList.size() : 0, hasHeadImg, headImgStatus, headImgStatusFromDb);
+        // 实时更新数据库:如果数据库中的状态与实际情况不一致,或者为null,则更新数据库
+        if (headImgStatusFromDb == null || !headImgStatus.equals(headImgStatusFromDb)) {
+            try {
+                updateHeadImgStatus(id, headImgStatus);
+                log.info("门店头图审核状态已实时更新,storeId={}, 原状态={}, 新状态={}", id, headImgStatusFromDb, headImgStatus);
+            } catch (Exception e) {
+                log.error("更新门店头图审核状态失败,storeId={}, error={}", id, e.getMessage(), e);
+            }
+        }
+        // 设置返回的审核状态(以实际的头图数据为准)
+        storeMainInfoVo.setHeadImgStatus(headImgStatus);
         List<StoreUser> storeUsers = storeUserMapper.selectList(new LambdaQueryWrapper<StoreUser>().eq(StoreUser::getStoreId, storeInfo.getId()));
         for (StoreUser storeUser : storeUsers) {
             storeMainInfoVo.setLogoutFlagUser(storeUser.getLogoutFlag());
@@ -796,8 +860,14 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         List<String> businessTypeNames = new ArrayList<>();
         //获取经营种类名称
         for (String businessType : businessTypes) {
-            StoreDictionary storeDictionary = storeDictionaryMapper.selectOne(new LambdaQueryWrapper<StoreDictionary>().eq(StoreDictionary::getDictId, businessType).eq(StoreDictionary::getParentId, businessSectionName.getId()));
-            businessTypeNames.add(storeDictionary.getDictDetail());
+            StoreDictionary storeDictionary = storeDictionaryMapper.selectOne(new LambdaQueryWrapper<StoreDictionary>()
+                    .eq(StoreDictionary::getDictId, businessType)
+                    .eq(StoreDictionary::getParentId, businessSectionName.getId())
+                    .eq(StoreDictionary::getTypeName, "business_type")
+                    .eq(StoreDictionary::getDeleteFlag, 0));
+            if (storeDictionary != null) {
+                businessTypeNames.add(storeDictionary.getDictDetail());
+            }
         }
 
         StoreInfoVo result = new StoreInfoVo();
@@ -1251,8 +1321,14 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         List<String> businessTypeNames = new ArrayList<>();
         //获取经营种类名称
         for (String businessType : businessTypes) {
-            StoreDictionary storeDictionary = storeDictionaryMapper.selectOne(new LambdaQueryWrapper<StoreDictionary>().eq(StoreDictionary::getDictId, businessType).eq(StoreDictionary::getParentId, businessSectionName.getId()));
-            businessTypeNames.add(storeDictionary.getDictDetail());
+            StoreDictionary storeDictionary = storeDictionaryMapper.selectOne(new LambdaQueryWrapper<StoreDictionary>()
+                    .eq(StoreDictionary::getDictId, businessType)
+                    .eq(StoreDictionary::getParentId, businessSectionName.getId())
+                    .eq(StoreDictionary::getTypeName, "business_type")
+                    .eq(StoreDictionary::getDeleteFlag, 0));
+            if (storeDictionary != null) {
+                businessTypeNames.add(storeDictionary.getDictDetail());
+            }
         }
 
         StoreInfoVo result = new StoreInfoVo();
@@ -1429,7 +1505,9 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
                 .eq(StoreDictionary::getDictId, parentId)
                 .isNull(StoreDictionary::getParentId));
         List<StoreDictionary> storeDictionaries = storeDictionaryMapper.selectList(new LambdaQueryWrapper<StoreDictionary>()
-                .eq(StoreDictionary::getParentId, businessSection.getId()));
+                .eq(StoreDictionary::getTypeName, "business_type")
+                .eq(StoreDictionary::getParentId, businessSection.getId())
+                .eq(StoreDictionary::getDeleteFlag, 0));
         List<StoreDictionaryVo> voList = new ArrayList<>();
         for (StoreDictionary storeDictionary : storeDictionaries) {
             StoreDictionaryVo vo = new StoreDictionaryVo();
@@ -2541,6 +2619,8 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
                         new LambdaQueryWrapper<StoreDictionary>()
                                 .in(StoreDictionary::getDictId, businessTypes) // 批量匹配id
                                 .eq(StoreDictionary::getParentId, businessSectionDict.getId())
+                                .eq(StoreDictionary::getTypeName, "business_type") // 只查询经营种类,排除擅长标签
+                                .eq(StoreDictionary::getDeleteFlag, 0)
                 );
                 // 转为Map<dictId, StoreDictionary>,方便快速获取
                 Map<String, StoreDictionary> typeDictMap = typeDicts.stream()
@@ -3089,6 +3169,98 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
     }
 
     /**
+     * 头图上传后AI审核并更新审核状态
+     * 流程:上传头图 → 调用AI门头识别接口 → 根据结果更新head_img_status → 保存到store_img表
+     *
+     * @param storeImgInfoVo 图片信息VO
+     * @return 审核结果 true-审核通过,false-审核不通过
+     */
+    @Override
+    public boolean auditHeadImageAndUpdateStatus(StoreImgInfoVo storeImgInfoVo) {
+        // 判断是否是头图(20:单图模式, 21:多图模式)
+        Integer imgType = storeImgInfoVo.getImgType();
+        if (imgType == null || (imgType != GroupConstant.IMG_TYPE_SINGLE_MODE && imgType != GroupConstant.IMG_TYPE_MULTI_MODE)) {
+            // 不是头图,不需要审核,直接返回true
+            return true;
+        }
+
+        List<StoreImg> storeImgList = storeImgInfoVo.getStoreImgList();
+        if (storeImgList == null || storeImgList.isEmpty()) {
+            // 没有图片,设置审核状态为不通过
+            updateHeadImgStatus(storeImgInfoVo.getStoreId(), 2);
+            return false;
+        }
+
+        // 获取第一张头图的URL(头图可能有多张,取第一张进行审核)
+        StoreImg firstHeadImg = storeImgList.get(0);
+        String imageUrl = firstHeadImg.getImgUrl();
+        if (imageUrl == null || imageUrl.trim().isEmpty()) {
+            // 图片URL为空,设置审核状态为不通过
+            updateHeadImgStatus(storeImgInfoVo.getStoreId(), 2);
+            return false;
+        }
+
+        // 获取门店信息(需要门店名称用于AI审核)
+        StoreInfo storeInfo = storeInfoMapper.selectById(storeImgInfoVo.getStoreId());
+        if (storeInfo == null) {
+            log.error("门店不存在,storeId={}", storeImgInfoVo.getStoreId());
+            updateHeadImgStatus(storeImgInfoVo.getStoreId(), 2);
+            return false;
+        }
+
+        String merchantName = storeInfo.getStoreName();
+        if (merchantName == null || merchantName.trim().isEmpty()) {
+            log.error("门店名称为空,storeId={}", storeImgInfoVo.getStoreId());
+            updateHeadImgStatus(storeImgInfoVo.getStoreId(), 2);
+            return false;
+        }
+
+        try {
+            // 调用AI门头识别接口进行审核
+            Map<String, Object> ocrData = getStoreOcrData(imageUrl, merchantName);
+            Boolean overallMatch = (Boolean) ocrData.get("overall_match");
+
+            // 根据AI审核结果更新head_img_status
+            // overall_match = true -> 审核通过 -> head_img_status = 1(保持通过状态)
+            // overall_match = false -> 审核不通过 -> head_img_status = 2(改为不通过)
+            if (overallMatch != null && overallMatch) {
+                // AI审核通过,保持审核通过状态(1)
+                updateHeadImgStatus(storeImgInfoVo.getStoreId(), 1);
+                log.info("头图AI审核通过,storeId={}, imageUrl={}, headImgStatus=1",
+                        storeImgInfoVo.getStoreId(), imageUrl);
+                return true;
+            } else {
+                // AI审核不通过,更新为不通过状态(2)
+                updateHeadImgStatus(storeImgInfoVo.getStoreId(), 2);
+                log.info("头图AI审核不通过,storeId={}, imageUrl={}, overallMatch={}, headImgStatus=2",
+                        storeImgInfoVo.getStoreId(), imageUrl, overallMatch);
+                return false;
+            }
+        } catch (Exception e) {
+            log.error("头图AI审核失败,storeId={}, imageUrl={}, error={}",
+                    storeImgInfoVo.getStoreId(), imageUrl, e.getMessage(), e);
+            // AI审核失败,设置审核状态为不通过
+            updateHeadImgStatus(storeImgInfoVo.getStoreId(), 2);
+            return false;
+        }
+    }
+
+    /**
+     * 更新门店头图审核状态
+     *
+     * @param storeId 门店ID
+     * @param headImgStatus 审核状态 1-审核通过,2-审核不通过
+     */
+    @Override
+    public void updateHeadImgStatus(Integer storeId, Integer headImgStatus) {
+        LambdaUpdateWrapper<StoreInfo> wrapper = new LambdaUpdateWrapper<>();
+        wrapper.eq(StoreInfo::getId, storeId);
+        wrapper.set(StoreInfo::getHeadImgStatus, headImgStatus);
+        storeInfoMapper.update(null, wrapper);
+        log.info("更新门店头图审核状态,storeId={}, headImgStatus={}", storeId, headImgStatus);
+    }
+
+    /**
      * 获取活动banner图
      * 关联查询 store_img 和 store_operational_activity 表
      * 关联条件:store_operational_activity.id = store_img.business_id

+ 108 - 0
alien-store/src/main/java/shop/alien/store/util/ai/AiImageColorExtractUtil.java

@@ -0,0 +1,108 @@
+package shop.alien.store.util.ai;
+
+import com.alibaba.fastjson2.JSONObject;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.*;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.HttpServerErrorException;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * AI 图片封面颜色提取工具类
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class AiImageColorExtractUtil {
+
+    private final RestTemplate restTemplate;
+    private final AiAuthTokenUtil aiAuthTokenUtil;
+
+    @Value("${ai.service.image-color-extract-url}")
+    private String extractColorUrl;
+
+    /**
+     * 提取图片封面颜色
+     *
+     * @param imageUrl 图片URL
+     * @return 颜色码,如果提取失败返回null
+     */
+    public String extractCoverColor(String imageUrl) {
+        if (!StringUtils.hasText(imageUrl)) {
+            log.warn("图片URL为空,无法提取颜色");
+            return null;
+        }
+
+        // 获取AI服务访问令牌
+        String accessToken = aiAuthTokenUtil.getAccessToken();
+        if (!StringUtils.hasText(accessToken)) {
+            log.error("获取AI服务访问令牌失败,无法调用颜色提取接口");
+            return null;
+        }
+
+        try {
+            // 构建请求体
+            Map<String, Object> requestBody = new HashMap<>();
+            requestBody.put("image_url", imageUrl);
+
+            // 构建请求头,添加Authorization
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.APPLICATION_JSON);
+            headers.set("Authorization", "Bearer " + accessToken);
+
+            HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, headers);
+
+            log.info("调用AI颜色提取接口,图片URL: {}", imageUrl);
+            ResponseEntity<String> response = restTemplate.postForEntity(extractColorUrl, request, String.class);
+
+            if (response != null && response.getStatusCode() == HttpStatus.OK) {
+                String responseBody = response.getBody();
+                log.info("AI颜色提取接口响应: {}", responseBody);
+
+                if (StringUtils.hasText(responseBody)) {
+                    JSONObject jsonObject = JSONObject.parseObject(responseBody);
+                    if (jsonObject != null) {
+                        Integer code = jsonObject.getInteger("code");
+                        if (code != null && code == 200) {
+                            JSONObject data = jsonObject.getJSONObject("data");
+                            if (data != null) {
+                                String colorCode = data.getString("color_code");
+                                log.info("成功提取图片颜色码: {}", colorCode);
+                                return colorCode;
+                            } else {
+                                log.warn("AI接口返回数据为空");
+                            }
+                        } else {
+                            String message = jsonObject.getString("message");
+                            log.warn("AI接口返回错误,code: {}, message: {}", code, message);
+                        }
+                    }
+                }
+            } else {
+                log.error("AI颜色提取接口调用失败,HTTP状态码: {}", 
+                        response != null ? response.getStatusCode() : "null");
+            }
+        } catch (HttpClientErrorException e) {
+            log.error("调用AI颜色提取接口失败,HTTP客户端错误,状态码: {}, 响应体: {}, 图片URL: {}", 
+                    e.getStatusCode(), e.getResponseBodyAsString(), imageUrl, e);
+        } catch (HttpServerErrorException e) {
+            log.error("调用AI颜色提取接口失败,HTTP服务器错误,状态码: {}, 响应体: {}, 图片URL: {}", 
+                    e.getStatusCode(), e.getResponseBodyAsString(), imageUrl, e);
+        } catch (Exception e) {
+            log.error("调用AI颜色提取接口异常,图片URL: {}", imageUrl, e);
+        }
+
+        return null;
+    }
+}
+