Преглед на файлове

中台子账号关联页面bug修复 商家端优惠券代码增加

wuchen преди 2 месеца
родител
ревизия
290090343f

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

@@ -15,7 +15,7 @@ public class StoreSubExcelVo {
     @ApiModelProperty(value = "序号")
     @ApiModelProperty(value = "序号")
     private Integer serialNumber;
     private Integer serialNumber;
 
 
-    @ApiModelProperty(value = "主键")
+    @ApiModelProperty(value = "账号ID")
     @ExcelHeader(value = "账号ID")
     @ExcelHeader(value = "账号ID")
     private Integer id;
     private Integer id;
 
 

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

@@ -70,11 +70,15 @@ public class CommonRatingController {
 //        return R.data(commonRatingService.getById(id));
 //        return R.data(commonRatingService.getById(id));
 //    }
 //    }
 //
 //
-    @ApiOperation(value = "新增评价", notes = "0:成功, 1:失败, 2:文本内容异常, 3:图片内容异常")
+    @ApiOperation(value = "新增评价", notes = "0:成功, 1:失败, 2:文本内容异常, 3:图片内容异常。请求体需包含 businessType、business_id(店铺ID) 等必填字段")
     @PostMapping("/addRating")
     @PostMapping("/addRating")
     public R<Integer> add(@RequestBody CommonRating commonRating) {
     public R<Integer> add(@RequestBody CommonRating commonRating) {
         log.info("CommonRatingController.add?commonRating={}", commonRating);
         log.info("CommonRatingController.add?commonRating={}", commonRating);
-        return R.data(commonRatingService.saveCommonRating(commonRating));
+        try {
+            return R.data(commonRatingService.saveCommonRating(commonRating));
+        } catch (IllegalArgumentException e) {
+            return R.fail(e.getMessage());
+        }
     }
     }
 
 
     @ApiOperation("获取评价详情,和所有回复")
     @ApiOperation("获取评价详情,和所有回复")

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

@@ -132,6 +132,7 @@ public class LifeDiscountCouponStoreFriendController {
         return R.data(lifeDiscountCouponFriendRuleVo);
         return R.data(lifeDiscountCouponFriendRuleVo);
     }
     }
 
 
+
     @ApiOperation("删除赠券规则")
     @ApiOperation("删除赠券规则")
     @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "id", dataType = "String", paramType = "query", required = true)
     @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "id", dataType = "String", paramType = "query", required = true)
     })
     })

+ 9 - 0
alien-store/src/main/java/shop/alien/store/service/LifeDiscountCouponStoreFriendService.java

@@ -60,4 +60,13 @@ public interface LifeDiscountCouponStoreFriendService extends IService<LifeDisco
     LifeDiscountCouponFriendRuleVo getRuleById(String id);
     LifeDiscountCouponFriendRuleVo getRuleById(String id);
 
 
     List<LifeDiscountCouponFriendRuleVo> getReceivedSendFriendCouponList(String storeUserId, String friendStoreUserId,String storeName);
     List<LifeDiscountCouponFriendRuleVo> getReceivedSendFriendCouponList(String storeUserId, String friendStoreUserId,String storeName);
+
+    /**
+     * 好评送券:用户对店铺好评且通过AI审核后,将店铺可用券发放到用户卡包
+     *
+     * @param userId  评价用户ID(life用户)
+     * @param storeId 店铺ID(businessId)
+     * @return 发放的优惠券数量,0表示未发放
+     */
+    int issueCouponForGoodRating(Integer userId, Integer storeId);
 }
 }

+ 18 - 1
alien-store/src/main/java/shop/alien/store/service/impl/CommonRatingServiceImpl.java

@@ -26,6 +26,7 @@ import shop.alien.entity.store.vo.WebSocketVo;
 import shop.alien.mapper.*;
 import shop.alien.mapper.*;
 import shop.alien.store.config.WebSocketProcess;
 import shop.alien.store.config.WebSocketProcess;
 import shop.alien.store.service.CommonRatingService;
 import shop.alien.store.service.CommonRatingService;
+import shop.alien.store.service.LifeDiscountCouponStoreFriendService;
 import shop.alien.store.util.CommonConstant;
 import shop.alien.store.util.CommonConstant;
 import shop.alien.util.common.constant.CommentSourceTypeEnum;
 import shop.alien.util.common.constant.CommentSourceTypeEnum;
 import shop.alien.util.common.constant.RatingBusinessTypeEnum;
 import shop.alien.util.common.constant.RatingBusinessTypeEnum;
@@ -66,6 +67,7 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
     private final LifeLikeRecordMapper lifeLikeRecordMapper;
     private final LifeLikeRecordMapper lifeLikeRecordMapper;
     private final LifeCollectMapper lifeCollectMapper;
     private final LifeCollectMapper lifeCollectMapper;
     private final LifeFansMapper lifeFansMapper;
     private final LifeFansMapper lifeFansMapper;
+    private final LifeDiscountCouponStoreFriendService lifeDiscountCouponStoreFriendService;
 
 
 
 
     public static final List<String> SERVICES_LIST = ImmutableList.of(
     public static final List<String> SERVICES_LIST = ImmutableList.of(
@@ -77,8 +79,12 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
 
 
     @Override
     @Override
     public Integer saveCommonRating(CommonRating commonRating) {
     public Integer saveCommonRating(CommonRating commonRating) {
-        // 1. 文本审核
         try {
         try {
+            // 必填校验:business_id 为库表必填且无默认值,插入前必须存在
+            if (commonRating.getBusinessType() == null || commonRating.getBusinessId() == null) {
+                throw new IllegalArgumentException("business_type 与 business_id 不能为空");
+            }
+            // 1. 文本审核
             TextModerationResultVO textCheckResult = textModerationUtil.invokeFunction(commonRating.getContent(), SERVICES_LIST);
             TextModerationResultVO textCheckResult = textModerationUtil.invokeFunction(commonRating.getContent(), SERVICES_LIST);
             if ("high".equals(textCheckResult.getRiskLevel())) {
             if ("high".equals(textCheckResult.getRiskLevel())) {
                 return 2;
                 return 2;
@@ -93,6 +99,17 @@ public class CommonRatingServiceImpl extends ServiceImpl<CommonRatingMapper, Com
             int i = this.save(commonRating) ? 0 : 1;
             int i = this.save(commonRating) ? 0 : 1;
             // 对不同的businessType进行不同的处理,
             // 对不同的businessType进行不同的处理,
             doBusinessWithType(commonRating);
             doBusinessWithType(commonRating);
+            // 用户评论好评(4.5分以上)且AI审核通过:将店铺可用券发放到用户卡包,用户可在「优惠券列表」中查到
+            if (i == 0 && commonRating.getBusinessType() != null && commonRating.getBusinessType() == RatingBusinessTypeEnum.STORE_RATING.getBusinessType()) {
+                Double score = commonRating.getScore();
+                if (score != null && score >= 4.5 && commonRating.getUserId() != null && commonRating.getBusinessId() != null) {
+                    try {
+                        lifeDiscountCouponStoreFriendService.issueCouponForGoodRating(Math.toIntExact(commonRating.getUserId()), commonRating.getBusinessId());
+                    } catch (Exception ex) {
+                        log.warn("CommonRatingService.saveCommonRating 好评送券异常 userId={}, storeId={}, msg={}", commonRating.getUserId(), commonRating.getBusinessId(), ex.getMessage());
+                    }
+                }
+            }
             return i;
             return i;
         } catch (Exception e) {
         } catch (Exception e) {
             log.error("CommonRatingService.saveCommonRating ERROR Msg={}", e.getMessage());
             log.error("CommonRatingService.saveCommonRating ERROR Msg={}", e.getMessage());

+ 56 - 0
alien-store/src/main/java/shop/alien/store/service/impl/LifeDiscountCouponStoreFriendServiceImpl.java

@@ -524,4 +524,60 @@ public class LifeDiscountCouponStoreFriendServiceImpl extends ServiceImpl<LifeDi
             return new ArrayList<>();
             return new ArrayList<>();
         }
         }
     }
     }
+
+    /**
+     * 好评送券:用户对店铺好评且通过AI审核后,将店铺可用券发放到用户卡包
+     */
+    @Override
+    public int issueCouponForGoodRating(Integer userId, Integer storeId) {
+        if (userId == null || storeId == null) {
+            return 0;
+        }
+        LocalDate currentDate = LocalDate.now();
+        LambdaQueryWrapper<LifeDiscountCoupon> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(LifeDiscountCoupon::getStoreId, String.valueOf(storeId))
+                .eq(LifeDiscountCoupon::getGetStatus, DiscountCouponEnum.CAN_GET.getValue())
+                .ge(LifeDiscountCoupon::getValidDate, currentDate)
+                .gt(LifeDiscountCoupon::getSingleQty, 0);
+        List<LifeDiscountCoupon> coupons = lifeDiscountCouponMapper.selectList(queryWrapper);
+        if (CollectionUtils.isEmpty(coupons)) {
+            return 0;
+        }
+        int granted = 0;
+        int commenterUserId = userId.intValue();
+        StoreInfo storeInfo = storeInfoMapper.selectById(storeId);
+        LifeUser lifeUser = lifeUserMapper.selectById(commenterUserId);
+        for (LifeDiscountCoupon coupon : coupons) {
+            try {
+                LifeDiscountCouponUser lifeDiscountCouponUser = new LifeDiscountCouponUser();
+                lifeDiscountCouponUser.setCouponId(coupon.getId());
+                lifeDiscountCouponUser.setUserId(commenterUserId);
+                lifeDiscountCouponUser.setReceiveTime(new Date());
+                lifeDiscountCouponUser.setExpirationTime(coupon.getValidDate());
+                lifeDiscountCouponUser.setStatus(Integer.parseInt(DiscountCouponEnum.WAITING_USED.getValue()));
+                lifeDiscountCouponUser.setDeleteFlag(0);
+                lifeDiscountCouponUserMapper.insert(lifeDiscountCouponUser);
+                coupon.setSingleQty(coupon.getSingleQty() - 1);
+                lifeDiscountCouponMapper.updateById(coupon);
+                granted++;
+            } catch (Exception e) {
+                // 单张发放失败不影响其余
+            }
+        }
+        if (granted > 0 && lifeUser != null && storeInfo != null) {
+            LifeNotice lifeNotice = new LifeNotice();
+            lifeNotice.setSenderId("system");
+            lifeNotice.setReceiverId("user_" + lifeUser.getUserPhone());
+            String text = "您对好友店铺「" + storeInfo.getStoreName() + "」的好评已通过审核,已为您发放" + granted + "张优惠券,快去我的券包查看吧~";
+            JSONObject jsonObject = new JSONObject();
+            jsonObject.put("message", text);
+            lifeNotice.setContext(jsonObject.toJSONString());
+            lifeNotice.setNoticeType(1);
+            lifeNotice.setTitle("好评送券");
+            lifeNotice.setIsRead(0);
+            lifeNotice.setDeleteFlag(0);
+            lifeNoticeMapper.insert(lifeNotice);
+        }
+        return granted;
+    }
 }
 }

+ 93 - 86
alien-store/src/main/java/shop/alien/store/service/impl/StoreUserServiceImpl.java

@@ -405,14 +405,12 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
     public R<IPage<StoreUserVo>> getStoreUserList(int pageNum, int pageSize, String id, String phone, Integer status, Integer accountType, String mainAccountId, String mainAccountPhone) {
     public R<IPage<StoreUserVo>> getStoreUserList(int pageNum, int pageSize, String id, String phone, Integer status, Integer accountType, String mainAccountId, String mainAccountPhone) {
         IPage<StoreUser> page = new Page<>(pageNum, pageSize);
         IPage<StoreUser> page = new Page<>(pageNum, pageSize);
         IPage<StoreUserVo> storeUserVoIPage = new Page<>();
         IPage<StoreUserVo> storeUserVoIPage = new Page<>();
-        // 子账号分支:仅以 store_platform_user_role 中能查出的 userId 为子账号,不再用 sub_account_id 判断
+        // 子账号分支:以 store_platform_user_role 每条记录为一行(不去重 userId),同一子账号在不同店铺展示多行,每行带主账号 id
         if (accountType == 2) {
         if (accountType == 2) {
             LambdaQueryWrapper<StorePlatformUserRole> roleWrapper = new LambdaQueryWrapper<>();
             LambdaQueryWrapper<StorePlatformUserRole> roleWrapper = new LambdaQueryWrapper<>();
             roleWrapper.eq(StorePlatformUserRole::getDeleteFlag, 0);
             roleWrapper.eq(StorePlatformUserRole::getDeleteFlag, 0);
             List<StorePlatformUserRole> userRoles = storePlatformUserRoleMapper.selectList(roleWrapper);
             List<StorePlatformUserRole> userRoles = storePlatformUserRoleMapper.selectList(roleWrapper);
-            List<Integer> subAccountUserIds = CollectionUtils.isEmpty(userRoles) ? Collections.emptyList()
-                    : userRoles.stream().map(StorePlatformUserRole::getUserId).distinct().collect(Collectors.toList());
-            if (subAccountUserIds.isEmpty()) {
+            if (CollectionUtils.isEmpty(userRoles)) {
                 storeUserVoIPage.setRecords(new ArrayList<>());
                 storeUserVoIPage.setRecords(new ArrayList<>());
                 storeUserVoIPage.setTotal(0);
                 storeUserVoIPage.setTotal(0);
                 storeUserVoIPage.setCurrent(pageNum);
                 storeUserVoIPage.setCurrent(pageNum);
@@ -420,86 +418,90 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
                 return R.data(storeUserVoIPage);
                 return R.data(storeUserVoIPage);
             }
             }
 
 
-            // 主账号ID 模糊:先查主账号(store_user accountType=1, id like),取其 store_id,再取 platform 中该 store 下的 userId
+            // 主账号ID 模糊:只保留 platform 中 store_id 属于这些主账号店铺的 role 记录
             if (StringUtils.isNotEmpty(mainAccountId)) {
             if (StringUtils.isNotEmpty(mainAccountId)) {
                 LambdaQueryWrapper<StoreUser> mainById = new LambdaQueryWrapper<>();
                 LambdaQueryWrapper<StoreUser> mainById = new LambdaQueryWrapper<>();
                 mainById.eq(StoreUser::getAccountType, 1).eq(StoreUser::getDeleteFlag, 0)
                 mainById.eq(StoreUser::getAccountType, 1).eq(StoreUser::getDeleteFlag, 0)
                         .apply("CAST(id AS CHAR) LIKE {0}", "%" + mainAccountId + "%");
                         .apply("CAST(id AS CHAR) LIKE {0}", "%" + mainAccountId + "%");
                 List<StoreUser> mains = storeUserMapper.selectList(mainById);
                 List<StoreUser> mains = storeUserMapper.selectList(mainById);
                 List<Integer> storeIds = mains == null ? Collections.emptyList() : mains.stream().map(StoreUser::getStoreId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
                 List<Integer> storeIds = mains == null ? Collections.emptyList() : mains.stream().map(StoreUser::getStoreId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
-                if (!storeIds.isEmpty()) {
-                    List<Integer> userIdsInStores = userRoles.stream()
-                            .filter(r -> storeIds.contains(r.getStoreId()))
-                            .map(StorePlatformUserRole::getUserId)
-                            .distinct()
-                            .collect(Collectors.toList());
-                    subAccountUserIds = subAccountUserIds.stream().filter(userIdsInStores::contains).collect(Collectors.toList());
+                if (storeIds.isEmpty()) {
+                    userRoles = Collections.emptyList();
                 } else {
                 } else {
-                    subAccountUserIds = Collections.emptyList();
-                }
-                if (subAccountUserIds.isEmpty()) {
-                    storeUserVoIPage.setRecords(new ArrayList<>());
-                    storeUserVoIPage.setTotal(0);
-                    storeUserVoIPage.setCurrent(pageNum);
-                    storeUserVoIPage.setSize(pageSize);
-                    return R.data(storeUserVoIPage);
+                    userRoles = userRoles.stream().filter(r -> storeIds.contains(r.getStoreId())).collect(Collectors.toList());
                 }
                 }
             }
             }
-            // 主账号联系电话 模糊:主账号 phone like,取其 store_id,再取 platform 该 store 下的 userId
-            if (StringUtils.isNotEmpty(mainAccountPhone)) {
+            // 主账号联系电话 模糊:只保留 platform 中 store_id 属于这些主账号店铺的 role 记录
+            if (StringUtils.isNotEmpty(mainAccountPhone) && !userRoles.isEmpty()) {
                 LambdaQueryWrapper<StoreUser> mainByPhone = new LambdaQueryWrapper<>();
                 LambdaQueryWrapper<StoreUser> mainByPhone = new LambdaQueryWrapper<>();
                 mainByPhone.eq(StoreUser::getAccountType, 1).eq(StoreUser::getDeleteFlag, 0).like(StoreUser::getPhone, mainAccountPhone);
                 mainByPhone.eq(StoreUser::getAccountType, 1).eq(StoreUser::getDeleteFlag, 0).like(StoreUser::getPhone, mainAccountPhone);
                 List<StoreUser> mains = storeUserMapper.selectList(mainByPhone);
                 List<StoreUser> mains = storeUserMapper.selectList(mainByPhone);
                 List<Integer> storeIds = mains == null ? Collections.emptyList() : mains.stream().map(StoreUser::getStoreId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
                 List<Integer> storeIds = mains == null ? Collections.emptyList() : mains.stream().map(StoreUser::getStoreId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
-                if (!storeIds.isEmpty()) {
-                    List<Integer> userIdsInStores = userRoles.stream()
-                            .filter(r -> storeIds.contains(r.getStoreId()))
-                            .map(StorePlatformUserRole::getUserId)
-                            .distinct()
-                            .collect(Collectors.toList());
-                    subAccountUserIds = subAccountUserIds.stream().filter(userIdsInStores::contains).collect(Collectors.toList());
+                if (storeIds.isEmpty()) {
+                    userRoles = Collections.emptyList();
                 } else {
                 } else {
-                    subAccountUserIds = Collections.emptyList();
-                }
-                if (subAccountUserIds.isEmpty()) {
-                    storeUserVoIPage.setRecords(new ArrayList<>());
-                    storeUserVoIPage.setTotal(0);
-                    storeUserVoIPage.setCurrent(pageNum);
-                    storeUserVoIPage.setSize(pageSize);
-                    return R.data(storeUserVoIPage);
+                    userRoles = userRoles.stream().filter(r -> storeIds.contains(r.getStoreId())).collect(Collectors.toList());
                 }
                 }
             }
             }
 
 
-            LambdaQueryWrapper<StoreUser> subAccountWrapper = new LambdaQueryWrapper<>();
-            subAccountWrapper.eq(StoreUser::getDeleteFlag, 0)
-                    .eq(status != null, StoreUser::getStatus, status)
-                    .in(StoreUser::getId, subAccountUserIds)
-                    .and(StringUtils.isNotEmpty(id), w -> w.apply("CAST(id AS CHAR) LIKE {0}", "%" + id + "%"))
-                    .like(StringUtils.isNotEmpty(phone), StoreUser::getPhone, phone);
-            subAccountWrapper.orderByDesc(StoreUser::getCreatedTime);
-            IPage<StoreUser> subAccountsPage = storeUserMapper.selectPage(page, subAccountWrapper);
-            BeanUtils.copyProperties(subAccountsPage, storeUserVoIPage);
+            if (userRoles.isEmpty()) {
+                storeUserVoIPage.setRecords(new ArrayList<>());
+                storeUserVoIPage.setTotal(0);
+                storeUserVoIPage.setCurrent(pageNum);
+                storeUserVoIPage.setSize(pageSize);
+                return R.data(storeUserVoIPage);
+            }
+
+            // 按子账号 id/phone/status 过滤:批量查 store_user,只保留对应子账号满足条件的 role
+            List<Integer> distinctUserIds = userRoles.stream().map(StorePlatformUserRole::getUserId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
+            List<StoreUser> subAccountList = distinctUserIds.isEmpty() ? Collections.emptyList() : storeUserMapper.selectBatchIds(distinctUserIds);
+            Map<Integer, StoreUser> subAccountMap = subAccountList == null ? new HashMap<>() : subAccountList.stream().filter(u -> u.getDeleteFlag() != null && u.getDeleteFlag() == 0).collect(Collectors.toMap(StoreUser::getId, u -> u, (a, b) -> a));
+            List<StorePlatformUserRole> filteredRoles = new ArrayList<>();
+            for (StorePlatformUserRole role : userRoles) {
+                StoreUser subAccount = subAccountMap.get(role.getUserId());
+                if (subAccount == null) continue;
+                if (StringUtils.isNotEmpty(id) && (subAccount.getId() == null || !String.valueOf(subAccount.getId()).contains(id))) continue;
+                if (StringUtils.isNotEmpty(phone) && (subAccount.getPhone() == null || !subAccount.getPhone().contains(phone))) continue;
+                if (status != null && !status.equals(subAccount.getStatus())) continue;
+                filteredRoles.add(role);
+            }
+            filteredRoles.sort(Comparator.comparing(StorePlatformUserRole::getId, Comparator.nullsLast(Comparator.naturalOrder())));
+
+            int total = filteredRoles.size();
+            storeUserVoIPage.setTotal(total);
+            storeUserVoIPage.setCurrent(pageNum);
+            storeUserVoIPage.setSize(pageSize);
+            int from = (pageNum - 1) * pageSize;
+            if (from >= total) {
+                storeUserVoIPage.setRecords(new ArrayList<>());
+                return R.data(storeUserVoIPage);
+            }
+            int to = Math.min(from + pageSize, total);
+            List<StorePlatformUserRole> pageRoles = filteredRoles.subList(from, to);
+
+            // 批量查本页主账号,避免 N+1
+            List<Integer> pageStoreIds = pageRoles.stream().map(StorePlatformUserRole::getStoreId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
+            Map<Integer, StoreUser> mainAccountMap = new HashMap<>();
+            if (!pageStoreIds.isEmpty()) {
+                LambdaQueryWrapper<StoreUser> mainW = new LambdaQueryWrapper<>();
+                mainW.eq(StoreUser::getAccountType, 1).eq(StoreUser::getDeleteFlag, 0).in(StoreUser::getStoreId, pageStoreIds);
+                List<StoreUser> mains = storeUserMapper.selectList(mainW);
+                if (mains != null) mains.forEach(m -> mainAccountMap.put(m.getStoreId(), m));
+            }
 
 
             List<StoreUserVo> resultRecords = new ArrayList<>();
             List<StoreUserVo> resultRecords = new ArrayList<>();
-            for (StoreUser subAccount : subAccountsPage.getRecords()) {
+            for (StorePlatformUserRole role : pageRoles) {
+                StoreUser subAccount = subAccountMap.get(role.getUserId());
+                if (subAccount == null) continue;
                 StoreUserVo storeUserVo = new StoreUserVo();
                 StoreUserVo storeUserVo = new StoreUserVo();
                 BeanUtils.copyProperties(subAccount, storeUserVo);
                 BeanUtils.copyProperties(subAccount, storeUserVo);
                 storeUserVo.setSwitchStatus(subAccount.getStatus() != null && subAccount.getStatus() == 0);
                 storeUserVo.setSwitchStatus(subAccount.getStatus() != null && subAccount.getStatus() == 0);
                 storeUserVo.setPhone(subAccount.getPhone());
                 storeUserVo.setPhone(subAccount.getPhone());
-                // 主账号通过 store_platform_user_role.store_id 关联:取该 userId 下一条 platform 的 storeId,再查该 store 的主账号
-                StorePlatformUserRole oneRole = userRoles.stream()
-                        .filter(r -> r.getUserId() != null && r.getUserId().equals(subAccount.getId()))
-                        .findFirst()
-                        .orElse(null);
-                if (oneRole != null && oneRole.getStoreId() != null) {
-                    LambdaQueryWrapper<StoreUser> mainByStore = new LambdaQueryWrapper<>();
-                    mainByStore.eq(StoreUser::getAccountType, 1).eq(StoreUser::getDeleteFlag, 0).eq(StoreUser::getStoreId, oneRole.getStoreId());
-                    StoreUser mainAccount = storeUserMapper.selectOne(mainByStore);
-                    if (mainAccount != null) {
-                        storeUserVo.setParentAccountId(mainAccount.getId());
-                        storeUserVo.setParentAccountPhone(mainAccount.getPhone());
-                        storeUserVo.setParentAccountName(mainAccount.getName());
-                    }
+                StoreUser mainAccount = role.getStoreId() == null ? null : mainAccountMap.get(role.getStoreId());
+                if (mainAccount != null) {
+                    storeUserVo.setParentAccountId(mainAccount.getId());
+                    storeUserVo.setParentAccountPhone(mainAccount.getPhone());
+                    storeUserVo.setParentAccountName(mainAccount.getName());
                 }
                 }
                 storeUserVo.setPassword(null);
                 storeUserVo.setPassword(null);
                 storeUserVo.setPayPassword(null);
                 storeUserVo.setPayPassword(null);
@@ -638,45 +640,50 @@ public class StoreUserServiceImpl extends ServiceImpl<StoreUserMapper, StoreUser
             String filePath = ExcelGenerator.generateExcel(excelPath + excelGeneratePath + fileName + ".xlsx", storeUserExcelVoList, StoreUserExcelVo.class);
             String filePath = ExcelGenerator.generateExcel(excelPath + excelGeneratePath + fileName + ".xlsx", storeUserExcelVoList, StoreUserExcelVo.class);
             return aliOSSUtil.uploadFile(new File(filePath), "excel/" + fileName + ".xlsx");
             return aliOSSUtil.uploadFile(new File(filePath), "excel/" + fileName + ".xlsx");
         }
         }
-        // 子账号导出:与 getStoreUserList 子账号分支一致,仅以 store_platform_user_role 的 userId 为子账号,主账号通过 store_id 关联
+        // 子账号导出:与 getStoreUserList 子账号分页一致,每条 store_platform_user_role 为一行(不去重),带主账号 id
         LambdaQueryWrapper<StorePlatformUserRole> roleWrapper = new LambdaQueryWrapper<>();
         LambdaQueryWrapper<StorePlatformUserRole> roleWrapper = new LambdaQueryWrapper<>();
         roleWrapper.eq(StorePlatformUserRole::getDeleteFlag, 0);
         roleWrapper.eq(StorePlatformUserRole::getDeleteFlag, 0);
         List<StorePlatformUserRole> userRoles = storePlatformUserRoleMapper.selectList(roleWrapper);
         List<StorePlatformUserRole> userRoles = storePlatformUserRoleMapper.selectList(roleWrapper);
-        List<Integer> subAccountUserIds = CollectionUtils.isEmpty(userRoles) ? Collections.emptyList()
-                : userRoles.stream().map(StorePlatformUserRole::getUserId).distinct().collect(Collectors.toList());
-        if (subAccountUserIds.isEmpty()) {
+        if (CollectionUtils.isEmpty(userRoles)) {
             String fileName = UUID.randomUUID().toString().replace("-", "");
             String fileName = UUID.randomUUID().toString().replace("-", "");
             String filePath = ExcelGenerator.generateExcel(excelPath + excelGeneratePath + fileName + ".xlsx", new ArrayList<StoreSubExcelVo>(), StoreSubExcelVo.class);
             String filePath = ExcelGenerator.generateExcel(excelPath + excelGeneratePath + fileName + ".xlsx", new ArrayList<StoreSubExcelVo>(), StoreSubExcelVo.class);
             return aliOSSUtil.uploadFile(new File(filePath), "excel/" + fileName + ".xlsx");
             return aliOSSUtil.uploadFile(new File(filePath), "excel/" + fileName + ".xlsx");
         }
         }
-        LambdaQueryWrapper<StoreUser> subWrapper = new LambdaQueryWrapper<>();
-        subWrapper.eq(StoreUser::getDeleteFlag, 0)
-                .in(StoreUser::getId, subAccountUserIds)
-                .and(StringUtils.isNotEmpty(id), w -> w.apply("CAST(id AS CHAR) LIKE {0}", "%" + id + "%"))
-                .like(StringUtils.isNotEmpty(phone), StoreUser::getPhone, phone)
-                .eq(StringUtils.isNotEmpty(status), StoreUser::getStatus, status)
-                .orderByDesc(StoreUser::getCreatedTime);
-        List<StoreUser> subAccounts = storeUserMapper.selectList(subWrapper);
+        List<Integer> distinctUserIds = userRoles.stream().map(StorePlatformUserRole::getUserId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
+        List<StoreUser> subAccountList = distinctUserIds.isEmpty() ? Collections.emptyList() : storeUserMapper.selectBatchIds(distinctUserIds);
+        Map<Integer, StoreUser> subAccountMap = subAccountList == null ? new HashMap<>() : subAccountList.stream().filter(u -> u.getDeleteFlag() != null && u.getDeleteFlag() == 0).collect(Collectors.toMap(StoreUser::getId, u -> u, (a, b) -> a));
+        List<StorePlatformUserRole> filteredRoles = new ArrayList<>();
+        for (StorePlatformUserRole role : userRoles) {
+            StoreUser subAccount = subAccountMap.get(role.getUserId());
+            if (subAccount == null) continue;
+            if (StringUtils.isNotEmpty(id) && (subAccount.getId() == null || !String.valueOf(subAccount.getId()).contains(id))) continue;
+            if (StringUtils.isNotEmpty(phone) && (subAccount.getPhone() == null || !subAccount.getPhone().contains(phone))) continue;
+            if (status != null && !status.equals(subAccount.getStatus())) continue;
+            filteredRoles.add(role);
+        }
+        filteredRoles.sort(Comparator.comparing(StorePlatformUserRole::getId, Comparator.nullsLast(Comparator.naturalOrder())));
+        List<Integer> storeIds = filteredRoles.stream().map(StorePlatformUserRole::getStoreId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
+        Map<Integer, StoreUser> mainAccountMap = new HashMap<>();
+        if (!storeIds.isEmpty()) {
+            LambdaQueryWrapper<StoreUser> mainW = new LambdaQueryWrapper<>();
+            mainW.eq(StoreUser::getAccountType, 1).eq(StoreUser::getDeleteFlag, 0).in(StoreUser::getStoreId, storeIds);
+            List<StoreUser> mains = storeUserMapper.selectList(mainW);
+            if (mains != null) mains.forEach(m -> mainAccountMap.put(m.getStoreId(), m));
+        }
         List<StoreSubExcelVo> storeSubExcelVoList = new ArrayList<>();
         List<StoreSubExcelVo> storeSubExcelVoList = new ArrayList<>();
         int serialNumber = 0;
         int serialNumber = 0;
-        for (StoreUser subAccount : subAccounts) {
+        for (StorePlatformUserRole role : filteredRoles) {
+            StoreUser subAccount = subAccountMap.get(role.getUserId());
+            if (subAccount == null) continue;
             StoreSubExcelVo vo = new StoreSubExcelVo();
             StoreSubExcelVo vo = new StoreSubExcelVo();
-            vo.setId(subAccount.getId());
             vo.setSerialNumber(++serialNumber);
             vo.setSerialNumber(++serialNumber);
+            vo.setId(subAccount.getId());
             vo.setPhone(subAccount.getPhone());
             vo.setPhone(subAccount.getPhone());
             vo.setCreatedTime(subAccount.getCreatedTime() != null ? subAccount.getCreatedTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().format(formatter) : null);
             vo.setCreatedTime(subAccount.getCreatedTime() != null ? subAccount.getCreatedTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().format(formatter) : null);
             vo.setStatus(subAccount.getStatus() == null ? "" : (subAccount.getStatus() == 0 ? "启用" : "禁用"));
             vo.setStatus(subAccount.getStatus() == null ? "" : (subAccount.getStatus() == 0 ? "启用" : "禁用"));
-            StorePlatformUserRole oneRole = userRoles.stream()
-                    .filter(r -> r.getUserId() != null && r.getUserId().equals(subAccount.getId()))
-                    .findFirst()
-                    .orElse(null);
-            if (oneRole != null && oneRole.getStoreId() != null) {
-                LambdaQueryWrapper<StoreUser> mainByStore = new LambdaQueryWrapper<>();
-                mainByStore.eq(StoreUser::getAccountType, 1).eq(StoreUser::getDeleteFlag, 0).eq(StoreUser::getStoreId, oneRole.getStoreId());
-                StoreUser mainAccount = storeUserMapper.selectOne(mainByStore);
-                if (mainAccount != null) {
-                    vo.setParentAccountPhone(mainAccount.getPhone());
-                }
+            StoreUser mainAccount = role.getStoreId() == null ? null : mainAccountMap.get(role.getStoreId());
+            if (mainAccount != null) {
+                vo.setParentAccountPhone(mainAccount.getPhone());
             }
             }
             storeSubExcelVoList.add(vo);
             storeSubExcelVoList.add(vo);
         }
         }