Просмотр исходного кода

Merge branch 'sit' into store-PlatFormSys

# Conflicts:
#	alien-util/src/main/resources/META-INF/spring.factories
zc 3 месяцев назад
Родитель
Сommit
7e993a7cde
28 измененных файлов с 749 добавлено и 160 удалено
  1. 1 1
      alien-api/src/main/resources/logback-spring.xml
  2. 0 1
      alien-entity/pom.xml
  3. 1 1
      alien-entity/src/main/java/shop/alien/entity/store/StoreInfoDraft.java
  4. 0 2
      alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackQueryDto.java
  5. 4 0
      alien-entity/src/main/java/shop/alien/entity/store/vo/CommonRatingVo.java
  6. 1 1
      alien-entity/src/main/java/shop/alien/entity/storePlatform/StoreLicenseHistory.java
  7. 1 1
      alien-entity/src/main/java/shop/alien/mapper/CommonRatingMapper.java
  8. 6 0
      alien-gateway/pom.xml
  9. 1 1
      alien-job/src/main/resources/logback-spring.xml
  10. 2 2
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/AiUserAuditTaskServiceImpl.java
  11. 1 1
      alien-store/src/main/java/shop/alien/store/AlienStoreApplication.java
  12. 2 2
      alien-store/src/main/java/shop/alien/store/aspect/AiAuditAspect.java
  13. 4 2
      alien-store/src/main/java/shop/alien/store/controller/AiAuditController.java
  14. 158 50
      alien-store/src/main/java/shop/alien/store/controller/AiSearchController.java
  15. 21 3
      alien-store/src/main/java/shop/alien/store/controller/CommonRatingController.java
  16. 2 2
      alien-store/src/main/java/shop/alien/store/controller/LifeFeedbackController.java
  17. 1 1
      alien-store/src/main/java/shop/alien/store/controller/StoreInfoController.java
  18. 44 10
      alien-store/src/main/java/shop/alien/store/controller/StorePriceController.java
  19. 61 11
      alien-store/src/main/java/shop/alien/store/service/impl/CommonRatingServiceImpl.java
  20. 263 0
      alien-store/src/main/java/shop/alien/store/service/impl/LicenseAuditAsyncService.java
  21. 35 60
      alien-store/src/main/java/shop/alien/store/service/impl/StoreInfoServiceImpl.java
  22. 25 2
      alien-util/pom.xml
  23. 57 0
      alien-util/src/main/java/shop/alien/util/encryption/JasyptAutoConfiguration.java
  24. 33 0
      alien-util/src/main/java/shop/alien/util/encryption/JasyptEncryptorUtil.java
  25. 2 2
      alien-util/src/main/java/shop/alien/util/encryption/advice/DecryptRequestBodyAdvice.java
  26. 2 2
      alien-util/src/main/java/shop/alien/util/encryption/advice/EncryptResponseBodyAdvice.java
  27. 1 2
      alien-util/src/main/java/shop/alien/util/encryption/properties/EncryptProperties.java
  28. 20 0
      pom.xml

+ 1 - 1
alien-api/src/main/resources/logback-spring.xml

@@ -12,7 +12,7 @@
     <!-- 定义全局参数常量 -->
     <property name="log.level" value="debug"/>
     <property name="log.maxHistory" value="30"/><!-- 30表示30个 -->
-    <springProperty scope="context" name="logging.path" source="logging.path"/>
+    <springProperty scope="context" name="logging.path" source="logging.path" defaultValue="C:/project/ext/log"/>
     <!--输出文件前缀-->
     <property name="FILENAME" value="xiaokuihua_api"/>
 

+ 0 - 1
alien-entity/pom.xml

@@ -28,7 +28,6 @@
         <dependency>
             <groupId>javax.servlet</groupId>
             <artifactId>javax.servlet-api</artifactId>
-            <version>3.1.0</version>
         </dependency>
 
         <dependency>

+ 1 - 1
alien-entity/src/main/java/shop/alien/entity/store/StoreInfoDraft.java

@@ -204,7 +204,7 @@ public class StoreInfoDraft extends Model<StoreInfoDraft> {
     @TableField("other_licenses")
     private String otherLicenses;
 
-    @ApiModelProperty(value = "其他资质证明图片地址列表(临时字段,用于接收前端数据)")
+    @ApiModelProperty(value = "其他资质证明图片地址列表")
     @TableField(exist = false)
     private List<String> otherQualificationImages;
 

+ 0 - 2
alien-entity/src/main/java/shop/alien/entity/store/dto/LifeFeedbackQueryDto.java

@@ -28,10 +28,8 @@ public class LifeFeedbackQueryDto implements Serializable {
 
     @ApiModelProperty(value = "页码", required = true)
     private Integer pageNum = 1;
-    private Integer page = 1;
 
     @ApiModelProperty(value = "每页数量", required = true)
     private Integer pageSize = 10;
-    private Integer size = 10;
 }
 

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

@@ -14,6 +14,8 @@ public class CommonRatingVo extends CommonRating {
     private String userImage;
     @ApiModelProperty(name = "userName", value = "用户名")
     private String userName;
+    @ApiModelProperty(name = "phone", value = "用户手机号")
+    private String phone;
     @ApiModelProperty(name = "isLike", value = "是否点赞")
     private Integer isLike;
     @ApiModelProperty(name = "storeName", value = "门店名称")
@@ -34,4 +36,6 @@ public class CommonRatingVo extends CommonRating {
     private Integer isCollect;
     @ApiModelProperty(name = "commentCount", value = "评论数量")
     private Long commentCount;
+    @ApiModelProperty(name = "isFollow", value = "是否关注")
+    private Integer isFollow;
 }

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

@@ -30,7 +30,7 @@ public class StoreLicenseHistory extends Model<StoreLicenseHistory> {
     @TableField("store_id")
     private Integer storeId;
 
-    @ApiModelProperty(value = "证照类型: 1-合同管理, 2-食品经营许可证")
+    @ApiModelProperty(value = "证照类型: 1-营业执照, 2-其他资质证明")
     @TableField("license_status")
     private Integer licenseStatus;
 

+ 1 - 1
alien-entity/src/main/java/shop/alien/mapper/CommonRatingMapper.java

@@ -46,7 +46,7 @@ public interface CommonRatingMapper extends BaseMapper<CommonRating> {
 //            "    -- 1. 总评论数(当前门店、根评论、未删除)\n" +
 //            "    COUNT(1) AS totalCount,\n" +
             "    -- 2. 有图评论数(image_urls 不为 null 且不为空字符串)\n" +
-            "    SUM(CASE WHEN image_urls IS NOT NULL AND image_urls != '' THEN 1 ELSE 0 END) AS imageCount,\n" +
+            "    SUM(CASE WHEN image_urls IS NOT NULL AND image_urls != '' AND TRIM(image_urls) <> '' THEN 1 ELSE 0 END) AS imageCount,\n" +
             "    -- 3. 好评数(score >= 4.5)\n" +
             "    SUM(CASE WHEN score >= 4.5 THEN 1 ELSE 0 END) AS goodCount,\n" +
             "    -- 4. 中评数(score >= 3.0 AND score <= 4.0)\n" +

+ 6 - 0
alien-gateway/pom.xml

@@ -174,6 +174,12 @@
             <groupId>shop.alien</groupId>
             <artifactId>alien-util</artifactId>
             <version>1.0.0</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-starter-web</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
 
         <dependency>

+ 1 - 1
alien-job/src/main/resources/logback-spring.xml

@@ -12,7 +12,7 @@
     <!-- 定义全局参数常量 -->
     <property name="log.level" value="debug"/>
     <property name="log.maxHistory" value="30"/><!-- 30表示30个 -->
-    <springProperty scope="context" name="logging.path" source="logging.path"/>
+    <springProperty scope="context" name="logging.path" source="logging.path" defaultValue="C:/project/ext/log"/>
     <!--输出文件前缀-->
     <property name="FILENAME" value="alien"/>
 

+ 2 - 2
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/AiUserAuditTaskServiceImpl.java

@@ -53,10 +53,10 @@ public class AiUserAuditTaskServiceImpl implements AiUserAuditTaskService {
 
     private final LawyerUserViolationService lawyerUserViolationService;
 
-    @Value("${third-party-ai-auto-review.user-complaint-url:http://192.168.2.250:9100/ai/auto-review/api/v1/lawyer_user_complaint_audit_task/submit}")
+    @Value("${third-party-ai-auto-review.user-complaint-url:http://124.93.18.180:9100/ai/auto-review/api/v1/lawyer_user_complaint_audit_task/submit}")
     private String aiUserAuditTaskUrl;
 
-    @Value("${third-party-ai-auto-review.lawyer-complaint-url:http://192.168.2.250:9100/ai/auto-review/api/v1/lawyer_complaint_audit_task/submit}")
+    @Value("${third-party-ai-auto-review.lawyer-complaint-url:http://124.93.18.180:9100/ai/auto-review/api/v1/lawyer_complaint_audit_task/submit}")
     private String aiAutoReviewUrl;
 
     private final LawyerConsultationOrderMapper consultationOrderMapper;

+ 1 - 1
alien-store/src/main/java/shop/alien/store/AlienStoreApplication.java

@@ -8,7 +8,7 @@ import org.springframework.cloud.openfeign.EnableFeignClients;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.scheduling.annotation.EnableScheduling;
 
-@ComponentScan({"shop.alien.store.*","shop.alien.util.*","shop.alien.config.http","shop.alien.config.properties","shop.alien.config.advice"})
+@ComponentScan({"shop.alien.store.*","shop.alien.util.*","shop.alien.config.http","shop.alien.config.properties"})
 @EnableSwaggerBootstrapUI
 @MapperScan({"shop.alien.mapper"})
 @SpringBootApplication

+ 2 - 2
alien-store/src/main/java/shop/alien/store/aspect/AiAuditAspect.java

@@ -89,10 +89,10 @@ public class AiAuditAspect {
     }
 
     /**
-     * 将审核结果写入入参的status字段(通过=1,不通过=0
+     将审核结果写入入参的status字段(通过=1,不通过=2
      */
     private void applyStatus(Object[] args, boolean passed) {
-        int status = passed ? 1 : 0;
+        int status = passed ? 1 : 2;
         for (Object arg : args) {
             if (arg == null) {
                 continue;

+ 4 - 2
alien-store/src/main/java/shop/alien/store/controller/AiAuditController.java

@@ -8,6 +8,7 @@ import io.swagger.annotations.ApiSort;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
 import org.springframework.http.HttpEntity;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.MediaType;
@@ -32,6 +33,7 @@ import java.util.Map;
 @RestController
 @RequestMapping("/aiAudit")
 @RequiredArgsConstructor
+@RefreshScope
 public class AiAuditController {
 
     private final RestTemplate restTemplate;
@@ -48,10 +50,10 @@ public class AiAuditController {
     @Value("${third-party-login.base-url:http://192.168.2.250:9100/ai/user-auth-core/api/v1/auth/login}")
     private String loginUrl;
 
-    @Value("${third-party-text-check.base-url:http://192.168.2.250:9100/ai/auto-review/api/v1/trade_relevance/check}")
+    @Value("${third-party-text-check.base-url:http://124.93.18.180:8892/api/v1/moderate}")
     private String aiTextCheckUrl;
 
-    @Value("${third-party-content_compliance-check.base-url:http://192.168.2.250:9100/ai/auto-review/api/v1/content_compliance/check}")
+    @Value("${third-party-content_compliance-check.base-url:http://124.93.18.180:8892/api/v1/moderate}")
     private String aiContentCheckUrl;
 
     @ApiOperation("ai文本审核")

+ 158 - 50
alien-store/src/main/java/shop/alien/store/controller/AiSearchController.java

@@ -19,9 +19,12 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.client.RestTemplate;
 import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreImg;
 import shop.alien.entity.store.StoreUser;
 import shop.alien.entity.store.vo.StoreInfoVo;
+import shop.alien.mapper.StoreImgMapper;
 import shop.alien.mapper.StoreUserMapper;
+import shop.alien.store.service.CommonRatingService;
 import shop.alien.store.service.StoreImgService;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 
@@ -42,12 +45,17 @@ import java.util.Comparator;
 @RefreshScope
 public class AiSearchController {
 
-    private final StoreUserMapper storeUserMapper;
-    @Value("${third-party-ai-search.base-url:http://124.93.18.180:7870/api/v1/search}")
-    private String aiSearchUrl;
+    private final StoreImgMapper storeImgMapper;
+    
+    @Value("${third-party-ai-search.exact.base-url:http://124.93.18.180:7870/api/v1/search}")
+    private String aiSearchExactUrl;
+    
+    @Value("${third-party-ai-search.fuzzy.base-url:http://124.93.18.180:7869/api/v1/search}")
+    private String aiSearchFuzzyUrl;
 
     private final RestTemplate restTemplate;
     private final StoreImgService storeImgService;
+    private final CommonRatingService commonRatingService;
 
     @RequestMapping("/search")
     public R search(@RequestBody Map<String,String> map) {
@@ -69,57 +77,28 @@ public class AiSearchController {
 
         HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, null);
         try {
-            ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity(aiSearchUrl, request, String.class);
+            
+            ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity(aiSearchExactUrl, request, String.class);
             String body = stringResponseEntity.getBody();
             JSONObject jsonObject = JSONObject.parseObject(body);
             JSONObject jsonObject1 = new JSONObject();
-//            if ("生活服务".equals(requestBody.get("category")) || "休闲娱乐".equals(requestBody.get("category"))) {
-                // 生活服务类别:转换为StoreInfoVo,确保返回的字段名按照StoreInfoVo定义
-                List<StoreInfoVo> result = convertToStoreInfoList(jsonObject.getJSONArray("results"));
-                
-                // 从结果中提取所有的id,放到集合中
-                List<Integer> storeIdList = result.stream()
-                        .map(StoreInfoVo::getId)
-                        .filter(id -> id != null)
-                        .collect(Collectors.toList());
-                
-                // 使用storeImgService批量查询,imgType为10
-                List<StoreUser> storeImgList = new ArrayList<>();
-                if (!storeIdList.isEmpty()) {
-                    QueryWrapper<StoreUser> queryWrapper = new QueryWrapper<>();
-                    queryWrapper.in("store_id", storeIdList);
-                    storeImgList = storeUserMapper.selectList(queryWrapper);
-                }
+            // 生活服务类别:转换为StoreInfoVo,确保返回的字段名按照StoreInfoVo定义
+            // 模糊搜索:从related_results和matched_results字段获取数据
+            List<StoreInfoVo> relatedResult = convertToStoreInfoList(jsonObject.getJSONArray("related_results"));
+            List<StoreInfoVo> matchedResult = convertToStoreInfoList(jsonObject.getJSONArray("matched_results"));
+
+            // 查找图片并设置到result中(图片类型1-入口图)
+            fillStoreImages(relatedResult, 1);
+            fillStoreImages(matchedResult, 1);
+
+            // 合并两个列表
+//            List<StoreInfoVo> relatedResults = new ArrayList<>();
+//            List<StoreInfoVo> matchedResults = new ArrayList<>();
+//            matchedResults.addAll(matchedResult);
+//            relatedResults.addAll(relatedResult);
+            jsonObject1.put("matchedRecords", matchedResult);
+            jsonObject1.put("relatedRecords", relatedResult);
 
-                // 将storeImgList按照storeId分组,并关联到对应的result中
-                Map<Integer, List<StoreUser>> storeImgMap = storeImgList.stream()
-                        .collect(Collectors.groupingBy(StoreUser::getStoreId));
-                
-                // 将图片列表设置到对应的StoreInfoVo中
-                for (StoreInfoVo storeInfo : result) {
-                    if (storeInfo.getId() != null) {
-                        List<StoreUser> imgs = storeImgMap.get(storeInfo.getId());
-                        if (imgs != null && !imgs.isEmpty()) {
-                            // 将图片列表转换为URL列表,按照imgSort排序
-                            List<String> imgUrlList = imgs.stream()
-                                    .sorted(Comparator.comparing(StoreUser::getStoreId))
-                                    .map(StoreUser::getHeadImg)
-                                    .filter(url -> url != null)
-                                    .collect(Collectors.toList());
-                            storeInfo.setStoreAlbumUrlList(imgUrlList);
-                            // 如果第一个图片存在,也可以设置到imgUrl字段作为头像
-                            if (!imgUrlList.isEmpty()) {
-                                storeInfo.setImgUrl(imgUrlList.get(0));
-                            }
-                        }
-                    }
-                }
-                
-                jsonObject1.put("records", result);
-//            } else {
-//                // 其他类别:直接返回原始结果
-//                jsonObject1.put("records", jsonObject.get("results"));
-//            }
 
             jsonObject1.put("total", jsonObject.get("total"));
             jsonObject1.put("size", map.get("pageSize"));
@@ -131,6 +110,47 @@ public class AiSearchController {
         return  R.fail("请求失败");
     }
 
+    @RequestMapping("/fuzzySearch")
+    public R fuzzySearch(@RequestBody Map<String,String> map) {
+        // 初始化请求体Map
+        Map<String, Object> requestBody = new HashMap<>();
+        requestBody.put("query", map.get("storeName"));
+        requestBody.put("limit", map.get("pageSize"));
+        requestBody.put("user_lat", map.get("lat"));
+        requestBody.put("user_lng", map.get("lon"));
+        requestBody.put("category", map.get("category"));
+        requestBody.put("page", map.get("pageNum"));
+        requestBody.put("sort_by", map.get("sortBy"));
+        HttpHeaders aiHeaders = new HttpHeaders();
+        aiHeaders.setContentType(MediaType.APPLICATION_JSON);
+
+        HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, null);
+        try {
+            ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity(aiSearchFuzzyUrl, request, String.class);
+            String body = stringResponseEntity.getBody();
+            JSONObject jsonObject = JSONObject.parseObject(body);
+            JSONObject jsonObject1 = new JSONObject();
+            // 模糊搜索:从related_results和matched_results字段获取数据
+            List<StoreInfoVo> result = convertToStoreInfoList(jsonObject.getJSONArray("results"));
+
+            // 查找图片并设置到result中(图片类型1-入口图)
+            fillStoreImages(result, 1);
+            
+            // 填充评论总数
+            fillRatingCount(result);
+
+            jsonObject1.put("records", result);
+
+            jsonObject1.put("total", jsonObject.get("total"));
+            jsonObject1.put("size", map.get("pageSize"));
+            log.info("调用AI模糊搜索接口 接口返回------{}", body);
+            return R.data(jsonObject1);
+        } catch (Exception e) {
+            log.error("调用AI模糊搜索接口 接口异常------", e);
+        }
+        return  R.fail("请求失败");
+    }
+
     private List<StoreInfoVo> convertToStoreInfoList(JSONArray results) {
         List<StoreInfoVo> storeInfoList = new ArrayList<>();
         if (results != null) {
@@ -162,6 +182,94 @@ public class AiSearchController {
     }
 
     /**
+     * 查找店铺图片并设置到StoreInfoVo列表中
+     *
+     * @param result StoreInfoVo列表
+     * @param imgType 图片类型(1:入口图, 2:相册, 10:商家头像等)
+     */
+    private void fillStoreImages(List<StoreInfoVo> result, Integer imgType) {
+        if (result == null || result.isEmpty()) {
+            return;
+        }
+        
+        // 从结果中提取所有的id,放到集合中
+        List<Integer> storeIdList = result.stream()
+                .map(StoreInfoVo::getId)
+                .filter(id -> id != null)
+                .distinct()
+                .collect(Collectors.toList());
+        
+        if (storeIdList.isEmpty()) {
+            return;
+        }
+        
+        // 批量查询图片
+        QueryWrapper<StoreImg> queryWrapper = new QueryWrapper<>();
+        queryWrapper.in("store_id", storeIdList)
+                .eq("img_type", imgType)
+                .orderByAsc("img_sort");
+        List<StoreImg> storeImgList = storeImgMapper.selectList(queryWrapper);
+        
+        // 将图片列表按照storeId分组
+        Map<Integer, List<StoreImg>> storeImgMap = storeImgList.stream()
+                .collect(Collectors.groupingBy(StoreImg::getStoreId));
+        
+        // 将图片列表设置到对应的StoreInfoVo中
+        for (StoreInfoVo storeInfo : result) {
+            if (storeInfo.getId() != null) {
+                List<StoreImg> imgs = storeImgMap.get(storeInfo.getId());
+                if (imgs != null && !imgs.isEmpty()) {
+                    // 将图片列表转换为URL列表,按照imgSort排序
+                    List<String> imgUrlList = imgs.stream()
+                            .sorted(Comparator.comparing(StoreImg::getImgSort, Comparator.nullsLast(Comparator.naturalOrder())))
+                            .map(StoreImg::getImgUrl)
+                            .filter(url -> url != null)
+                            .collect(Collectors.toList());
+                    storeInfo.setStoreAlbumUrlList(imgUrlList);
+                    // 如果第一个图片存在,也可以设置到imgUrl字段作为头像
+                    if (!imgUrlList.isEmpty()) {
+                        storeInfo.setImgUrl(imgUrlList.get(0));
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 填充评论总数到StoreInfoVo列表中
+     *
+     * @param result StoreInfoVo列表
+     */
+    private void fillRatingCount(List<StoreInfoVo> result) {
+        if (result == null || result.isEmpty()) {
+            return;
+        }
+        
+        for (StoreInfoVo storeInfo : result) {
+            if (storeInfo.getId() != null) {
+                try {
+                    // 调用评论服务获取评论总数,businessId传id,businessType传1
+                    Object ratingCountObj = commonRatingService.getRatingCount(storeInfo.getId(), 1);
+                    
+                    // 将返回的Object转换为Map
+                    if (ratingCountObj instanceof Map) {
+                        Map<String, Object> ratingCountMap = (Map<String, Object>) ratingCountObj;
+                        // 从map中取出totalCount字段
+                        Object totalCount = ratingCountMap.get("totalCount");
+                        if (totalCount != null) {
+                            // 赋值给totalNum字段(转为String类型)
+                            storeInfo.setTotalNum(String.valueOf(totalCount));
+                        }
+                    }
+                } catch (Exception e) {
+                    log.warn("获取评论总数失败,storeId: {}", storeInfo.getId(), e);
+                    // 如果获取失败,继续处理下一个,不影响其他数据
+                }
+            }
+        }
+    }
+
+    /**
      * 将下划线命名转换为驼峰命名
      * 例如: store_tel -> storeTel, created_time -> createdTime
      */

+ 21 - 3
alien-store/src/main/java/shop/alien/store/controller/CommonRatingController.java

@@ -86,12 +86,30 @@ public class CommonRatingController {
     public R getCommentDetail(@RequestParam Integer ratingId, @RequestParam Long userId) {
         log.info("CommonRatingController.getRatingDetail?id={}&userId={}", ratingId, userId);
         if (ratingId == null) {
-            throw new IllegalArgumentException("参数[ratingId]不能为空");
+            return R.fail("参数[ratingId]不能为空");
         }
         if (userId == null) {
-            throw new IllegalArgumentException("参数[userId]不能为空");
+            return R.fail("参数[userId]不能为空");
         }
-        return R.data(commonRatingService.getRatingDetail(ratingId, userId));
+        try {
+            return R.data(commonRatingService.getRatingDetail(ratingId, userId));
+        } catch (IllegalArgumentException e) {
+            return R.fail(e.getMessage());
+        }
+    }
+
+    /**删除评价
+     * @param ratingId 评价id
+     * @return 0:成功, 1:失败
+     */
+    @ApiOperation(value = "删除评价", notes = "0:成功, 1:失败")
+    @GetMapping("/deleteRating")
+    public R deleteRating(@RequestParam Long ratingId) {
+        boolean b = commonRatingService.removeById(ratingId);
+        if (b) {
+            return R.success("删除评价成功");
+        }
+        return R.fail("删除评价失败");
     }
 
 //

+ 2 - 2
alien-store/src/main/java/shop/alien/store/controller/LifeFeedbackController.java

@@ -78,8 +78,8 @@ public class LifeFeedbackController {
             @ApiImplicitParam(name = "handleStatus", value = "处理状态:0-处理中,1-已解决", dataType = "Integer", paramType = "query"),
             @ApiImplicitParam(name = "feedbackSource", value = "反馈来源:0-用户端,1-商家端", dataType = "Integer", paramType = "query"),
             @ApiImplicitParam(name = "feedbackWay", value = "反馈方式:0-用户反馈,1-AI识别", dataType = "Integer", paramType = "query"),
-            @ApiImplicitParam(name = "page", value = "页码", dataType = "Integer", paramType = "query", required = true),
-            @ApiImplicitParam(name = "size", value = "每页数量", dataType = "Integer", paramType = "query", required = true)
+            @ApiImplicitParam(name = "pageNum", value = "页码", dataType = "Integer", paramType = "query", required = true),
+            @ApiImplicitParam(name = "pageSize", value = "每页数量", dataType = "Integer", paramType = "query", required = true)
     })
     @GetMapping("/platform/list")
     public R<IPage<LifeFeedbackListVo>> getWebFeedbackList(LifeFeedbackQueryDto queryDto) {

+ 1 - 1
alien-store/src/main/java/shop/alien/store/controller/StoreInfoController.java

@@ -781,7 +781,7 @@ public class StoreInfoController {
         return R.data(storeInfoService.getOtherQualificationStatus(id));
     }
 
-    @ApiOperation(value = "更换其他资质证明(上传新图片、记录历史、调用AI审核,最多9张)")
+    @ApiOperation(value = "更换其他资质证明")
     @PostMapping("/uploadOtherQualification")
     public R<String> uploadOtherQualification(@RequestBody List<StoreImg> storeImgList) {
         log.info("StoreInfoController.uploadOtherQualification?storeImgList={}", storeImgList);

+ 44 - 10
alien-store/src/main/java/shop/alien/store/controller/StorePriceController.java

@@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.StoreInfo;
@@ -14,8 +15,11 @@ import shop.alien.mapper.StoreInfoMapper;
 import shop.alien.store.service.StorePriceService;
 import shop.alien.util.encryption.Decrypt;
 import shop.alien.util.encryption.Encrypt;
+import shop.alien.util.encryption.JasyptEncryptorUtil;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 通用价目表
@@ -36,6 +40,45 @@ public class StorePriceController {
 
     private final StoreInfoMapper storeInfoMapper;
 
+    @Value("${test1}")
+    private String dbPassword;
+
+    @ApiOperation("测试读取配置与手动加解密")
+    @GetMapping("/testConfig")
+    public R<Map<String, Object>> testConfig(@RequestParam String salt, @RequestParam String text) {
+        Map<String, Object> result = new HashMap<>();
+        
+        // 1. 测试自动解密(Jasypt 注入的结果)
+        result.put("configPassword", dbPassword);
+
+        // 2. 调用 utils 里的封装进行手动加解密逻辑
+        try {
+            String encrypted = JasyptEncryptorUtil.encode(salt, text);
+            String decrypted = JasyptEncryptorUtil.decode(salt, encrypted);
+
+            result.put("inputSalt", salt);
+            result.put("inputText", text);
+            result.put("manualEncrypted", encrypted);
+            result.put("manualDecrypted", decrypted);
+        } catch (Exception e) {
+            return R.fail("加解密调试失败: " + e.getMessage());
+        }
+
+        return R.data(result, "调试成功");
+    }
+
+    @ApiOperation("加解密测试接口")
+    @ApiOperationSupport(order = 10)
+    @PostMapping("/testEncryption")
+    @Decrypt
+    @Encrypt
+    public R<StorePrice> testEncryption(@RequestBody StorePrice storePrice) {
+        log.info("加解密测试接口接收数据: {}", storePrice);
+        // 原样返回,测试响应加密
+        return R.data(storePrice, "加解密测试成功");
+    }
+
+
     @ApiOperation("新增通用价目")
     @ApiOperationSupport(order = 1)
     @PostMapping("/save")
@@ -243,15 +286,6 @@ public class StorePriceController {
         return R.fail("操作失败");
     }
 
-    @ApiOperation("加解密测试接口")
-    @ApiOperationSupport(order = 10)
-    @PostMapping("/testEncryption")
-    @Decrypt
-    @Encrypt
-    public R<StorePrice> testEncryption(@RequestBody StorePrice storePrice) {
-        log.info("加解密测试接口接收数据: {}", storePrice);
-        // 原样返回,测试响应加密
-        return R.data(storePrice, "加解密测试成功");
-    }
+
 }
 

+ 61 - 11
alien-store/src/main/java/shop/alien/store/service/impl/CommonRatingServiceImpl.java

@@ -65,6 +65,7 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
     private final CommonCommentMapper commonCommentMapper;
     private final LifeLikeRecordMapper lifeLikeRecordMapper;
     private final LifeCollectMapper lifeCollectMapper;
+    private final LifeFansMapper lifeFansMapper;
 
 
     public static final List<String> SERVICES_LIST = ImmutableList.of(
@@ -172,14 +173,19 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
                 wrapper.ge(CommonRating::getScore, 4.5);
             }else if(searchScore == 2){
                 // 2-中评
-                wrapper.ge(CommonRating::getScore, 3.5);
-                wrapper.lt(CommonRating::getScore, 4.5);
+                wrapper.ge(CommonRating::getScore, 3.0);
+                wrapper.le(CommonRating::getScore, 4.0);
             }else if(searchScore == 3){
                 // 3-差评
-                wrapper.lt(CommonRating::getScore, 3.5);
+                wrapper.ge(CommonRating::getScore, 0.5);
+                wrapper.le(CommonRating::getScore, 2.5);
             }else if(searchScore == 4){
                 // 4-有图
                 wrapper.isNotNull(CommonRating::getImageUrls);
+                // 2. 排除 空字符串 ""
+                wrapper.ne(CommonRating::getImageUrls, "");
+                // 3. 排除 纯空格字符串(如"   ")—— 用 TRIM 函数去掉首尾空格后判断非空
+                wrapper.apply("TRIM({0}) <> ''", "image_urls");
             }
         }
         
@@ -198,8 +204,13 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
         wrapper.eq(CommonRating::getIsShow, 1);
         List<CommonRating> commonRatings = commonRatingMapper.selectList(wrapper);
         List<Long> collect = commonRatings.stream().map(x -> x.getId()).collect(Collectors.toList());
+        // 如果为空直接返回
+        Map<String, Object> ratingCount = new HashMap<>();
+        if(commonRatings.isEmpty()){
+            return ratingCount;
+        }
         // 获取评价统计信息(总评论数、有图评论数、好评数、中评数、差评数)
-        Map<String, Object> ratingCount = commonRatingMapper.getRatingCount(new QueryWrapper<CommonRating>().in("id", collect));
+        ratingCount = commonRatingMapper.getRatingCount(new QueryWrapper<CommonRating>().in("id", collect));
         if(RatingBusinessTypeEnum.STORE_RATING.getBusinessType() == businessType){
             // 1店铺评分
             StoreInfo storeInfo = storeInfoMapper.selectById(businessId);
@@ -225,7 +236,7 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
             List<Long> collect2 = commonRatings.stream().filter(i -> i.getScore() >= 4.5).map(CommonRating::getUserId).distinct().limit(6).collect(Collectors.toList());
             if(!collect2.isEmpty()) {
                 List<LifeUser> lifeUsers = lifeUserMapper.selectList(new QueryWrapper<LifeUser>().lambda().in(LifeUser::getId, collect2));
-                ratingCount.put("img", lifeUsers.stream().map(LifeUser::getUserImage).collect(Collectors.toList()));
+                ratingCount.put("img", lifeUsers.stream().filter(x -> x.getUserImage() != null).map(LifeUser::getUserImage).collect(Collectors.toList()));
             } else {
                 ratingCount.put("img", new ArrayList<>());
             }
@@ -243,11 +254,40 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
         }
         CommonRatingVo commonRatingVo = new CommonRatingVo();
         BeanUtils.copyProperties(commonRating, commonRatingVo);
-        LifeUser lifeUser = lifeUserMapper.selectById(Integer.parseInt(commonRating.getUserId().toString()));
-        // 设置评论用户信息
-        commonRatingVo.setUserImage(lifeUser.getUserImage());
-        commonRatingVo.setUserName(lifeUser.getUserName());
+        LifeUser ratingUser = lifeUserMapper.selectById(Integer.parseInt(commonRating.getUserId().toString()));
+        if (ratingUser == null){
+            throw new IllegalArgumentException("评价用户不存在");
+        }
+        // 设置评价用户信息
+        commonRatingVo.setUserImage(ratingUser.getUserImage());
+        commonRatingVo.setUserName(ratingUser.getUserName());
+        commonRatingVo.setPhone(ratingUser.getUserPhone());
         // 查询当前用户点赞列表(仅评价)
+        setIsLike(ratingId, userId, commonRatingVo);
+        LifeUser loginUser = lifeUserMapper.selectById(userId);
+        if (loginUser == null){
+            throw new IllegalArgumentException("登录用户不存在");
+        }
+        // 查询当前用户关注列表
+        List<LifeFans> lifeFans = lifeFansMapper.selectList(new QueryWrapper<LifeFans>().lambda()
+                .eq(LifeFans::getFollowedId, "user_".concat(ratingUser.getUserPhone()))
+                .eq(LifeFans::getFansId, "user_".concat(loginUser.getUserPhone()))
+                .eq(LifeFans::getFansType, "1")
+                .eq(LifeFans::getDeleteFlag, 0));
+        // 设置是否关注
+        commonRatingVo.setIsFollow(lifeFans.size() > 0 ? 1 : 0);
+        // 根据业务类型处理
+        extractedWithBusinessType(ratingId, userId, commonRatingVo);
+        return commonRatingVo;
+    }
+
+    /**
+     * 设置是否点赞
+     * @param ratingId 评价id
+     * @param userId 用户id
+     * @param commonRatingVo 评价vo
+     */
+    private void setIsLike(Integer ratingId, Long userId, CommonRatingVo commonRatingVo) {
         List<LifeLikeRecord> lifeLikeRecords = lifeLikeRecordMapper.selectList(new QueryWrapper<LifeLikeRecord>().lambda()
                 .eq(LifeLikeRecord::getDianzanId, userId)
                 .eq(LifeLikeRecord::getHuifuId, ratingId)
@@ -258,10 +298,21 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
         } else {
             commonRatingVo.setIsLike(0);
         }
-        // 根据业务类型处理
+    }
+
+    /**
+     * 根据业务类型处理
+     * @param ratingId 评价id
+     * @param userId 用户id
+     * @param commonRatingVo 评价vo
+     */
+    private void extractedWithBusinessType(Integer ratingId, Long userId, CommonRatingVo commonRatingVo) {
         if(commonRatingVo.getBusinessType().equals(RatingBusinessTypeEnum.STORE_RATING.getBusinessType())){
             // 1店铺信息
             StoreInfo storeInfo = storeInfoMapper.selectById(commonRatingVo.getBusinessId());
+            if(storeInfo == null){
+                throw new IllegalArgumentException("店铺不存在");
+            }
             if( null != storeInfo) {
                 commonRatingVo.setStoreName(storeInfo.getStoreName());
                 commonRatingVo.setStoreEvaluate(storeInfo.getStoreEvaluate());
@@ -310,7 +361,6 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
             commonRatingVo.setCommentCount(count.get());
             commonRatingVo.setChildCommonComments(commonCommentVos);
         }
-        return commonRatingVo;
     }
 
     private List<CommonCommentVo> getChildCommentsRecursively(Long id, Long userId) {

+ 263 - 0
alien-store/src/main/java/shop/alien/store/service/impl/LicenseAuditAsyncService.java

@@ -0,0 +1,263 @@
+package shop.alien.store.service.impl;
+
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.*;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+import shop.alien.entity.store.StoreImg;
+import shop.alien.entity.store.StoreInfo;
+import shop.alien.entity.storePlatform.StoreLicenseHistory;
+import shop.alien.mapper.StoreImgMapper;
+import shop.alien.mapper.StoreInfoMapper;
+import shop.alien.mapper.storePlantform.StoreLicenseHistoryMapper;
+import shop.alien.store.util.ai.AiAuthTokenUtil;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 证照审核异步服务
+ * 用于异步处理证照审核,避免阻塞主流程
+ *
+ * @author system
+ * @since 2026-01-14
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class LicenseAuditAsyncService {
+
+    private final AiAuthTokenUtil aiAuthTokenUtil;
+    private final RestTemplate restTemplate;
+    private final StoreInfoMapper storeInfoMapper;
+    private final StoreLicenseHistoryMapper licenseHistoryMapper;
+    private final StoreImgMapper storeImgMapper;
+
+    @Value("${third-party-license-review.base-url}")
+    private String licenseReviewPath;
+
+    /**
+     * 异步调用证照审核AI接口并处理审核结果
+     * 1. 调用AI接口获取审核结果
+     * 2. 如果是营业执照(licenseStatus=1),解析并存入到期时间
+     * 3. 根据审核结果(is_expired或is_valid)更新store_license_history表的审核状态
+     *
+     * @param storeId 门店ID
+     * @param licenseStatus 证照类型:1-营业执照,2-其他资质证明
+     * @param imageUrl 证照图片URL
+     */
+    @Async("taskExecutor")
+    public void validateLicenseExpiryAndUpdate(Integer storeId, Integer licenseStatus, String imageUrl) {
+        if (storeId == null || licenseStatus == null || StringUtils.isEmpty(imageUrl)) {
+            log.warn("证照审核参数不完整,storeId={}, licenseStatus={}, imageUrl={}", storeId, licenseStatus, imageUrl);
+            return;
+        }
+
+        try {
+            String accessToken = aiAuthTokenUtil.getAccessToken();
+            if (StringUtils.isEmpty(accessToken)) {
+                log.error("获取AI访问令牌失败,门店ID:{}", storeId);
+                return;
+            }
+
+            HttpHeaders aiHeaders = new HttpHeaders();
+            aiHeaders.setContentType(MediaType.APPLICATION_JSON);
+            aiHeaders.set("Authorization", "Bearer " + accessToken);
+
+            // 构建请求体
+            Map<String, Object> jsonBody = new HashMap<>();
+            jsonBody.put("image_url", imageUrl);
+
+            HttpEntity<Map<String, Object>> request = new HttpEntity<>(jsonBody, aiHeaders);
+
+            // 设置超时时间
+            HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
+            factory.setConnectTimeout(10000); // 连接超时10秒
+            factory.setReadTimeout(120000); // 读取超时120秒(2分钟)
+            restTemplate.setRequestFactory(factory);
+
+            ResponseEntity<String> response = restTemplate.postForEntity(licenseReviewPath, request, String.class);
+
+            if (response.getStatusCodeValue() != 200) {
+                log.error("证照审核接口调用失败,门店ID:{},http状态:{}", storeId, response.getStatusCode());
+                return;
+            }
+
+            if (StringUtils.isEmpty(response.getBody())) {
+                log.error("证照审核接口返回内容为空,门店ID:{}", storeId);
+                return;
+            }
+
+            JSONObject responseNode = JSONObject.parseObject(response.getBody());
+            if (responseNode == null) {
+                log.error("证照审核接口响应解析失败,门店ID:{}", storeId);
+                return;
+            }
+
+            Integer code = responseNode.getInteger("code");
+            if (code == null || code != 200) {
+                String message = responseNode.getString("message");
+                log.error("证照审核接口调用失败,门店ID:{},错误码: {}, 错误信息: {}", storeId, code, message);
+                return;
+            }
+
+            Map<String, Object> dataMap = (Map<String, Object>) responseNode.get("data");
+            if (dataMap == null) {
+                log.warn("证照审核接口返回data为空,门店ID:{}", storeId);
+                return;
+            }
+
+            Boolean isValid = (Boolean) dataMap.get("is_valid");
+            String expiryDateStr = (String) dataMap.get("expiry_date");
+            Boolean isExpired = (Boolean) dataMap.get("is_expired");
+            Integer remainingDays = (Integer) dataMap.get("remaining_days");
+            String reason = (String) dataMap.get("reason");
+            String licenseType = (String) dataMap.get("license_type");
+
+            String licenseTypeName = licenseStatus == 1 ? "营业执照" : "其他资质证明";
+            log.info("{}证照审核结果,门店ID:{},图片URL:{},is_valid={},expiry_date={},is_expired={},remaining_days={},license_type={}",
+                    licenseTypeName, storeId, imageUrl, isValid, expiryDateStr, isExpired, remainingDays, licenseType);
+
+            // 如果是营业执照,解析并存入到期时间
+            if (licenseStatus == 1 && StringUtils.isNotEmpty(expiryDateStr)) {
+                try {
+                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+                    Date expiryDate = sdf.parse(expiryDateStr);
+
+                    StoreInfo updateStoreInfo = new StoreInfo();
+                    updateStoreInfo.setId(storeId);
+                    updateStoreInfo.setBusinessLicenseExpirationTime(expiryDate);
+                    storeInfoMapper.updateById(updateStoreInfo);
+                    log.info("营业执照到期时间已更新,门店ID:{},到期时间:{}", storeId, expiryDateStr);
+                } catch (Exception e) {
+                    log.error("解析营业执照到期时间失败,门店ID:{},expiryDate:{}", storeId, expiryDateStr, e);
+                }
+            }
+
+            // 判断审核结果
+            boolean needReject = false;
+            boolean needApprove = false;
+            String rejectReason = null;
+
+            if (licenseStatus == 1) {
+                // 营业执照的审核逻辑
+                if (!"business_license".equals(licenseType)) {
+                    // 上传的图片不是营业执照licenseType="business_license"
+                    needReject = true;
+                    rejectReason = "上传的图片不是营业执照";
+                } else if (Boolean.TRUE.equals(isExpired)) {
+                    // 营业执照已过期
+                    needReject = true;
+                    rejectReason = "已过期";
+                } else if (Boolean.TRUE.equals(isValid) && "business_license".equals(licenseType) && Boolean.FALSE.equals(isExpired)) {
+                    // 营业执照有效且未过期,审核通过
+                    needApprove = true;
+                }
+            } else if (licenseStatus == 2) {
+                // 其他资质证明的审核逻辑
+                if (Boolean.FALSE.equals(isValid)) {
+                    // 上传的不是证件图片
+                    needReject = true;
+                    rejectReason = "上传的图片含非证件图片";
+                } else if (Boolean.TRUE.equals(isValid)) {
+                    // 证件有效,审核通过
+                    needApprove = true;
+                }
+            }
+
+            // 更新store_license_history表的审核状态
+            if (licenseStatus == 2) {
+                // 其他资质证明:img_url字段包含多个URL(用逗号分隔),需要查找包含当前图片URL的记录
+                LambdaUpdateWrapper<StoreLicenseHistory> updateWrapper = new LambdaUpdateWrapper<>();
+                updateWrapper.eq(StoreLicenseHistory::getStoreId, storeId)
+                        .eq(StoreLicenseHistory::getLicenseStatus, licenseStatus)
+                        .eq(StoreLicenseHistory::getLicenseExecuteStatus, 2) // 2-审核中
+                        .like(StoreLicenseHistory::getImgUrl, imageUrl) // 使用like查找包含该图片URL的记录
+                        .eq(StoreLicenseHistory::getDeleteFlag, 0);
+
+                if (needReject) {
+                    // 审核拒绝:一张图片审核拒绝或失败就设置状态为审核拒绝
+                    updateWrapper.set(StoreLicenseHistory::getLicenseExecuteStatus, 3) // 3-审核拒绝
+                            .set(StoreLicenseHistory::getReasonRefusal, rejectReason);
+                    licenseHistoryMapper.update(null, updateWrapper);
+                    log.info("{}AI审核拒绝,门店ID:{},图片URL:{},拒绝原因:{}", licenseTypeName, storeId, imageUrl, rejectReason);
+                    
+                    // 审核拒绝时,删除store_img表中的记录(逻辑删除),避免前端展示审核拒绝的图片
+                    LambdaUpdateWrapper<StoreImg> deleteImgWrapper = new LambdaUpdateWrapper<>();
+                    deleteImgWrapper.eq(StoreImg::getStoreId, storeId)
+                            .eq(StoreImg::getImgType, 35) // 其他资质证明对应35
+                            .eq(StoreImg::getImgUrl, imageUrl)
+                            .eq(StoreImg::getDeleteFlag, 0)
+                            .set(StoreImg::getDeleteFlag, 1);
+                    storeImgMapper.update(null, deleteImgWrapper);
+                    log.info("{}AI审核拒绝,已删除store_img记录,门店ID:{},图片URL:{}", licenseTypeName, storeId, imageUrl);
+                } else if (needApprove) {
+                    // 审核通过:需要检查所有图片是否都审核通过
+                    // 先查询当前记录,获取所有图片URL
+                    StoreLicenseHistory currentHistory = licenseHistoryMapper.selectOne(
+                            new LambdaQueryWrapper<StoreLicenseHistory>()
+                                    .eq(StoreLicenseHistory::getStoreId, storeId)
+                                    .eq(StoreLicenseHistory::getLicenseStatus, licenseStatus)
+                                    .eq(StoreLicenseHistory::getLicenseExecuteStatus, 2)
+                                    .like(StoreLicenseHistory::getImgUrl, imageUrl)
+                                    .eq(StoreLicenseHistory::getDeleteFlag, 0)
+                                    .last("LIMIT 1")
+                    );
+                    
+                    if (currentHistory != null && StringUtils.isNotEmpty(currentHistory.getImgUrl())) {
+                        // 检查是否所有图片都已审核通过
+                        // 由于是异步审核,每张图片都会调用此方法,所以这里只记录单张图片的审核通过
+                        // 如果需要等所有图片都审核通过才更新状态,需要额外的机制来跟踪
+                        log.info("{}AI审核通过,门店ID:{},图片URL:{}", licenseTypeName, storeId, imageUrl);
+                    }
+                }
+            } else {
+                // 营业执照:精确匹配imgUrl
+                LambdaUpdateWrapper<StoreLicenseHistory> updateWrapper = new LambdaUpdateWrapper<>();
+                updateWrapper.eq(StoreLicenseHistory::getStoreId, storeId)
+                        .eq(StoreLicenseHistory::getLicenseStatus, licenseStatus)
+                        .eq(StoreLicenseHistory::getLicenseExecuteStatus, 2) // 2-审核中
+                        .eq(StoreLicenseHistory::getImgUrl, imageUrl)
+                        .eq(StoreLicenseHistory::getDeleteFlag, 0);
+
+                if (needReject) {
+                    // 审核拒绝
+                    updateWrapper.set(StoreLicenseHistory::getLicenseExecuteStatus, 3) // 3-审核拒绝
+                            .set(StoreLicenseHistory::getReasonRefusal, rejectReason);
+                    licenseHistoryMapper.update(null, updateWrapper);
+                    log.info("{}AI审核拒绝,门店ID:{},图片URL:{},拒绝原因:{}", licenseTypeName, storeId, imageUrl, rejectReason);
+                    
+                    // 审核拒绝时,删除store_img表中的记录(逻辑删除),避免前端展示审核拒绝的图片
+                    LambdaUpdateWrapper<StoreImg> deleteImgWrapper = new LambdaUpdateWrapper<>();
+                    deleteImgWrapper.eq(StoreImg::getStoreId, storeId)
+                            .eq(StoreImg::getImgType, 14) // 营业执照对应14
+                            .eq(StoreImg::getImgUrl, imageUrl)
+                            .eq(StoreImg::getDeleteFlag, 0)
+                            .set(StoreImg::getDeleteFlag, 1);
+                    storeImgMapper.update(null, deleteImgWrapper);
+                    log.info("{}AI审核拒绝,已删除store_img记录,门店ID:{},图片URL:{}", licenseTypeName, storeId, imageUrl);
+                } else if (needApprove) {
+                    // 审核通过
+                    updateWrapper.set(StoreLicenseHistory::getLicenseExecuteStatus, 1); // 1-审核通过
+                    licenseHistoryMapper.update(null, updateWrapper);
+                    log.info("{}AI审核通过,门店ID:{},图片URL:{}", licenseTypeName, storeId, imageUrl);
+                }
+            }
+        } catch (Exception e) {
+            String licenseTypeName = licenseStatus == 1 ? "营业执照" : "其他资质证明";
+            log.error("调用{}证照审核接口异常,门店ID:{},图片URL:{}", licenseTypeName, storeId, imageUrl, e);
+            // AI审核失败不影响业务流程,继续执行
+        }
+    }
+}
+

+ 35 - 60
alien-store/src/main/java/shop/alien/store/service/impl/StoreInfoServiceImpl.java

@@ -166,6 +166,11 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
      */
     private final StoreLicenseHistoryMapper licenseHistoryMapper;
 
+    /**
+     * 证照审核异步服务
+     */
+    private final LicenseAuditAsyncService licenseAuditAsyncService;
+
     @Resource
     private StoreIncomeDetailsRecordService storeIncomeDetailsRecordService;
 
@@ -6059,10 +6064,10 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         storeImg.setDeleteFlag(0);
         storeImgMapper.insert(storeImg);
         
-        // 插入营业执照历史记录(licenseStatus=3表示营业执照,licenseExecuteStatus=2表示审核中)
+        // 插入营业执照历史记录(licenseStatus=1表示营业执照,licenseExecuteStatus=2表示审核中)
         StoreLicenseHistory licenseHistory = new StoreLicenseHistory();
         licenseHistory.setStoreId(storeImg.getStoreId());
-        licenseHistory.setLicenseStatus(3); // 3-营业执照
+        licenseHistory.setLicenseStatus(1); // 1-营业执照
         licenseHistory.setLicenseExecuteStatus(2); // 2-审核中
         licenseHistory.setImgUrl(storeImg.getImgUrl());
         licenseHistory.setDeleteFlag(0);
@@ -6074,44 +6079,11 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         storeInfo.setBusinessLicenseStatus(2); // 2-待审核
         storeInfo.setUpdateBusinessLicenseTime(new Date());
         
-        // 从OCR识别记录中获取营业执照到期时间
-        if (StringUtils.isNotEmpty(storeImg.getImgUrl())) {
-            OcrImageUpload businessLicenseOcr = ocrImageUploadMapper.selectOne(
-                    new LambdaQueryWrapper<OcrImageUpload>()
-                            .eq(OcrImageUpload::getImageUrl, storeImg.getImgUrl())
-                            .eq(OcrImageUpload::getOcrType, OcrTypeEnum.BUSINESS_LICENSE.getCode())
-                            .orderByDesc(OcrImageUpload::getCreateTime)
-                            .last("limit 1")
-            );
-            if (businessLicenseOcr != null && StringUtils.isNotEmpty(businessLicenseOcr.getOcrResult())) {
-                try {
-                    com.alibaba.fastjson2.JSONObject ocrResult = com.alibaba.fastjson2.JSONObject.parseObject(businessLicenseOcr.getOcrResult());
-                    // 解析营业执照到期时间
-                    Date expirationTime = parseBusinessLicenseExpirationDate(ocrResult);
-                    if (expirationTime != null) {
-                        storeInfo.setBusinessLicenseExpirationTime(expirationTime);
-                        log.info("营业执照OCR数据解析成功,门店ID:{},到期时间:{}", storeImg.getStoreId(), expirationTime);
-                    }
-                } catch (Exception e) {
-                    log.error("解析营业执照OCR数据失败,门店ID:{}", storeImg.getStoreId(), e);
-                }
-            }
-        }
-        
         storeInfoMapper.updateById(storeInfo);
         
-        // 调用AI审核接口
-        try {
-            StoreInfo storeInfoForAi = storeInfoMapper.selectById(storeImg.getStoreId());
-            if (storeInfoForAi != null && StringUtils.isNotEmpty(storeInfoForAi.getStoreName())) {
-                Map<String, Object> ocrData = getStoreOcrData(storeImg.getImgUrl(), storeInfoForAi.getStoreName());
-                Boolean overallMatch = (Boolean) ocrData.get("overall_match");
-                log.info("营业执照AI审核结果,门店ID:{},overallMatch:{}", storeImg.getStoreId(), overallMatch);
-                // 注意:AI审核结果不影响当前状态,审核状态由后台管理员审核后更新
-            }
-        } catch (Exception e) {
-            log.error("调用营业执照AI审核接口失败,门店ID:{}", storeImg.getStoreId(), e);
-            // AI审核失败不影响业务流程,继续执行
+        // 异步调用证照审核AI接口并处理审核结果
+        if (StringUtils.isNotEmpty(storeImg.getImgUrl())) {
+            licenseAuditAsyncService.validateLicenseExpiryAndUpdate(storeImg.getStoreId(), 1, storeImg.getImgUrl());
         }
         
         return 1;
@@ -6119,15 +6091,15 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
 
     @Override
     public List<StoreLicenseHistory> getBusinessLicenseHistory(Integer storeId) {
-        // 查询营业执照变更记录(licenseStatus=3表示营业执照)
-        // 包括审核中的记录(licenseExecuteStatus=2)和已删除的记录(用于显示拒绝记录)
+        // 查询营业执照变更记录
+        // 包括审核中的记录(licenseExecuteStatus=2)和已删除的记录
         return licenseHistoryMapper.selectList(
                 new LambdaQueryWrapper<StoreLicenseHistory>()
                         .eq(StoreLicenseHistory::getStoreId, storeId)
-                        .eq(StoreLicenseHistory::getLicenseStatus, 3) // 3-营业执照
+                        .eq(StoreLicenseHistory::getLicenseStatus, 1) // 1-营业执照
                         .and(wrapper -> wrapper
-                                .eq(StoreLicenseHistory::getLicenseExecuteStatus, 2) // 审核中
-                                .or()
+                                // .eq(StoreLicenseHistory::getLicenseExecuteStatus, 2) // 审核中
+                                // .or()
                                 .eq(StoreLicenseHistory::getDeleteFlag, 0) // 未删除的记录
                         )
                         .orderByDesc(StoreLicenseHistory::getCreatedTime)
@@ -6185,6 +6157,9 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         StoreInfo storeInfoForAi = storeInfoMapper.selectById(storeId);
         String merchantName = storeInfoForAi != null ? storeInfoForAi.getStoreName() : null;
         
+        // 收集所有图片URL
+        List<String> imgUrlList = new ArrayList<>();
+        
         // 插入新的其他资质证明图片(最多9张)
         for (int i = 0; i < maxCount; i++) {
             StoreImg storeImg = storeImgList.get(i);
@@ -6195,26 +6170,26 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
             storeImg.setDeleteFlag(0);
             storeImgMapper.insert(storeImg);
             
-            // 插入其他资质证明历史记录(licenseStatus=4表示其他资质证明,licenseExecuteStatus=2表示审核中)
+            // 收集图片URL
+            if (StringUtils.isNotEmpty(storeImg.getImgUrl())) {
+                imgUrlList.add(storeImg.getImgUrl());
+            }
+        }
+        
+        // 将所有图片URL合并存入一条store_license_history记录(用逗号分隔)
+        if (!imgUrlList.isEmpty()) {
+            String allImgUrls = String.join(",", imgUrlList);
             StoreLicenseHistory licenseHistory = new StoreLicenseHistory();
             licenseHistory.setStoreId(storeId);
-            licenseHistory.setLicenseStatus(4); // 4-其他资质证明
+            licenseHistory.setLicenseStatus(2); // 2-其他资质证明
             licenseHistory.setLicenseExecuteStatus(2); // 2-审核中
-            licenseHistory.setImgUrl(storeImg.getImgUrl());
+            licenseHistory.setImgUrl(allImgUrls);
             licenseHistory.setDeleteFlag(0);
             licenseHistoryMapper.insert(licenseHistory);
             
-            // 调用AI审核接口(每张图片都审核)
-            if (StringUtils.isNotEmpty(merchantName) && StringUtils.isNotEmpty(storeImg.getImgUrl())) {
-                try {
-                    Map<String, Object> ocrData = getStoreOcrData(storeImg.getImgUrl(), merchantName);
-                    Boolean overallMatch = (Boolean) ocrData.get("overall_match");
-                    log.info("其他资质证明AI审核结果,门店ID:{},图片URL:{},overallMatch:{}", storeId, storeImg.getImgUrl(), overallMatch);
-                    // 注意:AI审核结果不影响当前状态,审核状态由后台管理员审核后更新
-                } catch (Exception e) {
-                    log.error("调用其他资质证明AI审核接口失败,门店ID:{},图片URL:{}", storeId, storeImg.getImgUrl(), e);
-                    // AI审核失败不影响业务流程,继续执行
-                }
+            // 异步调用证照审核AI接口并处理审核结果(每张图片都审核)
+            for (String imgUrl : imgUrlList) {
+                licenseAuditAsyncService.validateLicenseExpiryAndUpdate(storeId, 2, imgUrl);
             }
         }
         
@@ -6223,9 +6198,9 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
 
     @Override
     public List<StoreLicenseHistory> getOtherQualificationHistory(Integer storeId) {
-        // 查询其他资质证明变更记录(licenseStatus=4表示其他资质证明)
-        // 查询所有状态的记录:审核中(2)、审核拒绝(3)、审核通过(1)
-        return licenseHistoryMapper.queryLicenceByStatusList(storeId, 4, 0);
+        // 查询其他资质证明变更记录(licenseStatus=2表示其他资质证明)
+        // 查询所有状态的记录:审核通过(1)、审核中(2)、审核拒绝(3)
+        return licenseHistoryMapper.queryLicenceByStatusList(storeId, 2, 0);
     }
 
     @Override

+ 25 - 2
alien-util/pom.xml

@@ -14,8 +14,8 @@
     <description>alien-util</description>
 
     <properties>
-        <maven.compiler.source>21</maven.compiler.source>
-        <maven.compiler.target>21</maven.compiler.target>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
     </properties>
@@ -33,6 +33,12 @@
 <!--        </dependency>-->
 
         <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
         </dependency>
@@ -87,6 +93,12 @@
         <dependency>
             <groupId>dom4j</groupId>
             <artifactId>dom4j</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>xml-apis</groupId>
+                    <artifactId>xml-apis</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
 
         <dependency>
@@ -142,6 +154,12 @@
             <groupId>com.artofsolving</groupId>
             <artifactId>jodconverter</artifactId>
             <version>2.2.1</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>javax.servlet</groupId>
+                    <artifactId>servlet-api</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
 
         <!--生成二维码 -->
@@ -335,6 +353,11 @@
             <artifactId>spring-cloud-context</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>com.github.ulisesbocchio</groupId>
+            <artifactId>jasypt-spring-boot-starter</artifactId>
+        </dependency>
+
     </dependencies>
 
     <build>

+ 57 - 0
alien-util/src/main/java/shop/alien/util/encryption/JasyptAutoConfiguration.java

@@ -0,0 +1,57 @@
+package shop.alien.util.encryption;
+
+import org.jasypt.encryption.StringEncryptor;
+import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
+import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import shop.alien.util.encryption.properties.EncryptProperties;
+
+/**
+ * Jasypt 自动配置类 - 只要依赖 alien-util 即可自动生效
+ */
+@Configuration
+@EnableConfigurationProperties(EncryptProperties.class)
+public class JasyptAutoConfiguration {
+
+    public static final String ALGORITHM = "PBEWithMD5AndDES";
+
+    @Bean("jasyptStringEncryptor")
+    public StringEncryptor stringEncryptor(org.springframework.core.env.Environment environment) {
+        PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
+        encryptor.setConfig(createConfig(environment));
+        return encryptor;
+    }
+
+    private SimpleStringPBEConfig createConfig(org.springframework.core.env.Environment environment) {
+        // 1. 尝试从 Spring 环境获取(支持 -D 参数和环境变量)
+        String pwd = environment.getProperty("jasypt.encryptor.password");
+
+        // 2. 兜底:直接从 OS 环境变量读取(Docker 常用)
+        if (pwd == null || pwd.isEmpty()) {
+            pwd = System.getenv("JASYPT_ENCRYPTOR_PASSWORD");
+        }
+
+        // 3. 兜底:尝试读取系统属性
+        if (pwd == null || pwd.isEmpty()) {
+            pwd = System.getProperty("jasypt.encryptor.password");
+        }
+
+        if (pwd == null || pwd.isEmpty()) {
+            // 这里抛出明确异常,防止 Jasypt 报那个模糊的 "empty" 错误
+            throw new RuntimeException("Jasypt 密码配置失败!请检查 Docker 环境变量 JASYPT_ENCRYPTOR_PASSWORD 是否正确设置。");
+        }
+
+        SimpleStringPBEConfig config = new SimpleStringPBEConfig();
+        config.setPassword(pwd);
+        config.setAlgorithm(ALGORITHM);
+        config.setKeyObtentionIterations("1000");
+        config.setPoolSize("1");
+        config.setProviderName("SunJCE");
+        config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
+        config.setIvGeneratorClassName("org.jasypt.iv.NoIvGenerator");
+        config.setStringOutputType("base64");
+        return config;
+    }
+}

+ 33 - 0
alien-util/src/main/java/shop/alien/util/encryption/JasyptEncryptorUtil.java

@@ -0,0 +1,33 @@
+package shop.alien.util.encryption;
+
+import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
+import org.jasypt.encryption.pbe.config.EnvironmentPBEConfig;
+import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
+
+/**
+ * Jasypt 统一加解密工具类
+ */
+public class JasyptEncryptorUtil {
+
+    /**
+     * 手动加密
+     * @param salt 盐值
+     * @param text 待加密文本
+     * @return 密文
+     */
+    public static String encode(String salt, String text) {
+        StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
+        encryptor.setAlgorithm(JasyptAutoConfiguration.ALGORITHM);
+        encryptor.setPassword(salt); // 直接设置盐值,不经过 Config 对象
+        encryptor.setIvGenerator(new org.jasypt.iv.NoIvGenerator());
+        return encryptor.encrypt(text);
+    }
+
+    public static String decode(String salt, String cipherText) {
+        StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
+        encryptor.setAlgorithm(JasyptAutoConfiguration.ALGORITHM);
+        encryptor.setPassword(salt); // 直接设置盐值
+        encryptor.setIvGenerator(new org.jasypt.iv.NoIvGenerator());
+        return encryptor.decrypt(cipherText);
+    }
+}

+ 2 - 2
alien-config/src/main/java/shop/alien/config/advice/DecryptRequestBodyAdvice.java → alien-util/src/main/java/shop/alien/util/encryption/advice/DecryptRequestBodyAdvice.java

@@ -1,4 +1,4 @@
-package shop.alien.config.advice;
+package shop.alien.util.encryption.advice;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.core.MethodParameter;
@@ -8,9 +8,9 @@ import org.springframework.http.converter.HttpMessageConverter;
 import org.springframework.util.StreamUtils;
 import org.springframework.web.bind.annotation.ControllerAdvice;
 import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;
-import shop.alien.config.properties.EncryptProperties;
 import shop.alien.util.encryption.Decrypt;
 import shop.alien.util.encryption.StandardAesUtil;
+import shop.alien.util.encryption.properties.EncryptProperties;
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;

+ 2 - 2
alien-config/src/main/java/shop/alien/config/advice/EncryptResponseBodyAdvice.java → alien-util/src/main/java/shop/alien/util/encryption/advice/EncryptResponseBodyAdvice.java

@@ -1,4 +1,4 @@
-package shop.alien.config.advice;
+package shop.alien.util.encryption.advice;
 
 import com.alibaba.fastjson.JSON;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -9,9 +9,9 @@ import org.springframework.http.server.ServerHttpRequest;
 import org.springframework.http.server.ServerHttpResponse;
 import org.springframework.web.bind.annotation.ControllerAdvice;
 import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
-import shop.alien.config.properties.EncryptProperties;
 import shop.alien.util.encryption.Encrypt;
 import shop.alien.util.encryption.StandardAesUtil;
+import shop.alien.util.encryption.properties.EncryptProperties;
 
 /**
  * 响应体加密 Advice

+ 1 - 2
alien-config/src/main/java/shop/alien/config/properties/EncryptProperties.java → alien-util/src/main/java/shop/alien/util/encryption/properties/EncryptProperties.java

@@ -1,4 +1,4 @@
-package shop.alien.config.properties;
+package shop.alien.util.encryption.properties;
 
 import lombok.Data;
 import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -9,7 +9,6 @@ import org.springframework.stereotype.Component;
  * 加解密配置属性
  */
 @Data
-@Component
 @RefreshScope
 @ConfigurationProperties(prefix = "alien.encrypt")
 public class EncryptProperties {

+ 20 - 0
pom.xml

@@ -30,6 +30,7 @@
         <spring-cloud-nacos.version>2.2.5.RELEASE</spring-cloud-nacos.version>
         <spring.cloud.version>Hoxton.SR1</spring.cloud.version>
         <mybatisplus.version>3.2.0</mybatisplus.version>
+        <jasypt.version>3.0.3</jasypt.version>
     </properties>
 
     <dependencyManagement>
@@ -378,6 +379,18 @@
                 <groupId>pro.fessional</groupId>
                 <artifactId>kaptcha</artifactId>
                 <version>2.3.3</version>
+                <exclusions>
+                    <exclusion>
+                        <groupId>javax.servlet</groupId>
+                        <artifactId>servlet-api</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+
+            <dependency>
+                <groupId>javax.servlet</groupId>
+                <artifactId>javax.servlet-api</artifactId>
+                <version>4.0.1</version>
             </dependency>
 
             <dependency>
@@ -392,6 +405,13 @@
                 <version>${spring-boot.version}</version>
             </dependency>
 
+            <!-- Jasypt Encryption -->
+            <dependency>
+                <groupId>com.github.ulisesbocchio</groupId>
+                <artifactId>jasypt-spring-boot-starter</artifactId>
+                <version>${jasypt.version}</version>
+            </dependency>
+
             <!--Other End-->
         </dependencies>
     </dependencyManagement>