Переглянути джерело

新增用户标签功能(新用户,老用户,常客,未消费用户)

skyline 1 місяць тому
батько
коміт
25f1202b28

+ 15 - 0
haha-admin-web/src/views/customer/utils/hook.tsx

@@ -70,6 +70,21 @@ export function useCustomer(tableRef: Ref) {
         </el-tag>
       )
     },
+    {
+      label: "消费标签",
+      prop: "userTagLabel",
+      minWidth: 90,
+      cellRenderer: ({ row }) => {
+        if (!row.userTagLabel) return <span>-</span>;
+        const tagMap: Record<string, { type: string; label: string }> = {
+          "new": { type: "success", label: "新用户" },
+          "regular": { type: "", label: "老用户" },
+          "frequent": { type: "warning", label: "常客" }
+        };
+        const tag = tagMap[row.userTag] || { type: "info", label: row.userTagLabel };
+        return <el-tag type={tag.type as any} size="small">{tag.label}</el-tag>;
+      }
+    },
     {
       label: "订单数",
       prop: "totalOrders",

+ 4 - 0
haha-admin-web/src/views/customer/utils/types.ts

@@ -11,6 +11,10 @@ export interface CustomerItem {
   totalSpent: number;
   lastLoginTime?: string;
   createTime: string;
+  /** 用户消费标签:new-新用户, regular-老用户, frequent-常客 */
+  userTag?: string;
+  /** 用户消费标签中文描述 */
+  userTagLabel?: string;
 }
 
 // 搜索表单类型

+ 15 - 0
haha-admin-web/src/views/order/utils/hook.tsx

@@ -157,6 +157,21 @@ export function useOrder(tableRef: Ref) {
         return <el-tag type={status.type as any}>{status.text}</el-tag>;
       }
     },
+    {
+      label: "用户标签",
+      prop: "userTagLabel",
+      minWidth: 90,
+      cellRenderer: ({ row }) => {
+        if (!row.userTagLabel) return <span>-</span>;
+        const tagMap: Record<string, { type: string; label: string }> = {
+          "new": { type: "success", label: "新用户" },
+          "regular": { type: "", label: "老用户" },
+          "frequent": { type: "warning", label: "常客" }
+        };
+        const tag = tagMap[row.userTag] || { type: "info", label: row.userTagLabel };
+        return <el-tag type={tag.type as any} size="small">{tag.label}</el-tag>;
+      }
+    },
     {
       label: "下单时间",
       prop: "createTime",

+ 4 - 0
haha-admin-web/src/views/order/utils/types.ts

@@ -25,6 +25,10 @@ export interface OrderItem {
   products?: OrderItemDetail[];
   videoUrl?: string;
   confidence?: number;
+  /** 用户消费标签:new-新用户, regular-老用户, frequent-常客 */
+  userTag?: string;
+  /** 用户消费标签中文描述 */
+  userTagLabel?: string;
 }
 
 // 订单明细类型(与后端 OrderItemVO 对齐)

+ 4 - 27
haha-admin/src/main/java/com/haha/admin/controller/UserController.java

@@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.*;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.List;
 
 /**
  * 客户管理控制器
@@ -48,10 +49,8 @@ public class UserController {
 
             IPage<User> userPage = userService.getPage(page, pageSize, params);
 
-            // 填充额外字段
-            for (User user : userPage.getRecords()) {
-                fillUserLabels(user);
-            }
+            // 填充标签字段(由Service层处理)
+            userService.fillUserLabels(userPage.getRecords());
 
             Map<String, Object> data = new HashMap<>();
             data.put("list", userPage.getRecords());
@@ -79,7 +78,7 @@ public class UserController {
             if (user == null) {
                 return Result.error(404, "客户不存在");
             }
-            fillUserLabels(user);
+            userService.fillUserLabels(List.of(user));
             return Result.success("查询成功", user);
         } catch (Exception e) {
             log.error("查询客户详情失败: id={}, error={}", id, e.getMessage(), e);
@@ -153,26 +152,4 @@ public class UserController {
         }
     }
 
-    /**
-     * 填充客户标签字段
-     * @param user 客户对象
-     */
-    private void fillUserLabels(User user) {
-        // 状态标签
-        if (user.getStatus() != null) {
-            switch (user.getStatus()) {
-                case 1:
-                    user.setStatusLabel("正常");
-                    user.setStatusColor("success");
-                    break;
-                case 0:
-                    user.setStatusLabel("禁用");
-                    user.setStatusColor("danger");
-                    break;
-                default:
-                    user.setStatusLabel("未知");
-                    user.setStatusColor("info");
-            }
-        }
-    }
 }

+ 41 - 0
haha-common/src/main/java/com/haha/common/enums/UserTag.java

@@ -0,0 +1,41 @@
+package com.haha.common.enums;
+
+import lombok.Getter;
+
+/**
+ * 用户消费标签枚举
+ * 用于标识用户的消费行为特征
+ */
+@Getter
+public enum UserTag {
+
+    NEW("new", "新用户"),
+    REGULAR("regular", "老用户"),
+    FREQUENT("frequent", "常客"),
+    POTENTIAL("potential", "未消费用户");
+
+    private final String code;
+    private final String description;
+
+    UserTag(String code, String description) {
+        this.code = code;
+        this.description = description;
+    }
+
+    public static UserTag fromCode(String code) {
+        if (code == null) {
+            return null;
+        }
+        for (UserTag tag : values()) {
+            if (tag.code.equals(code)) {
+                return tag;
+            }
+        }
+        return null;
+    }
+
+    public static String getDescription(String code) {
+        UserTag tag = fromCode(code);
+        return tag != null ? tag.description : "未知";
+    }
+}

+ 6 - 0
haha-common/src/main/java/com/haha/common/vo/OrderVO.java

@@ -38,4 +38,10 @@ public class OrderVO {
     private String videoUrl;
     private BigDecimal confidence;
     private List<OrderItemVO> products;
+
+    /** 用户消费标签:new-新用户, regular-老用户, frequent-常客 */
+    private String userTag;
+
+    /** 用户消费标签中文描述 */
+    private String userTagLabel;
 }

+ 8 - 0
haha-entity/src/main/java/com/haha/entity/Order.java

@@ -121,4 +121,12 @@ public class Order implements Serializable {
 
     @TableField(exist = false)
     private Long shopId;
+
+    /** 用户消费标签:new-新用户, regular-老用户, frequent-常客 */
+    @TableField(exist = false)
+    private String userTag;
+
+    /** 用户消费标签中文描述 */
+    @TableField(exist = false)
+    private String userTagLabel;
 }

+ 8 - 0
haha-entity/src/main/java/com/haha/entity/User.java

@@ -47,4 +47,12 @@ public class User implements Serializable {
     
     @TableField(exist = false)
     private String statusColor;
+
+    /** 用户消费标签:new-新用户, regular-老用户, frequent-常客 */
+    @TableField(exist = false)
+    private String userTag;
+
+    /** 用户消费标签中文描述 */
+    @TableField(exist = false)
+    private String userTagLabel;
 }

+ 53 - 0
haha-mapper/src/main/java/com/haha/mapper/OrderMapper.java

@@ -7,6 +7,8 @@ import org.apache.ibatis.annotations.Select;
 
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
 
 public interface OrderMapper extends BaseMapper<Order> {
 
@@ -24,4 +26,55 @@ public interface OrderMapper extends BaseMapper<Order> {
             "</if>" +
             "</script>")
     BigDecimal sumCompletedOrderAmount(@Param("startDate") LocalDateTime startDate);
+
+    /**
+     * 批量查询用户历史已支付订单数
+     *
+     * @param userIds 用户ID列表
+     * @return 每个用户的已支付订单数(user_id, order_count)
+     */
+    @Select("<script>" +
+            "SELECT user_id, COUNT(*) as order_count FROM t_order " +
+            "WHERE pay_status = 'PAID' AND user_id IN " +
+            "<foreach collection='userIds' item='id' open='(' separator=',' close=')'>" +
+            "#{id}" +
+            "</foreach>" +
+            " GROUP BY user_id" +
+            "</script>")
+    List<Map<String, Object>> batchCountPaidOrdersByUserIds(@Param("userIds") List<Long> userIds);
+
+    /**
+     * 批量查询用户近30天已支付订单数
+     *
+     * @param userIds 用户ID列表
+     * @param thirtyDaysAgo 30天前的时间
+     * @return 每个用户近30天的已支付订单数(user_id, order_count)
+     */
+    @Select("<script>" +
+            "SELECT user_id, COUNT(*) as order_count FROM t_order " +
+            "WHERE pay_status = 'PAID' AND pay_time &gt;= #{thirtyDaysAgo} AND user_id IN " +
+            "<foreach collection='userIds' item='id' open='(' separator=',' close=')'>" +
+            "#{id}" +
+            "</foreach>" +
+            " GROUP BY user_id" +
+            "</script>")
+    List<Map<String, Object>> batchCountRecentPaidOrdersByUserIds(
+            @Param("userIds") List<Long> userIds,
+            @Param("thirtyDaysAgo") LocalDateTime thirtyDaysAgo);
+
+    /**
+     * 批量查询用户总订单数(包含所有状态)
+     *
+     * @param userIds 用户ID列表
+     * @return 每个用户的总订单数(user_id, order_count)
+     */
+    @Select("<script>" +
+            "SELECT user_id, COUNT(*) as order_count FROM t_order " +
+            "WHERE user_id IN " +
+            "<foreach collection='userIds' item='id' open='(' separator=',' close=')'>" +
+            "#{id}" +
+            "</foreach>" +
+            " GROUP BY user_id" +
+            "</script>")
+    List<Map<String, Object>> batchCountAllOrdersByUserIds(@Param("userIds") List<Long> userIds);
 }

+ 8 - 0
haha-service/src/main/java/com/haha/service/UserService.java

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.haha.entity.User;
 import java.math.BigDecimal;
+import java.util.List;
 import java.util.Map;
 
 public interface UserService extends IService<User> {
@@ -21,4 +22,11 @@ public interface UserService extends IService<User> {
      * 获取客户统计信息
      */
     Map<String, Object> getStatistics();
+
+    /**
+     * 批量填充客户标签字段(状态标签 + 用户消费标签)
+     *
+     * @param users 客户列表
+     */
+    void fillUserLabels(List<User> users);
 }

+ 21 - 0
haha-service/src/main/java/com/haha/service/UserTagService.java

@@ -0,0 +1,21 @@
+package com.haha.service;
+
+import com.haha.common.enums.UserTag;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 用户消费标签服务
+ * 用于批量计算用户的消费行为标签(新用户/老用户/常客)
+ */
+public interface UserTagService {
+
+    /**
+     * 批量计算用户消费标签
+     *
+     * @param userIds 用户ID列表
+     * @return userId -> UserTag 映射
+     */
+    Map<Long, UserTag> batchComputeUserTags(List<Long> userIds);
+}

+ 80 - 40
haha-service/src/main/java/com/haha/service/impl/OrderServiceImpl.java

@@ -12,6 +12,7 @@ import com.haha.common.enums.OrderStatus;
 import com.haha.common.enums.PayStatus;
 import com.haha.common.enums.PaymentChannel;
 import com.haha.common.config.CommonConfig;
+import com.haha.common.enums.UserTag;
 import com.haha.common.utils.EntityLabelUtils;
 import com.haha.common.utils.OrderUtils;
 import com.haha.common.vo.OrderItemVO;
@@ -22,6 +23,7 @@ import com.haha.entity.Shop;
 import com.haha.mapper.DeviceMapper;
 import com.haha.mapper.OrderMapper;
 import com.haha.mapper.ShopMapper;
+import com.haha.service.UserTagService;
 import com.haha.service.OrderGoodsService;
 import com.haha.service.OrderService;
 import com.haha.service.InviteActivityService;
@@ -46,6 +48,8 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
 @Slf4j
 @Service
@@ -55,6 +59,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
     private final DeviceMapper deviceMapper;
     private final ShopMapper shopMapper;
     private final CommonConfig commonConfig;
+    private final UserTagService userTagService;
 
     @Autowired
     @Lazy
@@ -109,8 +114,8 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
         // 分页查询
         IPage<Order> orderPage = this.page(new Page<>(page, pageSize), wrapper);
 
-        // 填充标签字段和关联信息
-        orderPage.getRecords().forEach(this::fillOrderLabels);
+        // 批量填充标签字段和关联信息(含用户消费标签)
+        fillOrderLabels(orderPage.getRecords());
 
         return orderPage;
     }
@@ -121,7 +126,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
         if (order == null) {
             return null;
         }
-        fillOrderLabels(order);
+        fillOrderLabels(List.of(order));
         return order;
     }
 
@@ -154,6 +159,16 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
         List<OrderItemVO> products = orderGoodsService.getVOByOrderId(id);
         vo.setProducts(products);
 
+        // 填充用户消费标签
+        if (order.getUserId() != null) {
+            Map<Long, UserTag> tagMap = userTagService.batchComputeUserTags(List.of(order.getUserId()));
+            UserTag tag = tagMap.get(order.getUserId());
+            if (tag != null) {
+                vo.setUserTag(tag.getCode());
+                vo.setUserTagLabel(tag.getDescription());
+            }
+        }
+
         return vo;
     }
 
@@ -387,50 +402,75 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
     }
 
     /**
-     * 填充订单标签字段
+     * 批量填充订单标签字段(含用户消费标签)
+     * 优化:使用批量SQL预计算用户标签,避免N+1查询
      */
-    private void fillOrderLabels(Order order) {
-        if (order.getPayStatus() != null) {
-            var payLabel = EntityLabelUtils.getPayStatusLabel(order.getPayStatus());
-            order.setPayStatusLabel(payLabel.getLabel());
-        }
-        if (order.getStatus() != null) {
-            var statusLabel = EntityLabelUtils.getStatusLabel("order", order.getStatus());
-            order.setStatusLabel(statusLabel.getLabel());
-        }
-
-        // 如果 payType 为空但 payChannel 有值,从 payChannel 获取描述
-        if (order.getPayType() == null || order.getPayType().isEmpty()) {
-            if (order.getPayChannel() != null && !order.getPayChannel().isEmpty()) {
-                PaymentChannel channel = PaymentChannel.fromCode(order.getPayChannel());
-                if (channel != null) {
-                    order.setPayType(channel.getDescription());
+    private void fillOrderLabels(List<Order> orders) {
+        if (orders == null || orders.isEmpty()) {
+            return;
+        }
+
+        // 1. 批量计算用户消费标签
+        List<Long> userIds = orders.stream()
+                .map(Order::getUserId)
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+        Map<Long, UserTag> userTagMap = userTagService.batchComputeUserTags(userIds);
+
+        // 2. 逐条填充标签
+        for (Order order : orders) {
+            // 用户消费标签
+            if (order.getUserId() != null) {
+                UserTag tag = userTagMap.get(order.getUserId());
+                if (tag != null) {
+                    order.setUserTag(tag.getCode());
+                    order.setUserTagLabel(tag.getDescription());
+                }
+            }
+
+            // 支付状态标签
+            if (order.getPayStatus() != null) {
+                var payLabel = EntityLabelUtils.getPayStatusLabel(order.getPayStatus());
+                order.setPayStatusLabel(payLabel.getLabel());
+            }
+            // 订单状态标签
+            if (order.getStatus() != null) {
+                var statusLabel = EntityLabelUtils.getStatusLabel("order", order.getStatus());
+                order.setStatusLabel(statusLabel.getLabel());
+            }
+
+            // 如果 payType 为空但 payChannel 有值,从 payChannel 获取描述
+            if (order.getPayType() == null || order.getPayType().isEmpty()) {
+                if (order.getPayChannel() != null && !order.getPayChannel().isEmpty()) {
+                    PaymentChannel channel = PaymentChannel.fromCode(order.getPayChannel());
+                    if (channel != null) {
+                        order.setPayType(channel.getDescription());
+                    }
+                } else if (order.getOutTradeNo() != null && !order.getOutTradeNo().isEmpty()) {
+                    order.setPayType("微信支付"); // 兼容旧数据
+                } else if (PayStatus.isPaid(order.getPayStatus())) {
+                    order.setPayType("免费");
                 }
-            } else if (order.getOutTradeNo() != null && !order.getOutTradeNo().isEmpty()) {
-                order.setPayType("微信支付"); // 兼容旧数据
-            } else if (PayStatus.isPaid(order.getPayStatus())) {
-                order.setPayType("免费");
             }
-        }
 
-        // 根据 deviceId 查询设备信息,获取门店名称
-        if (order.getDeviceId() != null && !order.getDeviceId().isEmpty()) {
-            try {
-                // 通过 deviceId 查询设备
-                Device device = deviceMapper.selectByDeviceId(order.getDeviceId());
-                if (device != null) {
-                    order.setShopId(device.getShopId());
-                    // 通过 shopId 查询门店名称
-                    if (device.getShopId() != null) {
-                        Shop shop = shopMapper.selectById(device.getShopId());
-                        if (shop != null) {
-                            order.setShopName(shop.getName());
-                            order.setStoreName(shop.getName());
+            // 根据 deviceId 查询设备信息,获取门店名称
+            if (order.getDeviceId() != null && !order.getDeviceId().isEmpty()) {
+                try {
+                    Device device = deviceMapper.selectByDeviceId(order.getDeviceId());
+                    if (device != null) {
+                        order.setShopId(device.getShopId());
+                        if (device.getShopId() != null) {
+                            Shop shop = shopMapper.selectById(device.getShopId());
+                            if (shop != null) {
+                                order.setShopName(shop.getName());
+                                order.setStoreName(shop.getName());
+                            }
                         }
                     }
+                } catch (Exception e) {
+                    log.warn("获取订单关联门店信息失败: orderId={}, deviceId={}", order.getId(), order.getDeviceId(), e);
                 }
-            } catch (Exception e) {
-                log.warn("获取订单关联门店信息失败: orderId={}, deviceId={}", order.getId(), order.getDeviceId(), e);
             }
         }
     }

+ 53 - 3
haha-service/src/main/java/com/haha/service/impl/UserServiceImpl.java

@@ -4,23 +4,31 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.haha.common.enums.UserTag;
 import com.haha.entity.User;
 import com.haha.mapper.UserMapper;
 import com.haha.service.UserService;
 import com.haha.service.AccountService;
-import org.springframework.beans.factory.annotation.Autowired;
+import com.haha.service.UserTagService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.util.StringUtils;
 
 import java.math.BigDecimal;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
+@Slf4j
 @Service
+@RequiredArgsConstructor
 public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
 
-    @Autowired
-    private AccountService accountService;
+    private final UserTagService userTagService;
+    private final AccountService accountService;
 
     @Override
     public User getUserByOpenId(String openId) {
@@ -91,4 +99,46 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
         
         return stats;
     }
+
+    @Override
+    public void fillUserLabels(List<User> users) {
+        if (users == null || users.isEmpty()) {
+            return;
+        }
+
+        // 1. 批量计算用户消费标签
+        List<Long> userIds = users.stream()
+                .map(User::getId)
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+        Map<Long, UserTag> userTagMap = userTagService.batchComputeUserTags(userIds);
+
+        // 2. 逐条填充标签
+        for (User user : users) {
+            // 用户消费标签
+            UserTag tag = userTagMap.get(user.getId());
+            if (tag != null) {
+                user.setUserTag(tag.getCode());
+                user.setUserTagLabel(tag.getDescription());
+            }
+
+            // 状态标签
+            if (user.getStatus() != null) {
+                switch (user.getStatus()) {
+                    case 1:
+                        user.setStatusLabel("正常");
+                        user.setStatusColor("success");
+                        break;
+                    case 0:
+                        user.setStatusLabel("禁用");
+                        user.setStatusColor("danger");
+                        break;
+                    default:
+                        user.setStatusLabel("未知");
+                        user.setStatusColor("info");
+                }
+            }
+        }
+    }
 }

+ 93 - 0
haha-service/src/main/java/com/haha/service/impl/UserTagServiceImpl.java

@@ -0,0 +1,93 @@
+package com.haha.service.impl;
+
+import com.haha.common.enums.UserTag;
+import com.haha.mapper.OrderMapper;
+import com.haha.service.UserTagService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.*;
+
+/**
+ * 用户消费标签服务实现
+ * 通过批量SQL预计算用户消费数据,在内存中映射标签
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class UserTagServiceImpl implements UserTagService {
+
+    private final OrderMapper orderMapper;
+
+    /** 常客判定阈值:近30天消费次数 >= 此值 */
+    private static final int FREQUENT_THRESHOLD = 4;
+    /** 老用户判定阈值:总消费次数 >= 此值 */
+    private static final int REGULAR_THRESHOLD = 2;
+
+    @Override
+    public Map<Long, UserTag> batchComputeUserTags(List<Long> userIds) {
+        if (userIds == null || userIds.isEmpty()) {
+            return Collections.emptyMap();
+        }
+
+        // 去重
+        List<Long> distinctUserIds = userIds.stream().distinct().toList();
+
+        // 1. 批量查询总消费次数(已支付订单)
+        List<Map<String, Object>> totalCounts = orderMapper.batchCountPaidOrdersByUserIds(distinctUserIds);
+        Map<Long, Integer> totalCountMap = new HashMap<>();
+        for (Map<String, Object> row : totalCounts) {
+            Long userId = ((Number) row.get("user_id")).longValue();
+            int count = ((Number) row.get("order_count")).intValue();
+            totalCountMap.put(userId, count);
+        }
+
+        // 2. 批量查询近30天消费次数(已支付订单)
+        LocalDateTime thirtyDaysAgo = LocalDateTime.now().minusDays(30);
+        List<Map<String, Object>> recentCounts = orderMapper.batchCountRecentPaidOrdersByUserIds(distinctUserIds, thirtyDaysAgo);
+        Map<Long, Integer> recentCountMap = new HashMap<>();
+        for (Map<String, Object> row : recentCounts) {
+            Long userId = ((Number) row.get("user_id")).longValue();
+            int count = ((Number) row.get("order_count")).intValue();
+            recentCountMap.put(userId, count);
+        }
+
+        // 3. 批量查询总订单数(包含所有状态,用于判断"新用户")
+        List<Map<String, Object>> allOrderCounts = orderMapper.batchCountAllOrdersByUserIds(distinctUserIds);
+        Map<Long, Integer> allOrderCountMap = new HashMap<>();
+        for (Map<String, Object> row : allOrderCounts) {
+            Long userId = ((Number) row.get("user_id")).longValue();
+            int count = ((Number) row.get("order_count")).intValue();
+            allOrderCountMap.put(userId, count);
+        }
+
+        // 4. 按优先级判断标签:常客 > 老用户 > 新用户 > 潜在用户
+        Map<Long, UserTag> result = new HashMap<>();
+        for (Long userId : distinctUserIds) {
+            int paidTotalCount = totalCountMap.getOrDefault(userId, 0);
+            int paidRecentCount = recentCountMap.getOrDefault(userId, 0);
+            int allOrderCount = allOrderCountMap.getOrDefault(userId, 0);
+
+            UserTag tag;
+            if (paidRecentCount >= FREQUENT_THRESHOLD) {
+                tag = UserTag.FREQUENT;
+            } else if (paidTotalCount >= REGULAR_THRESHOLD) {
+                tag = UserTag.REGULAR;
+            } else if (paidTotalCount == 1) {
+                tag = UserTag.NEW;
+            } else if (allOrderCount >= 1) {
+                // 有下单行为但尚未完成支付的用户
+                tag = UserTag.POTENTIAL;
+            } else {
+                // 完全没有订单记录的用户,不设置标签
+                continue;
+            }
+            result.put(userId, tag);
+        }
+
+        log.debug("批量计算用户标签完成:输入{}个用户,输出{}个标签", distinctUserIds.size(), result.size());
+        return result;
+    }
+}