Bläddra i källkod

Merge branch 'sit' into uat-20260202

dujian 1 månad sedan
förälder
incheckning
ac2afb69d8

+ 60 - 0
SSE前后端使用说明书.md

@@ -140,6 +140,66 @@ sseService.pushCartUpdate(tableId, new CartDTO());
 
 ---
 
+## 线上环境部署说明(SSE 必读)
+
+项目发到线上时,SSE 长连接需要以下配置,否则容易出现连接被提前断开、收不到推送等问题。
+
+### 1. 网关(Spring Cloud Gateway)
+
+- **问题**:网关对后端响应有默认或全局 `response-timeout`,会主动断开长时间无“完成”的响应,导致 SSE 长连接被踢掉。
+- **做法**:对 SSE 路径**单独配一条路由**,并关闭该路由的响应超时(`response-timeout: -1`),且该路由需放在点餐通用路由**之前**,保证先匹配 SSE。
+- **配置示例**(加入 Nacos 中 `alien-gateway` 的网关路由配置,如 `alien-gateway.yml`):
+
+```yaml
+# 此条 SSE 路由必须放在 aliendining 通用路由前面
+- id: aliendining-sse
+  uri: http://${route_or_local_ip}:30014   # 与现有 aliendining 保持一致
+  predicates:
+    - Path=/aliendining/store/order/sse/**
+  filters:
+    - StripPrefix=1
+  metadata:
+    response-timeout: -1   # -1 表示不超时,避免 SSE 被网关提前断开
+```
+
+- 若使用 **lb 负载均衡**(如 `lb://alien-dining`),同样给上述 SSE 路由加上 `metadata.response-timeout: -1` 即可。
+
+### 2. Nginx / 反向代理(若存在)
+
+若网关前还有 Nginx(或其它反向代理),需避免对 SSE 做缓冲并拉长超时:
+
+- **关闭对 event-stream 的缓冲**:  
+  `proxy_buffering off;`(或对 `location ~ /aliendining/store/order/sse` 单独关闭)
+- **拉长读超时**:  
+  `proxy_read_timeout` 建议 ≥ 30 分钟(如 `1800s`),或略大于业务侧 SSE 超时(当前为 30 分钟)。
+- **可选**:  
+  `proxy_connect_timeout`、`proxy_send_timeout` 也可适当调大,避免代理层先断连。
+
+### 3. 多实例部署(alien-dining 多节点)
+
+- 当前 SSE 连接是**按实例内存**维护的(每个实例一份 `ConcurrentHashMap`)。同一桌号若被负载均衡到不同实例,只有“写操作发生的那台实例”上的 SSE 连接会收到推送,其它实例上的同桌连接收不到。
+- **建议**:
+  - **单实例**:无需改代码,按上面 1、2 配置即可。
+  - **多实例**:要么对该 SSE 路径做**会话保持**(同一桌号固定到同一实例),要么后续改造为基于 **Redis 等中间件** 的跨实例广播(需改 `SseServiceImpl` 与 Nacos/配置)。
+
+### 4. 前端线上地址
+
+- 线上建立 SSE 时,请使用**经过网关的完整路径**,例如:  
+  `https://你的域名/aliendining/store/order/sse/{tableId}`  
+  具体以你们网关的 `Path` 与 `StripPrefix` 为准,保证最终能路由到 `alien-dining` 的 `/store/order/sse/{tableId}`。
+- 若网关或 Nacos 中给点餐服务配了统一前缀(如 `/api`),则 SSE 地址中也要带上该前缀。
+
+### 5. 小结
+
+| 环境         | 必做项 |
+|--------------|--------|
+| 网关         | 为 SSE 路径单独路由并设置 `response-timeout: -1`,且路由顺序优先 |
+| Nginx/反向代理 | `proxy_buffering off`,`proxy_read_timeout` ≥ 30 分钟 |
+| 多实例       | 会话保持或改为 Redis 等跨实例推送 |
+| 前端         | 使用经网关的完整 SSE URL |
+
+---
+
 ## 三、前端实现说明
 
 ### 3.1 基础使用(原生JavaScript)

+ 30 - 1
alien-dining/doc/gateway-route-example.yml

@@ -1,11 +1,25 @@
 # 网关路由配置示例
 # 将此配置添加到 Nacos 的 alien-gateway.yml 配置文件中
+#
+# 若前端调用 /sse/{tableId} 出现 504 Gateway Timeout:
+# 原因:SSE 是长连接,网关默认响应超时(约 30s)会主动断开。
+# 解决:必须为 SSE 路径单独配一条路由,且 metadata.response-timeout: -1(见下)。
 
 spring:
   cloud:
     gateway:
       routes:
-        # 微信点餐模块路由配置
+        # 【重要】SSE 长连接路由:必须放在 aliendining 通用路由之前,并关闭响应超时
+        - id: aliendining-sse
+          uri: http://${route_or_local_ip}:30014
+          predicates:
+            - Path=/aliendining/store/order/sse/**
+          filters:
+            - StripPrefix=1
+          metadata:
+            response-timeout: -1   # -1 表示不超时,避免 SSE 长连接被网关提前断开
+
+        # 微信点餐模块通用路由
         - id: aliendining
           uri: http://${route_or_local_ip}:30014
           predicates:
@@ -13,3 +27,18 @@ spring:
           filters:
             - StripPrefix=1
 
+# ---------------------------------------------------------------------------
+# 网关白名单(免登录)配置
+# 在 Nacos 的 alien-gateway 使用的配置中增加 jwt.skip-auth-urls,以下路径不校验 Token。
+#
+# 示例:根据商铺ID查询店铺信息和首页展示美食价目表 GET /store/info/detail/{storeId}
+# - 经网关访问路径为:/aliendining/store/info/detail/{storeId}(以实际网关 Path 前缀为准)
+# - 支持前缀匹配:配置项以 ** 结尾表示该路径及其子路径均放行
+#
+# jwt:
+#   skip-auth-urls:
+#     - /aliendining/store/info/detail/**    # 店铺详情+首页价目表,无需登录
+#     - /aliendining/store/info/tables        # 精确匹配示例
+#     - /other/exact/path
+# ---------------------------------------------------------------------------
+

+ 31 - 3
alien-dining/src/main/java/shop/alien/dining/service/impl/SseServiceImpl.java

@@ -52,7 +52,11 @@ public class SseServiceImpl implements shop.alien.dining.service.SseService {
         });
 
         emitter.onError((ex) -> {
-            log.error("SSE连接错误, tableId={}, connectionId={}, error={}", tableId, connectionId, ex.getMessage(), ex);
+            if (isClientDisconnect(ex)) {
+                log.info("SSE客户端已断开, tableId={}, connectionId={}", tableId, connectionId);
+            } else {
+                log.error("SSE连接错误, tableId={}, connectionId={}, error={}", tableId, connectionId, ex.getMessage(), ex);
+            }
             removeConnection(tableId, connectionId);
         });
 
@@ -90,7 +94,11 @@ public class SseServiceImpl implements shop.alien.dining.service.SseService {
                         .data(messageJson));
                 log.info("推送购物车更新成功, tableId={}, connectionId={}", tableId, connectionId);
             } catch (IOException e) {
-                log.error("推送购物车更新失败, tableId={}, connectionId={}, error={}", tableId, connectionId, e.getMessage(), e);
+                if (isClientDisconnect(e)) {
+                    log.debug("推送时客户端已断开, tableId={}, connectionId={}", tableId, connectionId);
+                } else {
+                    log.error("推送购物车更新失败, tableId={}, connectionId={}, error={}", tableId, connectionId, e.getMessage(), e);
+                }
                 removeConnection(tableId, connectionId);
             }
         });
@@ -113,6 +121,22 @@ public class SseServiceImpl implements shop.alien.dining.service.SseService {
     }
 
     /**
+     * 判断是否为客户端主动断开(Broken pipe、Connection reset 等),此类情况属正常,无需打 ERROR。
+     */
+    private boolean isClientDisconnect(Throwable ex) {
+        if (ex == null) return false;
+        String msg = ex.getMessage();
+        if (msg != null) {
+            String lower = msg.toLowerCase();
+            if (lower.contains("broken pipe") || lower.contains("connection reset")
+                    || lower.contains("connection closed") || lower.contains("an established connection was aborted")) {
+                return true;
+            }
+        }
+        return isClientDisconnect(ex.getCause());
+    }
+
+    /**
      * 移除连接
      */
     private void removeConnection(Integer tableId, String connectionId) {
@@ -144,7 +168,11 @@ public class SseServiceImpl implements shop.alien.dining.service.SseService {
                             .data("ping"));
                 }
             } catch (IOException e) {
-                log.error("发送心跳失败, tableId={}, connectionId={}", tableId, connectionId, e);
+                if (isClientDisconnect(e)) {
+                    log.debug("心跳时客户端已断开, tableId={}, connectionId={}", tableId, connectionId);
+                } else {
+                    log.error("发送心跳失败, tableId={}, connectionId={}", tableId, connectionId, e);
+                }
                 removeConnection(tableId, connectionId);
             }
         }, 30, 30, TimeUnit.SECONDS); // 每30秒发送一次心跳

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

@@ -86,7 +86,7 @@ public class StoreOperationalActivity {
 
     @ApiModelProperty(value = "创建时间")
     @TableField(value = "created_time", fill = FieldFill.INSERT)
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.S", timezone = "GMT+8")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date createdTime;
 
     @ApiModelProperty(value = "创建人ID")

+ 5 - 0
alien-entity/src/main/java/shop/alien/mapper/storePlantform/StoreOperationalActivitySignupMapper.java

@@ -1,6 +1,9 @@
 package shop.alien.mapper.storePlantform;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 import shop.alien.entity.storePlatform.StoreOperationalActivitySignup;
@@ -88,4 +91,6 @@ public interface StoreOperationalActivitySignupMapper extends BaseMapper<StoreOp
      * @return 报名列表
      */
     List<StoreOperationalActivityMySignupVo> selectMySignups(@Param("userId") Integer userId);
+
+    IPage<StoreOperationalActivitySignup> selectAll(IPage<StoreOperationalActivitySignup> page, @Param(Constants.WRAPPER) LambdaQueryWrapper<StoreOperationalActivitySignup> wrapper);
 }

+ 4 - 0
alien-entity/src/main/resources/mapper/storePlatform/StoreOperationalActivitySignupMapper.xml

@@ -132,4 +132,8 @@
           AND a.delete_flag = 0
         ORDER BY s.signup_time DESC
     </select>
+
+    <select id="selectAll" resultType="shop.alien.entity.storePlatform.StoreOperationalActivitySignup">
+        select * from store_operational_activity_signup ${ew.customSqlSegment}
+    </select>
 </mapper>

+ 45 - 23
alien-gateway/src/main/java/shop/alien/gateway/config/JwtTokenFilter.java

@@ -81,8 +81,8 @@ public class JwtTokenFilter implements GlobalFilter, Ordered {
         if (Objects.equals(exchange.getRequest().getMethod(), HttpMethod.OPTIONS)) {
             return allowChain(exchange, chain);
         }
-        //跳过不需要验证的路径
-        if (null != skipAuthUrls && Arrays.asList(skipAuthUrls).contains(url)) {
+        // 跳过不需要验证的路径(支持精确匹配与前缀匹配:配置项以 ** 结尾表示前缀白名单)
+        if (isSkipAuthUrl(url)) {
             return allowChain(exchange, chain);
         }
         if (null != skipAuthUrls && url.contains("/alienStore/socket/")) {
@@ -146,7 +146,7 @@ public class JwtTokenFilter implements GlobalFilter, Ordered {
                     if ("store".equals(deviceType) || "storePlatform".equals(deviceType)) {
                         //判断程序是否为用户禁用
                         StoreUser storeUser = storeUserMapper.selectOne(new LambdaQueryWrapper<StoreUser>().eq(StoreUser::getPhone, phone));
-                        if (storeUser.getStatus() == 1) {
+                        if (storeUser != null && storeUser.getStatus() == 1) {
                             map.put("msg", "你的账号已被禁用");
                             //别问, 问就是约定俗成
                             map.put("code", 777);
@@ -214,28 +214,50 @@ public class JwtTokenFilter implements GlobalFilter, Ordered {
         }
     }
 
+    /**
+     * 判断请求路径是否在白名单内(免登录)。
+     * 配置项以 "**" 结尾时做前缀匹配,否则做精确匹配。
+     * 例如:/aliendining/store/info/detail/** 可放行 /aliendining/store/info/detail/1、/aliendining/store/info/detail/123 等。
+     */
+    private boolean isSkipAuthUrl(String requestPath) {
+        if (skipAuthUrls == null || skipAuthUrls.length == 0) {
+            return false;
+        }
+        for (String skip : skipAuthUrls) {
+            if (skip == null) continue;
+            String trimmed = skip.trim();
+            if (trimmed.endsWith("**")) {
+                String prefix = trimmed.substring(0, trimmed.length() - 2);
+                if (requestPath.startsWith(prefix)) {
+                    return true;
+                }
+            } else if (requestPath.equals(trimmed)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private Mono<Void> allowChain(ServerWebExchange exchange, GatewayFilterChain chain) {
         return chain.filter(exchange).then(Mono.fromRunnable(() -> {
-            exchange.getResponse().getHeaders().entrySet().stream()
-                    .filter(kv -> (kv.getValue() != null && kv.getValue().size() > 1))
-                    .filter(kv -> (kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)
-                            || kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)
-                            || kv.getKey().equals(HttpHeaders.VARY)))
-                    .forEach(kv -> {
-                        // Vary只需要去重即可
-                        if (kv.getKey().equals(HttpHeaders.VARY))
-                            kv.setValue(kv.getValue().stream().distinct().collect(Collectors.toList()));
-                        else {
-                            List<String> value = new ArrayList<>();
-                            if (kv.getValue().contains(ANY)) {  //如果包含*,则取*
-                                value.add(ANY);
-                                kv.setValue(value);
-                            } else {
-                                value.add(kv.getValue().get(0)); // 否则默认取第一个
-                                kv.setValue(value);
-                            }
-                        }
-                    });
+            try {
+                HttpHeaders headers = exchange.getResponse().getHeaders();
+                for (String name : Arrays.asList(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, HttpHeaders.VARY)) {
+                    List<String> values = headers.get(name);
+                    if (values == null || values.size() <= 1) continue;
+                    List<String> newValue;
+                    if (HttpHeaders.VARY.equals(name)) {
+                        newValue = values.stream().distinct().collect(Collectors.toList());
+                    } else {
+                        newValue = new ArrayList<>();
+                        newValue.add(values.contains(ANY) ? ANY : values.get(0));
+                    }
+                    headers.put(name, newValue);
+                }
+            } catch (Exception e) {
+                // 响应已提交或 headers 不可写时忽略,避免 UnsupportedOperationException 等打断链路
+                log.debug("allowChain 整理 CORS 头失败(可能响应已提交): {}", e.getMessage());
+            }
         }));
     }
 

+ 1 - 2
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/OperationalActivitySignupServiceImpl.java

@@ -82,8 +82,7 @@ public class OperationalActivitySignupServiceImpl implements OperationalActivity
 
         // 分页查询
         IPage<StoreOperationalActivitySignup> page = new Page<>(pageNum, pageSize);
-        IPage<StoreOperationalActivitySignup> signupPage = signupMapper.selectPage(page, wrapper);
-
+        IPage<StoreOperationalActivitySignup> signupPage = signupMapper.selectAll(page, wrapper);
         // 转换为VO
         IPage<StoreOperationalActivitySignupVO> voPage = new Page<>(pageNum, pageSize);
         voPage.setTotal(signupPage.getTotal());

+ 5 - 10
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StoreManageServiceImpl.java

@@ -456,22 +456,17 @@ public class StoreManageServiceImpl implements StoreManageService {
             }
         }
         
-        // 处理空集合字段,设置为 null
-        if (StringUtils.isNotEmpty(storeInfoDraft.getBusinessLicenseUrl()) 
-                && storeInfoDraft.getBusinessLicenseUrl().isEmpty()) {
+        // 将空字符串规范为 null,避免存空串
+        if (storeInfoDraft.getBusinessLicenseUrl() != null && storeInfoDraft.getBusinessLicenseUrl().trim().isEmpty()) {
             storeInfoDraft.setBusinessLicenseUrl(null);
         }
-        
-        if (StringUtils.isNotEmpty(storeInfoDraft.getContractUrl()) 
-                && storeInfoDraft.getContractUrl().isEmpty()) {
+        if (storeInfoDraft.getContractUrl() != null && storeInfoDraft.getContractUrl().trim().isEmpty()) {
             storeInfoDraft.setContractUrl(null);
         }
-        
-        if (StringUtils.isNotEmpty(storeInfoDraft.getFoodLicenceUrl()) 
-                && storeInfoDraft.getFoodLicenceUrl().isEmpty()) {
+        if (storeInfoDraft.getFoodLicenceUrl() != null && storeInfoDraft.getFoodLicenceUrl().trim().isEmpty()) {
             storeInfoDraft.setFoodLicenceUrl(null);
         }
-        
+
         int result = storeInfoDraftMapper.insert(storeInfoDraft);
         log.info("StoreManageServiceImpl.saveStoreDraft - 草稿保存完成,影响行数: {}", result);
         return result;

+ 7 - 8
alien-store/src/main/java/shop/alien/store/controller/AiSearchController.java

@@ -21,17 +21,11 @@ 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.CommonRating;
-import shop.alien.entity.store.LifeBlacklist;
-import shop.alien.entity.store.StoreImg;
-import shop.alien.entity.store.StoreUser;
+import shop.alien.entity.store.*;
 import shop.alien.entity.store.vo.StoreBannerVo;
 import shop.alien.entity.store.vo.StoreBusinessStatusVo;
 import shop.alien.entity.store.vo.StoreInfoVo;
-import shop.alien.mapper.CommonRatingMapper;
-import shop.alien.mapper.LifeBlacklistMapper;
-import shop.alien.mapper.StoreImgMapper;
-import shop.alien.mapper.StoreUserMapper;
+import shop.alien.mapper.*;
 import shop.alien.store.annotation.TrackEvent;
 import shop.alien.store.service.CommonRatingService;
 import shop.alien.store.service.StoreBannerService;
@@ -58,6 +52,7 @@ public class AiSearchController {
 
     private final StoreImgMapper storeImgMapper;
     private final StoreUserMapper storeUserMapper;
+    private final LifeUserMapper lifeUserMapper;
 
     @Value("${third-party-ai-search.exact.base-url:http://124.93.18.180:7870/api/v1/search}")
     private String aiSearchExactUrl;
@@ -99,6 +94,10 @@ public class AiSearchController {
         } else {
             requestBody.put("sort_order", "desc");
         }
+        LifeUser lifeUser = lifeUserMapper.selectById(map.get("userId"));
+        if(lifeUser!=null){
+            requestBody.put("fans_id","user_"+lifeUser.getUserPhone());
+        }
         HttpHeaders aiHeaders = new HttpHeaders();
         String accessToken = aiAuthTokenUtil.getAccessToken();
         aiHeaders.setContentType(MediaType.APPLICATION_JSON);

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

@@ -14,6 +14,7 @@ import shop.alien.store.service.StoreImgService;
 import shop.alien.store.service.StoreInfoService;
 import shop.alien.store.service.StoreOfficialAlbumService;
 import shop.alien.store.util.GroupConstant;
+import shop.alien.store.util.ai.AiContentModerationUtil;
 
 import java.util.List;
 import java.util.stream.Collectors;
@@ -35,6 +36,8 @@ public class StoreImgController {
     private final StoreImgService storeImgService;
     private final StoreInfoService storeInfoService;
     private final StoreOfficialAlbumService storeOfficialAlbumService;
+    private final AiContentModerationUtil aiContentModerationUtil;
+
     @ApiOperation("获取图片")
     @ApiOperationSupport(order = 1)
     @ApiImplicitParams({@ApiImplicitParam(name = "storeId", value = "门店id", dataType = "Integer", paramType = "query", required = true),
@@ -81,6 +84,14 @@ public class StoreImgController {
         if(storeImgInfoVo.getStoreImgList().isEmpty()){
             return R.fail("图片列表为空,请重新上传图片");
         }
+        // TODO 审核图片是否违规
+        List<String> imageUrls = storeImgList.stream()
+                .map(StoreImg::getImgUrl)
+                .collect(Collectors.toList());
+        AiContentModerationUtil.AuditResult auditResult = aiContentModerationUtil.auditContent(null, imageUrls);
+        if (!auditResult.isPassed()) {
+            return R.fail(auditResult.getFailureReason()); // 文本内容异常(包含敏感词)
+        }
         // 判断是否是头图(20:单图模式, 21:多图模式)
         Integer imgType = storeImgInfoVo.getImgType();
         boolean isHeadImage = (imgType == 20 || imgType == 21);

+ 7 - 4
alien-store/src/main/java/shop/alien/store/service/LifeUserDynamicsService.java

@@ -14,9 +14,10 @@ import org.springframework.util.CollectionUtils;
 import org.springframework.util.ObjectUtils;
 import org.springframework.util.StringUtils;
 import shop.alien.entity.store.*;
-import shop.alien.entity.store.vo.*;
+import shop.alien.entity.store.vo.CommonCommentVo;
+import shop.alien.entity.store.vo.LifePinglunVo;
+import shop.alien.entity.store.vo.LifeUserDynamicsVo;
 import shop.alien.mapper.*;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import shop.alien.util.common.constant.CommentSourceTypeEnum;
 
 import java.util.*;
@@ -574,6 +575,7 @@ public class LifeUserDynamicsService extends ServiceImpl<LifeUserDynamicsMapper,
                 if (storeUser.getStoreId() != null) {
                     StoreInfo storeInfo = storeInfoMapper.selectById(storeUser.getStoreId());
                     resultMap.put("businessSection",storeInfo.getBusinessSection());
+                    resultMap.put("scoreAvg",storeInfo.getScoreAvg());
                     if (storeInfo != null && storeInfo.getStoreName() != null) {
                         // 使用店铺名称作为昵称
                         storeUser.setUserName(storeInfo.getStoreName());
@@ -646,8 +648,9 @@ public class LifeUserDynamicsService extends ServiceImpl<LifeUserDynamicsMapper,
                 dynamicsVo.setIsLike("0");
             }
             // 根据store_comment 表 business_id 查询评论数量
-            int commentCount = storeCommentService.count(new QueryWrapper<StoreComment>().eq("business_id", dynamicsVo.getId()).eq("delete_flag", "0").eq("comment_type", "1"));
-            dynamicsVo.setCommentCount(commentCount);
+//            int commentCount = storeCommentService.count(new QueryWrapper<StoreComment>().eq("business_id", dynamicsVo.getId()).eq("delete_flag", "0").eq("comment_type", "1"));
+            int commonCommentCount = commonCommentMapper.selectCount(new QueryWrapper<CommonComment>().eq("source_id", dynamicsVo.getId()).eq("delete_flag", "0").eq("source_type", CommentSourceTypeEnum.DYNAMIC_COMMENT.getType()));
+            dynamicsVo.setCommentCount(commonCommentCount);
             String phoneId1 = dynamicsVo.getPhoneId();
             String storeUserId = "";
             if (phoneId1.startsWith("user_")) {

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

@@ -791,8 +791,8 @@ public class LawyerConsultationOrderServiceImpl extends ServiceImpl<LawyerConsul
                 fillLawyerInfo(orderVO, lawyerUser);
             }
 
-
-            
+            Integer serviceCount =consultationOrderMapper.selectCount(new QueryWrapper<LawyerConsultationOrder>().eq("lawyer_user_id", lawyerUserId).eq("order_status", 3).eq("delete_flag", 0));
+            orderVO.setServiceCount(serviceCount);
             // 查询律师问题场景
             List<Integer> lawyerIdList = Collections.singletonList(lawyerUserId);
             Map<String, List<String>> serviceAreaMap = getLawyerServiceArea(lawyerIdList);