Ver Fonte

Merge branch 'sit-shenzhen' into sit

liyafei há 4 dias atrás
pai
commit
48d688738f

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

@@ -30,7 +30,7 @@ public class StoreImg extends Model<StoreImg> {
     @TableField("store_id")
     private Integer storeId;
 
-    @ApiModelProperty(value = "图片类型, 0:其他, 1:入口图, 2:相册, 3:菜品, 4:环境, 5:价目表, 6:推荐菜, 7:菜单, 8:用户评论, 9:商家申诉,10:商家头像,11:店铺轮播图,12:联名卡图片,13:动态折扣, 14:营业执照,15:合同照片,17:打卡广场小人图片 18: 二手商品发布图片 19:二手商品与用户举报图片,20头图单图模式,21头图多图模式 , 22续签合同, 23 二手商品记录图片类型, 24 食品经营许可证审核前类型 25.食品经营许可证审核后类型 26.运营活动活动标题图 27.运营活动活动详情图 28.运动设施 29.洗浴设施及服务 30.酒水 31.娱乐经营许可证 32.娱乐经营许可证审核前")
+    @ApiModelProperty(value = "图片类型, 0:其他, 1:入口图, 2:相册, 3:菜品, 4:环境, 5:价目表, 6:推荐菜, 7:菜单, 8:用户评论, 9:商家申诉,10:商家头像,11:店铺轮播图,12:联名卡图片,13:动态折扣, 14:营业执照,15:合同照片,17:打卡广场小人图片 18: 二手商品发布图片 19:二手商品与用户举报图片,20头图单图模式,21头图多图模式 , 22续签合同, 23 二手商品记录图片类型, 24 食品经营许可证审核前类型 25.食品经营许可证审核后类型 26.运营活动活动标题图 27.运营活动活动详情图 28.运动设施 29.洗浴设施及服务 30.酒水 31.娱乐经营许可证 32.娱乐经营许可证审核前, 33:身份证正面, 34:身份证反面")
     @TableField("img_type")
     private Integer imgType;
 

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

@@ -289,6 +289,24 @@ public class StoreInfo {
     @TableField("update_business_license_time")
     private Date updateBusinessLicenseTime;
 
+    @ApiModelProperty(value = "身份证状态 字典 foodLicenceStatus")
+    @TableField("id_card_status")
+    private Integer idCardStatus;
+
+    @ApiModelProperty(value = "身份证审核失败原因")
+    @TableField("id_card_reason")
+    private String idCardReason;
+
+    @ApiModelProperty(value = "身份证到期时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @TableField("id_card_expiration_time")
+    private Date idCardExpirationTime;
+
+    @ApiModelProperty(value = "变更身份证提交时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @TableField("update_id_card_time")
+    private Date updateIdCardTime;
+
     @ApiModelProperty(value = "审核时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     @TableField("review_date")

+ 4 - 4
alien-entity/src/main/resources/mapper/LifeFeedbackMapper.xml

@@ -120,8 +120,8 @@
     <select id="selectWebFeedbackList" resultType="shop.alien.entity.store.vo.LifeFeedbackListVo">
         SELECT
             f.id,
-            u.user_name AS nickName,
-            u.user_phone AS phone,
+            u.nick_name AS nickName,
+            u.phone AS phone,
             f.feedback_type AS feedbackType,
             CASE f.feedback_type
                 WHEN 0 THEN 'bug反馈'
@@ -169,8 +169,8 @@
     <select id="selectWebFeedbackDetail" resultType="shop.alien.entity.store.vo.LifeFeedbackDetailVo">
         SELECT
             f.id,
-            u.user_name AS nickName,
-            u.user_phone AS phone,
+            u.nick_name AS nickName,
+            u.phone AS phone,
             f.staff_id AS staffId,
             CONCAT(IFNULL(s.user_name, '')) AS staffInfo,
             f.feedback_source AS feedbackSource,

+ 20 - 6
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivityServiceImpl.java

@@ -29,15 +29,11 @@ import shop.alien.storeplatform.feign.AlienAIFeign;
 import shop.alien.storeplatform.service.OperationalActivityService;
 
 import java.text.SimpleDateFormat;
-import java.time.Instant;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.time.ZoneId;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.List;
-import java.util.stream.Collectors;
+
 
 /**
  * 运营活动服务实现类
@@ -108,7 +104,25 @@ public class OperationalActivityServiceImpl implements OperationalActivityServic
                     } else {
                         ObjectMapper objectMapper = new ObjectMapper();
                         ObjectNode requestBody = objectMapper.createObjectNode();
-                        requestBody.put("text", dto.getImgDescribe());
+
+                        String tpl = "活动名称:%s\n"
+                                + "活动时间:%s - %s\n"
+                                + "用户可参与次数:%s\n"
+                                + "活动规则:%s\n"
+                                + "优惠券发放数量:%s\n"
+                                + "图片描述:%s";
+
+                        String filled = String.format(
+                                tpl,
+                                dto.getActivityName(),
+                                dto.getStartTime(),
+                                dto.getEndTime(),
+                                dto.getParticipationLimit(),
+                                dto.getActivityRule(),
+                                dto.getCouponQuantity(),
+                                dto.getImgDescribe()
+                        );
+                        requestBody.put("text", filled);
                         // 调用接口,传递Bearer token
                         String authorization = "Bearer " + accessToken;
                         JsonNode response = alienAIFeign.generatePromotionImage(authorization, requestBody);

+ 58 - 0
alien-store/src/main/java/shop/alien/store/controller/AiUploadController.java

@@ -0,0 +1,58 @@
+package shop.alien.store.controller;
+
+import com.alibaba.fastjson2.JSONObject;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiOperationSupport;
+import io.swagger.annotations.ApiSort;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+import shop.alien.entity.result.R;
+import shop.alien.store.util.ai.AiFeedbackAssignUtils;
+
+@Slf4j
+@Api(tags = {"ai语音识别"})
+@ApiSort(1)
+@CrossOrigin
+@RestController
+@RequestMapping("/aiUp")
+@RequiredArgsConstructor
+public class AiUploadController {
+    private final AiFeedbackAssignUtils aiFeedbackAssignUtils;
+    @ApiOperation("AI语音识别")
+    @ApiOperationSupport(order = 1)
+    @PostMapping("/GetAIUpload")
+    public R<JSONObject> getAiUpload(MultipartFile file, String language, String use_itn, String merge_vad ) {
+        // 检查文件是否为空
+        if (file == null || file.isEmpty()) {
+            return R.fail("音频文件不能为空");
+        }
+        
+        String accessToken = aiFeedbackAssignUtils.getAccessToken();
+        if (!StringUtils.hasText(accessToken)) {
+          return  R.fail("调用AI语音识别接口 登录接口失败");
+        }
+        
+        try {
+            String speechRecognitionResult = aiFeedbackAssignUtils.getSpeechRecognition1(file, accessToken, language, use_itn, merge_vad);
+            if (speechRecognitionResult == null) {
+                return R.fail("语音识别失败");
+            }
+            // 将返回的JSON字符串解析为JSONObject
+            JSONObject result = JSONObject.parseObject(speechRecognitionResult);
+            return R.data(result);
+        } catch (RuntimeException e) {
+            log.error("语音识别失败", e);
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("语音识别异常", e);
+            return R.fail("语音识别失败: " + e.getMessage());
+        }
+    }
+}

+ 35 - 0
alien-store/src/main/java/shop/alien/store/feign/AlienAIFeign.java

@@ -0,0 +1,35 @@
+package shop.alien.store.feign;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.http.MediaType;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.bind.annotation.*;
+
+@FeignClient(url = "${feign.alienAI.url}", name = "alien-AI")
+public interface AlienAIFeign {
+
+    /**
+     * 登录接口 - 获取access_token
+     *
+     *
+     *
+     * @return JsonNode 响应体,包含access_token
+     */
+    @PostMapping(value = "/ai/user-auth-core/api/v1/auth/login",
+            consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE)
+    JsonNode login(@RequestBody MultiValueMap<String, String> formData);
+
+    /**
+     * 使用 JsonNode 灵活调用接口
+     *
+     * @param authorization Bearer token,格式为 "Bearer {access_token}"
+     * @return JsonNode 响应体,可以灵活解析
+     */
+    @PostMapping(value = "/asr/upload",
+            consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
+            produces = MediaType.APPLICATION_JSON_VALUE)
+    JsonNode getSpeechRecognition (@RequestHeader("Authorization") String authorization, @RequestBody MultiValueMap<String, Object> formData);
+}
+

+ 14 - 22
alien-store/src/main/java/shop/alien/store/service/impl/LifeFeedbackServiceImpl.java

@@ -20,10 +20,8 @@ import shop.alien.entity.store.vo.FeedbackReplyVo;
 import shop.alien.mapper.LifeFeedbackMapper;
 import shop.alien.mapper.LifeLogMapper;
 import shop.alien.mapper.LifeNoticeMapper;
-import shop.alien.mapper.LifeUserMapper;
 import shop.alien.mapper.StoreUserMapper;
 import shop.alien.entity.store.LifeNotice;
-import shop.alien.entity.store.LifeUser;
 import shop.alien.entity.store.StoreUser;
 import shop.alien.entity.store.vo.WebSocketVo;
 import shop.alien.store.config.WebSocketProcess;
@@ -50,7 +48,6 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
     private final LifeLogMapper lifeLogMapper;
     private final LifeFeedbackReplyService lifeFeedbackReplyService;
     private final LifeNoticeMapper lifeNoticeMapper;
-    private final LifeUserMapper lifeUserMapper;
     private final StoreUserMapper storeUserMapper;
     private final WebSocketProcess webSocketProcess;
 
@@ -641,29 +638,24 @@ public class LifeFeedbackServiceImpl extends ServiceImpl<LifeFeedbackMapper, Lif
                 return;
             }
             
+            // userId对应store_user表的id,统一从store_user表查询
+            if (feedback.getUserId() == null) {
+                log.warn("用户ID为空,无法发送通知,feedbackId={}", feedback.getId());
+                return;
+            }
+            
+            StoreUser storeUser = storeUserMapper.selectById(feedback.getUserId());
+            if (storeUser == null || storeUser.getPhone() == null || storeUser.getPhone().trim().isEmpty()) {
+                log.warn("未找到商户用户信息或手机号为空,无法发送通知,userId={}", feedback.getUserId());
+                return;
+            }
+            
+            // 根据feedbackSource设置不同的接收者ID格式
             if (feedback.getFeedbackSource() == 0) {
                 // 用户端 - 使用user_手机号格式
-                if (feedback.getUserId() == null) {
-                    log.warn("用户ID为空,无法发送通知,feedbackId={}", feedback.getId());
-                    return;
-                }
-                LifeUser lifeUser = lifeUserMapper.selectById(feedback.getUserId());
-                if (lifeUser == null || lifeUser.getUserPhone() == null || lifeUser.getUserPhone().trim().isEmpty()) {
-                    log.warn("未找到用户信息或手机号为空,无法发送通知,userId={}", feedback.getUserId());
-                    return;
-                }
-                receiverId = "user_" + lifeUser.getUserPhone();
+                receiverId = "user_" + storeUser.getPhone();
             } else if (feedback.getFeedbackSource() == 1) {
                 // 商家端 - 使用store_手机号格式
-                if (feedback.getUserId() == null) {
-                    log.warn("商家用户ID为空,无法发送通知,feedbackId={}", feedback.getId());
-                    return;
-                }
-                StoreUser storeUser = storeUserMapper.selectById(feedback.getUserId());
-                if (storeUser == null || storeUser.getPhone() == null || storeUser.getPhone().trim().isEmpty()) {
-                    log.warn("未找到商家用户信息或手机号为空,无法发送通知,userId={}", feedback.getUserId());
-                    return;
-                }
                 receiverId = "store_" + storeUser.getPhone();
             } else {
                 log.warn("未知的反馈来源,feedbackSource={}, feedbackId={}", feedback.getFeedbackSource(), feedback.getId());

+ 171 - 2
alien-store/src/main/java/shop/alien/store/service/impl/StoreInfoServiceImpl.java

@@ -1036,7 +1036,7 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
             }
         }
 
-        // 计算并设置到期时间为四个过期时间的最小值
+        // 计算并设置到期时间为五个过期时间的最小值(包含身份证过期时间)
         List<Date> expirationTimeList = new ArrayList<>();
         // 收集所有非空的过期时间
         if (storeInfoDto.getExpirationTime() != null) {
@@ -1051,11 +1051,12 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         if (storeInfo.getBusinessLicenseExpirationTime() != null) {
             expirationTimeList.add(storeInfo.getBusinessLicenseExpirationTime());
         }
+        // 注意:身份证过期时间在saveIdCardImages方法中设置,这里先不包含,后续会在saveIdCardImages后更新
         // 取最小值设置为门店到期时间
         if (!expirationTimeList.isEmpty()) {
             Date minExpirationTime = Collections.min(expirationTimeList);
             storeInfo.setExpirationTime(minExpirationTime);
-            log.info("设置门店到期时间为四个过期时间的最小值:{}", minExpirationTime);
+            log.info("设置门店到期时间为过期时间的最小值:{}", minExpirationTime);
         }
 
         storeInfoMapper.insert(storeInfo);
@@ -1122,6 +1123,23 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
             storeImgMapper.insert(storeImg);
         }
 
+        //存入身份证正反面图片
+        saveIdCardImages(storeInfo.getId(), storeUser.getId().toString(), storeInfo);
+
+        // 更新门店到期时间,包含身份证过期时间
+        if (storeInfo.getIdCardExpirationTime() != null) {
+            List<Date> allExpirationTimeList = new ArrayList<>();
+            if (storeInfo.getExpirationTime() != null) {
+                allExpirationTimeList.add(storeInfo.getExpirationTime());
+            }
+            allExpirationTimeList.add(storeInfo.getIdCardExpirationTime());
+            Date minExpirationTime = Collections.min(allExpirationTimeList);
+            storeInfo.setExpirationTime(minExpirationTime);
+            // 更新数据库中的过期时间
+            storeInfoMapper.updateById(storeInfo);
+            log.info("更新门店到期时间,包含身份证过期时间,最小值:{}", minExpirationTime);
+        }
+
         //初始化标签数据
         LambdaQueryWrapper<TagStoreRelation> tagStoreRelationLambdaQueryWrapper = new LambdaQueryWrapper<>();
         tagStoreRelationLambdaQueryWrapper.eq(TagStoreRelation::getStoreId, storeInfo.getId());
@@ -4830,6 +4848,157 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         return parseDateString(endDateStr);
     }
 
+    /**
+     * 保存身份证正反面图片到store_img表,并解析OCR结果设置过期时间和状态
+     *
+     * @param storeId   门店ID
+     * @param storeUserId 店铺用户ID
+     * @param storeInfo 门店信息对象(用于设置身份证状态和过期时间)
+     */
+    private void saveIdCardImages(Integer storeId, String storeUserId, StoreInfo storeInfo) {
+        try {
+            // 查询身份证OCR识别记录(根据storeUserId和ocrType=ID_CARD)
+            List<OcrImageUpload> idCardOcrList = ocrImageUploadMapper.selectList(
+                    new LambdaQueryWrapper<OcrImageUpload>()
+                            .eq(OcrImageUpload::getStoreUserId, storeUserId)
+                            .eq(OcrImageUpload::getOcrType, OcrTypeEnum.ID_CARD.getCode())
+                            .orderByDesc(OcrImageUpload::getCreateTime)
+            );
+
+            if (CollectionUtils.isEmpty(idCardOcrList)) {
+                log.info("未找到身份证OCR记录,storeUserId={}", storeUserId);
+                // 没有OCR记录时,初始化状态为"未提交"(字典值0)
+                storeInfo.setIdCardStatus(0);
+                return;
+            }
+
+            String idCardFrontUrl = null;
+            String idCardBackUrl = null;
+            Date idCardExpirationTime = null;
+
+            // 遍历OCR记录,区分正面和反面
+            for (OcrImageUpload ocrRecord : idCardOcrList) {
+                if (StringUtils.isEmpty(ocrRecord.getOcrResult())) {
+                    continue;
+                }
+
+                try {
+                    // 解析OCR结果JSON
+                    com.alibaba.fastjson2.JSONObject ocrResultJson = com.alibaba.fastjson2.JSONObject.parseObject(ocrRecord.getOcrResult());
+
+                    // 检查是正面还是反面
+                    if (ocrResultJson.containsKey("face")) {
+                        // 身份证正面
+                        idCardFrontUrl = ocrRecord.getImageUrl();
+                        log.info("找到身份证正面图片,URL={}", idCardFrontUrl);
+                    } else if (ocrResultJson.containsKey("back")) {
+                        // 身份证反面
+                        idCardBackUrl = ocrRecord.getImageUrl();
+                        log.info("找到身份证反面图片,URL={}", idCardBackUrl);
+
+                        // 从反面OCR结果中提取有效期限
+                        com.alibaba.fastjson2.JSONObject backData = ocrResultJson.getJSONObject("back");
+                        if (backData != null) {
+                            com.alibaba.fastjson2.JSONObject data = backData.getJSONObject("data");
+                            if (data != null) {
+                                String validPeriod = data.getString("validPeriod");
+                                if (StringUtils.isNotEmpty(validPeriod)) {
+                                    idCardExpirationTime = parseIdCardExpirationDate(validPeriod);
+                                    if (idCardExpirationTime != null) {
+                                        log.info("解析身份证有效期限成功,过期时间:{}", idCardExpirationTime);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                } catch (Exception e) {
+                    log.error("解析身份证OCR结果失败,ocrRecordId={}", ocrRecord.getId(), e);
+                }
+            }
+
+            // 保存身份证正面图片到store_img表(img_type=33)
+            if (StringUtils.isNotEmpty(idCardFrontUrl)) {
+                StoreImg frontImg = new StoreImg();
+                frontImg.setStoreId(storeId);
+                frontImg.setImgType(33);
+                frontImg.setImgSort(1);
+                frontImg.setImgDescription("身份证正面");
+                frontImg.setImgUrl(idCardFrontUrl);
+                storeImgMapper.insert(frontImg);
+                log.info("保存身份证正面图片成功,storeId={}, imgUrl={}", storeId, idCardFrontUrl);
+            }
+
+            // 保存身份证反面图片到store_img表(img_type=34)
+            if (StringUtils.isNotEmpty(idCardBackUrl)) {
+                StoreImg backImg = new StoreImg();
+                backImg.setStoreId(storeId);
+                backImg.setImgType(34);
+                backImg.setImgSort(2);
+                backImg.setImgDescription("身份证反面");
+                backImg.setImgUrl(idCardBackUrl);
+                storeImgMapper.insert(backImg);
+                log.info("保存身份证反面图片成功,storeId={}, imgUrl={}", storeId, idCardBackUrl);
+            }
+
+            // 设置身份证状态和过期时间
+            if (StringUtils.isNotEmpty(idCardFrontUrl) || StringUtils.isNotEmpty(idCardBackUrl)) {
+                // 有身份证图片时,设置状态为"待审核"(字典值2,使用foodLicenceStatus字典)
+                storeInfo.setIdCardStatus(2);
+                storeInfo.setUpdateIdCardTime(new Date());
+                if (idCardExpirationTime != null) {
+                    storeInfo.setIdCardExpirationTime(idCardExpirationTime);
+                }
+                log.info("设置身份证状态为待审核,过期时间:{}", idCardExpirationTime);
+            } else {
+                // 没有身份证图片时,初始化状态为"未提交"(字典值0)
+                storeInfo.setIdCardStatus(0);
+            }
+
+        } catch (Exception e) {
+            log.error("保存身份证图片失败,storeId={}, storeUserId={}", storeId, storeUserId, e);
+            // 发生异常时,初始化状态为"未提交"(字典值0)
+            storeInfo.setIdCardStatus(0);
+        }
+    }
+
+    /**
+     * 解析身份证有效期限
+     * 身份证OCR返回格式:validPeriod="2023.05.29-2033.05.29"
+     *
+     * @param validPeriod 有效期限字符串,格式:"2023.05.29-2033.05.29"
+     * @return 到期日期,如果解析失败则返回null
+     */
+    private Date parseIdCardExpirationDate(String validPeriod) {
+        if (StringUtils.isEmpty(validPeriod)) {
+            return null;
+        }
+
+        // 处理"长期"或"永久"情况
+        if (validPeriod.contains("长期") || validPeriod.contains("永久")) {
+            return null;
+        }
+
+        try {
+            // 身份证格式:2023.05.29-2033.05.29,提取结束日期
+            if (validPeriod.contains("-")) {
+                String[] parts = validPeriod.split("-");
+                if (parts.length >= 2) {
+                    String endDateStr = parts[1].trim();
+                    // 将 "2023.05.29" 格式转换为 "2023-05-29"
+                    endDateStr = endDateStr.replace(".", "-");
+                    return parseDateString(endDateStr);
+                }
+            }
+
+            // 如果格式不匹配,尝试直接解析
+            String normalizedDateStr = validPeriod.replace(".", "-");
+            return parseDateString(normalizedDateStr);
+        } catch (Exception e) {
+            log.warn("解析身份证有效期限失败,validPeriod={}", validPeriod, e);
+            return null;
+        }
+    }
+
     @Override
     public StoreInfoVo getClientStoreDetail(String storeId, String userId, String jingdu, String weidu) {
         StoreInfoVo result = new StoreInfoVo();

+ 275 - 0
alien-store/src/main/java/shop/alien/store/util/ai/AiFeedbackAssignUtils.java

@@ -0,0 +1,275 @@
+package shop.alien.store.util.ai;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.http.*;
+import org.springframework.stereotype.Component;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.multipart.MultipartFile;
+import shop.alien.entity.store.LifeFeedback;
+import shop.alien.store.feign.AlienAIFeign;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class AiFeedbackAssignUtils {
+
+    private final RestTemplate restTemplate;
+
+    @Value("${ai.service.login-url}")
+    private String loginUrl;
+
+    @Value("${ai.service.username:UdUser}")
+    private String userName;
+
+    @Value("${ai.service.password:123456}")
+    private String passWord;
+
+    @Value("${ai.service.assign-staff-url}")
+    private String assignStaffUrl;
+
+    private  final AlienAIFeign alienAIFeign;
+
+    // 语音识别接口地址(从配置中读取,如果没有配置则使用默认值)
+    @Value("${feign.alienAI.url}")
+    private String aiServiceBaseUrl;
+
+    /**登录的语音识别文件登录
+     * 登录 AI 服务,获取 token
+     *
+     * @return accessToken
+     */
+    public String getSpeechRecognition1(MultipartFile file,String accessToken,String language,String use_itn,String merge_vad) {
+        log.info("调用AI语音识别接口...");
+        try {
+            // 使用 RestTemplate 直接发送 multipart/form-data 请求
+            MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
+            
+            // 添加音频文件
+            if (file != null && !file.isEmpty()) {
+                try {
+                    formData.add("file", new ByteArrayResource(file.getBytes()) {
+                        @Override
+                        public String getFilename() {
+                            String originalFilename = file.getOriginalFilename();
+                            return originalFilename != null ? originalFilename : "audio_file";
+                        }
+                    });
+                } catch (IOException e) {
+                    log.error("读取音频文件失败", e);
+                    throw new RuntimeException("读取音频文件失败", e);
+                }
+            } else {
+                log.warn("音频文件为空,无法进行语音识别");
+                throw new RuntimeException("音频文件不能为空");
+            }
+            
+            // 添加识别语言参数(默认 auto)
+            if (StringUtils.hasText(language)) {
+                formData.add("language", language);
+            } else {
+                formData.add("language", "auto");
+            }
+            
+            // 添加是否开启逆文本标准化参数(默认 true)
+            // 支持传入 "true"/"false" 字符串或 boolean 值
+            if (StringUtils.hasText(use_itn)) {
+                // 确保传递的是有效的 boolean 字符串值
+                String useItnValue = use_itn.trim().toLowerCase();
+                if ("true".equals(useItnValue) || "false".equals(useItnValue)) {
+                    formData.add("use_itn", useItnValue);
+                } else {
+                    formData.add("use_itn", "true"); // 默认值
+                }
+            } else {
+                formData.add("use_itn", "true"); // 默认值
+            }
+            
+            // 添加是否合并VAD参数(默认 true)
+            // 支持传入 "true"/"false" 字符串或 boolean 值
+            if (StringUtils.hasText(merge_vad)) {
+                // 确保传递的是有效的 boolean 字符串值
+                String mergeVadValue = merge_vad.trim().toLowerCase();
+                if ("true".equals(mergeVadValue) || "false".equals(mergeVadValue)) {
+                    formData.add("merge_vad", mergeVadValue);
+                } else {
+                    formData.add("merge_vad", "true"); // 默认值
+                }
+            } else {
+                formData.add("merge_vad", "true"); // 默认值
+            }
+
+            // 设置请求头
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.MULTIPART_FORM_DATA);
+            // 添加 Authorization header(如果需要)
+            if (StringUtils.hasText(accessToken)) {
+                headers.set("Authorization", "Bearer " + accessToken);
+            }
+
+            HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(formData, headers);
+
+            // 构建完整的URL
+            String url = aiServiceBaseUrl + "/asr/upload";
+            log.info("调用AI语音识别接口,URL: {}", url);
+
+            // 使用 RestTemplate 发送请求
+            ResponseEntity<String> response = restTemplate.postForEntity(url, requestEntity, String.class);
+            
+            // 处理响应
+            if (response != null && response.getStatusCode() == HttpStatus.OK) {
+                String responseBody = response.getBody();
+                log.info("AI语音识别接口响应:{}", responseBody);
+                return responseBody;
+            } else {
+                log.error("AI语音识别接口调用失败,HTTP状态码: {}", 
+                        response != null ? response.getStatusCode() : "null");
+                throw new RuntimeException("AI语音识别接口调用失败,HTTP状态码: " + 
+                        (response != null ? response.getStatusCode() : "null"));
+            }
+        } catch (org.springframework.web.client.HttpClientErrorException e) {
+            log.error("调用AI语音识别接口失败,HTTP错误: {}, 响应体: {}", 
+                    e.getStatusCode(), e.getResponseBodyAsString(), e);
+            throw new RuntimeException("调用AI语音识别接口失败: " + e.getStatusCode() + 
+                    ", 响应: " + e.getResponseBodyAsString(), e);
+        } catch (Exception e) {
+            log.error("调用AI语音识别接口失败", e);
+            throw new RuntimeException("调用AI语音识别接口失败: " + e.getMessage(), e);
+        }
+    }
+
+    public String getAccessToken() {
+        log.info("登录AI服务获取token...{}", loginUrl);
+        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
+        formData.add("username","admin");
+        formData.add("password", "123456");
+
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
+
+        ResponseEntity<String> response;
+        try {
+            log.info("请求AI服务登录接口===================>");
+            response = restTemplate.postForEntity(loginUrl, requestEntity, String.class);
+        } catch (Exception e) {
+            log.error("请求AI服务登录接口失败", e);
+            return null;
+        }
+
+        if (response != null && response.getStatusCode() == HttpStatus.OK) {
+            String body = response.getBody();
+            log.info("请求AI服务登录成功 postForEntity.getBody()\t{}", body);
+            if (StringUtils.hasText(body)) {
+                JSONObject jsonObject = JSONObject.parseObject(body);
+                if (jsonObject != null) {
+                    JSONObject dataJson = jsonObject.getJSONObject("data");
+                    if (dataJson != null) {
+                        return dataJson.getString("access_token");
+                    }
+                }
+            }
+            log.warn("AI服务登录响应解析失败 body: {}", body);
+            return null;
+        }
+
+        log.error("请求AI服务登录接口失败 http状态:{}", response != null ? response.getStatusCode() : null);
+        return null;
+    }
+
+    /**
+     * 调用AI接口分配跟踪人员
+     *
+     * @param feedback 反馈记录
+     * @param imageUrls 图片URL列表
+     * @param videoUrls 视频URL列表
+     * @return 分配的跟踪人员ID,失败返回null
+     */
+    public Integer assignStaffByAI(LifeFeedback feedback, List<String> imageUrls, List<String> videoUrls) {
+        try {
+            // 1. 获取访问令牌
+            String accessToken = getAccessToken();
+            if (accessToken == null) {
+                log.error("获取AI访问令牌失败,无法分配跟踪人员");
+                return null;
+            }
+
+            // 2. 构建请求参数
+            Map<String, Object> requestBody = new HashMap<>();
+            requestBody.put("feedback_id", feedback.getId());
+            requestBody.put("feedback_content", feedback.getContent());
+            requestBody.put("feedback_type", feedback.getFeedbackType());
+            requestBody.put("feedback_source", feedback.getFeedbackSource());
+            requestBody.put("feedback_way", feedback.getFeedbackWay());
+            requestBody.put("contact_way", feedback.getContactWay());
+            requestBody.put("user_id", feedback.getUserId());
+            if (imageUrls != null && !imageUrls.isEmpty()) {
+                requestBody.put("image_urls", imageUrls);
+            }
+            if (videoUrls != null && !videoUrls.isEmpty()) {
+                requestBody.put("video_urls", videoUrls);
+            }
+
+            // 3. 设置请求头
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.APPLICATION_JSON);
+            headers.set("Authorization", "Bearer " + accessToken);
+
+            HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(requestBody, headers);
+
+            // 4. 调用AI接口
+            log.info("调用AI分配跟踪人员接口,feedbackId={}, url={}", feedback.getId(), assignStaffUrl);
+            ResponseEntity<String> response = restTemplate.postForEntity(assignStaffUrl, requestEntity, String.class);
+
+            // 5. 处理响应
+            if (response != null && response.getStatusCodeValue() == 200) {
+                String responseBody = response.getBody();
+                log.info("AI分配跟踪人员接口响应:{}", responseBody);
+
+                if (StringUtils.hasText(responseBody)) {
+                    JSONObject responseJson = JSONObject.parseObject(responseBody);
+                    Integer code = responseJson.getInteger("code");
+                    
+                    if (code != null && code == 200) {
+                        JSONObject data = responseJson.getJSONObject("data");
+                        if (data != null) {
+                            Integer staffId = data.getInteger("staff_id");
+                            if (staffId != null) {
+                                log.info("AI分配跟踪人员成功,feedbackId={}, staffId={}, staffName={}", 
+                                        feedback.getId(), staffId, data.getString("staff_name"));
+                                return staffId;
+                            }
+                        }
+                    } else {
+                        String message = responseJson.getString("message");
+                        log.error("AI分配跟踪人员失败,feedbackId={}, code={}, message={}", 
+                                feedback.getId(), code, message);
+                    }
+                }
+            } else {
+                log.error("AI分配跟踪人员接口调用失败,feedbackId={}, http状态={}", 
+                        feedback.getId(), response != null ? response.getStatusCode() : null);
+            }
+
+            return null;
+        } catch (org.springframework.web.client.HttpServerErrorException.ServiceUnavailable e) {
+            log.error("AI分配跟踪人员接口返回503 Service Unavailable错误: {}", e.getResponseBodyAsString());
+            return null;
+        } catch (Exception e) {
+            log.error("调用AI分配跟踪人员接口异常,feedbackId={}", feedback.getId(), e);
+            return null;
+        }
+    }
+}