部门交易金额

This commit is contained in:
2026-06-19 21:13:56 +08:00
parent ce72d87d12
commit df823119ed
5 changed files with 394 additions and 0 deletions

View File

@@ -144,6 +144,29 @@ public class LbDepartmentUserController {
return ResponseEntity.badRequest().body(result);
}
@GetMapping("/treeSumTotalMoney")
@Operation(summary = "部门用户树形结构(累计交易金额)")
public ResponseEntity<Map<String, Object>> treeSumTotalMoney(
@Parameter(description = "租户id", required = true) @RequestParam String tenantId,
@Parameter(description = "是否活跃1活跃 0不活跃") @RequestParam(name = "is_active", required = false) Integer isActive,
@Parameter(description = "加入日期,查询 join_date 大于该日期的数据")
@RequestParam(name = "join_date", required = false)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate joinDate,
@Parameter(description = "最后成交日期,查询 last_trade_date 大于该日期的数据")
@RequestParam(name = "last_trade_date", required = false)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate lastTradeDate,
@Parameter(description = "交易开始时间(buy_time)格式yyyy-MM-dd HH:mm:ss")
@RequestParam(name = "buy_start_time", required = false) String buyStartTime,
@Parameter(description = "交易结束时间(buy_time)格式yyyy-MM-dd HH:mm:ss")
@RequestParam(name = "buy_end_time", required = false) String buyEndTime) {
Map<String, Object> result = lbDepartmentUserService.getDepartmentUserTreeCopy(tenantId, isActive, joinDate, lastTradeDate, buyStartTime, buyEndTime);
Boolean success = (Boolean) result.get("success");
if (success != null && success) {
return ResponseEntity.ok(result);
}
return ResponseEntity.badRequest().body(result);
}
@PostMapping("/exportUsers")
@Operation(summary = "导出指定用户Excel",
description = "按租户ID与用户ID列表导出用户ID、用户名、手机号、加入时间、最早/最晚buy_time、累计交易金额")

View File

@@ -7,7 +7,9 @@ import com.rj.entity.LbOrderRow;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
@Mapper
public interface LbOrderRowMapper extends BaseMapper<LbOrderRow> {
@@ -23,4 +25,20 @@ public interface LbOrderRowMapper extends BaseMapper<LbOrderRow> {
*/
List<LbBuyerTradeStats> selectLatestPaidStatsByTenantId(@Param("tenantId") String tenantId,
@Param("dataType") String dataType);
/**
* 按租户、买家ID列表和时间范围查询订单总金额。
*/
BigDecimal selectSumTotalMoneyByBuyerIdsAndTimeRange(@Param("tenantId") String tenantId,
@Param("buyerIds") List<String> buyerIds,
@Param("buyStartTime") String buyStartTime,
@Param("buyEndTime") String buyEndTime);
/**
* 按租户、买家ID列表和时间范围分组查询每个买家的订单总金额。
*/
List<Map<String, Object>> selectSumTotalMoneyGroupByBuyerIds(@Param("tenantId") String tenantId,
@Param("buyerIds") List<String> buyerIds,
@Param("buyStartTime") String buyStartTime,
@Param("buyEndTime") String buyEndTime);
}

View File

@@ -46,8 +46,17 @@ public interface ILbDepartmentUserService extends IService<LbDepartmentUser> {
*/
Map<String, Object> getDepartmentUserTree(String tenantId, Integer isActive, LocalDate joinDate, LocalDate lastTradeDate);
/**
* 按租户从 lb_user 构建父子树(克隆)
*/
Map<String, Object> getDepartmentUserTreeCopy(String tenantId, Integer isActive, LocalDate joinDate, LocalDate lastTradeDate,
String buyStartTime, String buyEndTime);
/**
* 按租户与用户 ID 列表导出部门用户及订单汇总 Excel。
*/
void exportUsersExcel(String tenantId, List<String> userIds, HttpServletResponse response);
Map<String, Object> getDepartmentUserTreeSumTotalMoney(String tenantId, Integer isActive, LocalDate joinDate,
LocalDate lastTradeDate, String buyStartTime, String buyEndTime);
}

View File

@@ -761,6 +761,170 @@ public class LbDepartmentUserServiceImpl extends ServiceImpl<LbDepartmentUserMap
}
}
@Override
public Map<String, Object> getDepartmentUserTreeCopy(String tenantId, Integer isActive, LocalDate joinDate, LocalDate lastTradeDate,
String buyStartTime, String buyEndTime) {
Map<String, Object> result = new HashMap<>();
try {
if (tenantId == null || tenantId.trim().isEmpty()) {
result.put("success", false);
result.put("message", "租户id不能为空");
return result;
}
String tenantIdTrim = tenantId.trim();
List<LbUser> lbUsers = lbUserMapper.selectList(
new LambdaQueryWrapper<LbUser>().eq(LbUser::getTenantId, tenantIdTrim));
if (lbUsers == null || lbUsers.isEmpty()) {
result.put("success", true);
result.put("message", "查询成功");
result.put("data", new ArrayList<>());
result.put("total", 0);
result.put("tenantId", tenantIdTrim);
return result;
}
Map<String, LbUser> userById = new LinkedHashMap<>();
for (LbUser lbUser : lbUsers) {
if (lbUser.getId() != null) {
userById.put(String.valueOf(lbUser.getId()), lbUser);
}
}
Map<String, LbBuyerTradeStats> tradeStatsByUserId = loadBuyerTradeStatsFromOrderRow(tenantIdTrim);
boolean hasFilter = isActive != null || joinDate != null || lastTradeDate != null;
Set<String> matchedUserIds = new HashSet<>();
Map<String, LocalDate> joinDateByUserId = new HashMap<>();
Map<String, LocalDate> lastTradeDateByUserId = new HashMap<>();
Map<String, BigDecimal> lastBuyAmtByUserId = new HashMap<>();
Map<String, Integer> isActiveByUserId = new HashMap<>();
for (LbUser lbUser : lbUsers) {
if (lbUser.getId() == null) {
continue;
}
String userId = String.valueOf(lbUser.getId());
LocalDate userJoinDate = lbUser.getJoinTime() != null
? lbUser.getJoinTime().toLocalDate() : null;
LbBuyerTradeStats tradeStats = tradeStatsByUserId.get(userId);
LocalDate userLastTradeDate = tradeStats == null
? null : parseOrderTimeToDate(tradeStats.getPayTime());
BigDecimal userLastBuyAmt = tradeStats == null ? null : tradeStats.getTotalMoney();
int userIsActive = tradeStats == null ? 0 : 1;
joinDateByUserId.put(userId, userJoinDate);
lastTradeDateByUserId.put(userId, userLastTradeDate);
lastBuyAmtByUserId.put(userId, userLastBuyAmt);
isActiveByUserId.put(userId, userIsActive);
if (!hasFilter || matchesTreeFilter(
userIsActive, userJoinDate, userLastTradeDate, isActive, joinDate, lastTradeDate)) {
matchedUserIds.add(userId);
}
}
Set<String> visibleUserIds = hasFilter
? includeAncestorUserIds(matchedUserIds, userById)
: new HashSet<>(userById.keySet());
Map<String, Map<String, Object>> nodeByUserId = new LinkedHashMap<>();
Map<String, String> parentByUserId = new HashMap<>();
Map<String, List<String>> childrenByUserId = new HashMap<>();
for (String userId : visibleUserIds) {
LbUser lbUser = userById.get(userId);
if (lbUser == null) {
continue;
}
nodeByUserId.put(userId, toLbUserTreeNode(
lbUser,
joinDateByUserId.get(userId),
lastTradeDateByUserId.get(userId),
lastBuyAmtByUserId.get(userId),
isActiveByUserId.getOrDefault(userId, 0)));
String parentUserId = resolveLbUserParentId(lbUser, userById);
if (parentUserId != null && visibleUserIds.contains(parentUserId)) {
parentByUserId.put(userId, parentUserId);
childrenByUserId.computeIfAbsent(parentUserId, k -> new ArrayList<>()).add(userId);
}
}
boolean hasTradeTimeFilter = (buyStartTime != null && !buyStartTime.trim().isEmpty())
|| (buyEndTime != null && !buyEndTime.trim().isEmpty());
Map<String, BigDecimal> tradeMoneySumByUserId = new HashMap<>();
Map<String, BigDecimal> userTradeMoney = new HashMap<>();
if (hasTradeTimeFilter) {
if (!visibleUserIds.isEmpty()) {
List<Map<String, Object>> tradeResults = lbOrderRowMapper.selectSumTotalMoneyGroupByBuyerIds(
tenantIdTrim, new ArrayList<>(visibleUserIds), buyStartTime, buyEndTime);
for (Map<String, Object> row : tradeResults) {
String buyerId = String.valueOf(row.get("buyerId"));
BigDecimal totalMoney = (BigDecimal) row.get("totalMoney");
userTradeMoney.put(buyerId, totalMoney != null ? totalMoney : BigDecimal.ZERO);
}
for (String userId : visibleUserIds) {
BigDecimal sumAmount = calculateRecursiveTradeSumOptimized(userId, childrenByUserId, userTradeMoney);
tradeMoneySumByUserId.put(userId, sumAmount);
}
}
}
List<Map<String, Object>> treeRoots = new ArrayList<>();
for (Map.Entry<String, Map<String, Object>> entry : nodeByUserId.entrySet()) {
String userId = entry.getKey();
Map<String, Object> node = entry.getValue();
if (hasTradeTimeFilter) {
node.put("selfTradeMoney", userTradeMoney.getOrDefault(userId, BigDecimal.ZERO));
node.put("tradeMoneySum", tradeMoneySumByUserId.getOrDefault(userId, BigDecimal.ZERO));
}
String parentUserId = parentByUserId.get(userId);
Map<String, Object> parentNode = parentUserId == null ? null : nodeByUserId.get(parentUserId);
if (parentNode == null) {
treeRoots.add(node);
continue;
}
@SuppressWarnings("unchecked")
List<Map<String, Object>> children = (List<Map<String, Object>>) parentNode.get("children");
children.add(node);
}
sortDepartmentUserTreeByName(treeRoots);
List<Map<String, Object>> treeData = skipFirstTreeLevel(treeRoots);
result.put("success", true);
result.put("message", "查询成功");
result.put("data", treeData);
result.put("total", countTreeNodes(treeData));
result.put("tenantId", tenantIdTrim);
return result;
} catch (Exception e) {
result.put("success", false);
result.put("message", "查询树形结构异常:" + e.getMessage());
return result;
}
}
/**
* 优化后的递归计算:从内存 Map 中获取数据,避免重复数据库查询
*/
private BigDecimal calculateRecursiveTradeSumOptimized(String userId, Map<String, List<String>> childrenByUserId,
Map<String, BigDecimal> userTradeMoney) {
BigDecimal sum = userTradeMoney.getOrDefault(userId, BigDecimal.ZERO);
List<String> children = childrenByUserId.get(userId);
if (children != null && !children.isEmpty()) {
for (String childId : children) {
sum = sum.add(calculateRecursiveTradeSumOptimized(childId, childrenByUserId, userTradeMoney));
}
}
return sum;
}
/**
* 去掉第 1 级根节点,将其 children 提升为返回给前端的顶层。
*/
@@ -1135,6 +1299,153 @@ public class LbDepartmentUserServiceImpl extends ServiceImpl<LbDepartmentUserMap
}
}
@Override
public Map<String, Object> getDepartmentUserTreeSumTotalMoney(String tenantId, Integer isActive, LocalDate joinDate, LocalDate lastTradeDate, String buyStartTime, String buyEndTime) {
Map<String, Object> result = new HashMap<>();
try {
if (tenantId == null || tenantId.trim().isEmpty()) {
result.put("success", false);
result.put("message", "租户id不能为空");
return result;
}
String tenantIdTrim = tenantId.trim();
List<LbUser> lbUsers = lbUserMapper.selectList(
new LambdaQueryWrapper<LbUser>().eq(LbUser::getTenantId, tenantIdTrim));
if (lbUsers == null || lbUsers.isEmpty()) {
result.put("success", true);
result.put("message", "查询成功");
result.put("data", new ArrayList<>());
result.put("total", 0);
result.put("tenantId", tenantIdTrim);
return result;
}
Map<String, LbUser> userById = new LinkedHashMap<>();
for (LbUser lbUser : lbUsers) {
if (lbUser.getId() != null) {
userById.put(String.valueOf(lbUser.getId()), lbUser);
}
}
Map<String, LbBuyerTradeStats> tradeStatsByUserId = loadBuyerTradeStatsFromOrderRow(tenantIdTrim);
boolean hasFilter = isActive != null || joinDate != null || lastTradeDate != null;
Set<String> matchedUserIds = new HashSet<>();
Map<String, LocalDate> joinDateByUserId = new HashMap<>();
Map<String, LocalDate> lastTradeDateByUserId = new HashMap<>();
Map<String, BigDecimal> lastBuyAmtByUserId = new HashMap<>();
Map<String, Integer> isActiveByUserId = new HashMap<>();
for (LbUser lbUser : lbUsers) {
if (lbUser.getId() == null) {
continue;
}
String userId = String.valueOf(lbUser.getId());
LocalDate userJoinDate = lbUser.getJoinTime() != null
? lbUser.getJoinTime().toLocalDate() : null;
LbBuyerTradeStats tradeStats = tradeStatsByUserId.get(userId);
LocalDate userLastTradeDate = tradeStats == null
? null : parseOrderTimeToDate(tradeStats.getPayTime());
BigDecimal userLastBuyAmt = tradeStats == null ? null : tradeStats.getTotalMoney();
int userIsActive = tradeStats == null ? 0 : 1;
joinDateByUserId.put(userId, userJoinDate);
lastTradeDateByUserId.put(userId, userLastTradeDate);
lastBuyAmtByUserId.put(userId, userLastBuyAmt);
isActiveByUserId.put(userId, userIsActive);
if (!hasFilter || matchesTreeFilter(
userIsActive, userJoinDate, userLastTradeDate, isActive, joinDate, lastTradeDate)) {
matchedUserIds.add(userId);
}
}
Set<String> visibleUserIds = hasFilter
? includeAncestorUserIds(matchedUserIds, userById)
: new HashSet<>(userById.keySet());
Map<String, Map<String, Object>> nodeByUserId = new LinkedHashMap<>();
Map<String, String> parentByUserId = new HashMap<>();
Map<String, List<String>> childrenByUserId = new HashMap<>();
for (String userId : visibleUserIds) {
LbUser lbUser = userById.get(userId);
if (lbUser == null) {
continue;
}
nodeByUserId.put(userId, toLbUserTreeNode(
lbUser,
joinDateByUserId.get(userId),
lastTradeDateByUserId.get(userId),
lastBuyAmtByUserId.get(userId),
isActiveByUserId.getOrDefault(userId, 0)));
String parentUserId = resolveLbUserParentId(lbUser, userById);
if (parentUserId != null && visibleUserIds.contains(parentUserId)) {
parentByUserId.put(userId, parentUserId);
childrenByUserId.computeIfAbsent(parentUserId, k -> new ArrayList<>()).add(userId);
}
}
boolean hasTradeTimeFilter = (buyStartTime != null && !buyStartTime.trim().isEmpty())
|| (buyEndTime != null && !buyEndTime.trim().isEmpty());
Map<String, BigDecimal> tradeMoneySumByUserId = new HashMap<>();
Map<String, BigDecimal> userTradeMoney = new HashMap<>();
if (hasTradeTimeFilter) {
if (!visibleUserIds.isEmpty()) {
List<Map<String, Object>> tradeResults = lbOrderRowMapper.selectSumTotalMoneyGroupByBuyerIds(
tenantIdTrim, new ArrayList<>(visibleUserIds), buyStartTime, buyEndTime);
for (Map<String, Object> row : tradeResults) {
String buyerId = String.valueOf(row.get("buyerId"));
BigDecimal totalMoney = (BigDecimal) row.get("totalMoney");
userTradeMoney.put(buyerId, totalMoney != null ? totalMoney : BigDecimal.ZERO);
}
for (String userId : visibleUserIds) {
BigDecimal sumAmount = calculateRecursiveTradeSumOptimized(userId, childrenByUserId, userTradeMoney);
tradeMoneySumByUserId.put(userId, sumAmount);
}
}
}
List<Map<String, Object>> treeRoots = new ArrayList<>();
for (Map.Entry<String, Map<String, Object>> entry : nodeByUserId.entrySet()) {
String userId = entry.getKey();
Map<String, Object> node = entry.getValue();
if (hasTradeTimeFilter) {
node.put("selfTradeMoney", userTradeMoney.getOrDefault(userId, BigDecimal.ZERO));
node.put("tradeMoneySum", tradeMoneySumByUserId.getOrDefault(userId, BigDecimal.ZERO));
}
String parentUserId = parentByUserId.get(userId);
Map<String, Object> parentNode = parentUserId == null ? null : nodeByUserId.get(parentUserId);
if (parentNode == null) {
treeRoots.add(node);
continue;
}
@SuppressWarnings("unchecked")
List<Map<String, Object>> children = (List<Map<String, Object>>) parentNode.get("children");
children.add(node);
}
sortDepartmentUserTreeByName(treeRoots);
List<Map<String, Object>> treeData = skipFirstTreeLevel(treeRoots);
result.put("success", true);
result.put("message", "查询成功");
result.put("data", treeData);
result.put("total", countTreeNodes(treeData));
result.put("tenantId", tenantIdTrim);
return result;
} catch (Exception e) {
result.put("success", false);
result.put("message", "查询树形结构异常:" + e.getMessage());
return result;
}
}
private static final class OrderBuyStats {
private String earliestBuyTime;
private LocalDateTime earliestBuyDateTime;

View File

@@ -85,4 +85,37 @@
)
</select>
<select id="selectSumTotalMoneyByBuyerIdsAndTimeRange" resultType="java.math.BigDecimal">
SELECT COALESCE(SUM(o.total_money), 0)
FROM lb_order_row o
WHERE o.tenant_id = #{tenantId}
AND o.buyer_id IN
<foreach collection="buyerIds" item="buyerId" open="(" separator="," close=")">
#{buyerId}
</foreach>
<if test="buyStartTime != null and buyStartTime != ''">
AND o.buy_time &gt;= #{buyStartTime}
</if>
<if test="buyEndTime != null and buyEndTime != ''">
AND o.buy_time &lt;= #{buyEndTime}
</if>
</select>
<select id="selectSumTotalMoneyGroupByBuyerIds" resultType="map">
SELECT o.buyer_id AS buyerId, COALESCE(SUM(o.total_money), 0) AS totalMoney
FROM lb_order_row o
WHERE o.tenant_id = #{tenantId}
AND o.buyer_id IN
<foreach collection="buyerIds" item="buyerId" open="(" separator="," close=")">
#{buyerId}
</foreach>
<if test="buyStartTime != null and buyStartTime != ''">
AND o.buy_time &gt;= #{buyStartTime}
</if>
<if test="buyEndTime != null and buyEndTime != ''">
AND o.buy_time &lt;= #{buyEndTime}
</if>
GROUP BY o.buyer_id
</select>
</mapper>