Parcourir la source

Merge branch 'sit' into sit-new-demands

lutong il y a 3 mois
Parent
commit
10719867f6
21 fichiers modifiés avec 498 ajouts et 89 suppressions
  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. 1 1
      alien-entity/src/main/java/shop/alien/entity/storePlatform/StoreLicenseHistory.java
  6. 6 0
      alien-gateway/pom.xml
  7. 1 1
      alien-job/src/main/resources/logback-spring.xml
  8. 1 1
      alien-store/src/main/java/shop/alien/store/AlienStoreApplication.java
  9. 2 2
      alien-store/src/main/java/shop/alien/store/controller/LifeFeedbackController.java
  10. 1 1
      alien-store/src/main/java/shop/alien/store/controller/StoreInfoController.java
  11. 44 10
      alien-store/src/main/java/shop/alien/store/controller/StorePriceController.java
  12. 263 0
      alien-store/src/main/java/shop/alien/store/service/impl/LicenseAuditAsyncService.java
  13. 35 60
      alien-store/src/main/java/shop/alien/store/service/impl/StoreInfoServiceImpl.java
  14. 25 2
      alien-util/pom.xml
  15. 57 0
      alien-util/src/main/java/shop/alien/util/encryption/JasyptAutoConfiguration.java
  16. 33 0
      alien-util/src/main/java/shop/alien/util/encryption/JasyptEncryptorUtil.java
  17. 2 2
      alien-util/src/main/java/shop/alien/util/encryption/advice/DecryptRequestBodyAdvice.java
  18. 2 2
      alien-util/src/main/java/shop/alien/util/encryption/advice/EncryptResponseBodyAdvice.java
  19. 1 2
      alien-util/src/main/java/shop/alien/util/encryption/properties/EncryptProperties.java
  20. 2 0
      alien-util/src/main/resources/META-INF/spring.factories
  21. 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;
 }
 

+ 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;
 

+ 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"/>
 

+ 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/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, "加解密测试成功");
-    }
+
 }
 

+ 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

@@ -168,6 +168,11 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
      */
     private final StoreLicenseHistoryMapper licenseHistoryMapper;
 
+    /**
+     * 证照审核异步服务
+     */
+    private final LicenseAuditAsyncService licenseAuditAsyncService;
+
     @Resource
     private StoreIncomeDetailsRecordService storeIncomeDetailsRecordService;
 
@@ -6070,10 +6075,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);
@@ -6085,44 +6090,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;
@@ -6130,15 +6102,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)
@@ -6196,6 +6168,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);
@@ -6206,26 +6181,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);
             }
         }
         
@@ -6234,9 +6209,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 {

+ 2 - 0
alien-util/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+shop.alien.util.encryption.JasyptAutoConfiguration

+ 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>