package modules.user; import com.jfinal.plugin.activerecord.Db; import com.jfinal.plugin.activerecord.Record; import common.jfinal.AppConfig; import common.model.DepositLog; import common.model.Nftt; import common.model.Order; import common.model.User; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; public class UserTeamShareTask implements Runnable { Order order; UserService userService; public UserTeamShareTask(Order order, UserService userService) { this.userService = userService; this.order = order; } // 统一分润逻辑,并进行容错和类型转换 (BigDecimal) private void processShare(String mobileNumber, BigDecimal percentage, String description) throws Exception { // 确保 order.getTotalPrice() 返回的是 BigDecimal,或者转换为 BigDecimal BigDecimal totalPrice = order.getBigDecimal("total_price"); if (totalPrice == null) { throw new RuntimeException("订单总价为空,无法分润"); } // 计算分润金额 // 使用 BigDecimal 进行精确计算,避免浮点数精度问题 BigDecimal shareAmount = totalPrice.multiply(percentage).divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP); // 查找用户 (userService 是注入的) User user = userService.findUserByMobileNumber(mobileNumber); if (user == null) { AppConfig.LOGGER.warn("分润用户 {} 不存在,无法分润", mobileNumber); // 根据业务需求抛出异常或跳过 return; } // 更新用户余额 BigDecimal currentBalance = user.getBigDecimal("balance"); // 如果 balance 字段为 null,初始化为 0 if (currentBalance == null) { currentBalance = BigDecimal.ZERO; } user.set("balance", currentBalance.add(shareAmount)); if (!user.update()) { throw new RuntimeException("更新用户 " + user.getStr("nickname") + " 余额失败"); } // 记录分润日志 DepositLog log = new DepositLog(); log.set("create_time", System.currentTimeMillis()); log.set("is_deleted", 0); log.set("description", description + " " + percentage.intValue() + "%"); // 描述中带上百分比 log.set("amount", shareAmount); log.set("user_id", user.getLong("id")); if (!log.save()) { throw new RuntimeException("保存分润日志失败 for user " + user.getStr("nickname")); } AppConfig.LOGGER.info("{} {}: {}", description, user.getStr("nickname"), shareAmount); } /** * 第一类、单独账号分润 * 0. 大领导 2% * 1. 团队奖励 5% * 2. 市场预留 3% * 4. 宣传外联 4% * 4. 技术公司运维 1% * 5. 平台费用 1% */ public void class1() throws Exception { AppConfig.LOGGER.info("正在处理订单 {} 的第一类分润", order.getLong("id")); // 确保这些手机号是常量,或者从配置中读取 processShare("27788881000", BigDecimal.valueOf(2), "大领导分润"); processShare("27788881001", BigDecimal.valueOf(5), "团队奖励"); processShare("27788881002", BigDecimal.valueOf(3), "市场预留"); processShare("27788881003", BigDecimal.valueOf(4), "宣传外联"); // 注意这里是 1%,用 BigDecimal.valueOf(1) processShare("27788881004", BigDecimal.valueOf(1), "技术公司运维"); processShare("27788881005", BigDecimal.valueOf(1), "平台费用"); AppConfig.LOGGER.info("订单 {} 第一类分润处理完成", order.getLong("id")); } /** * 第二类、直推分润 10% */ public void class2() { User user = User.dao.findById(order.getUserId()); User referrerUser = User.dao.findById(user.getReferrerId()); // 直推人不存在,直接跳过 if (referrerUser == null) { return; } // 确保 order.getTotalPrice() 返回的是 BigDecimal,或者转换为 BigDecimal BigDecimal totalPrice = order.getBigDecimal("total_price"); BigDecimal percentage = BigDecimal.valueOf(10); if (totalPrice == null) { throw new RuntimeException("订单总价为空,无法分润"); } // 直推10%分润 BigDecimal shareAmount = totalPrice.multiply(percentage).divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP); // 更新用户余额 BigDecimal currentBalance = referrerUser.getBigDecimal("balance"); // 如果 balance 字段为 null,初始化为 0 if (currentBalance == null) { currentBalance = BigDecimal.ZERO; } referrerUser.set("balance", currentBalance.add(shareAmount)); if (!referrerUser.update()) { throw new RuntimeException("更新用户 " + referrerUser.getMobileNumber() + " 余额失败"); } // 记录分润日志 String description = "直推分润"; DepositLog log = new DepositLog(); log.set("create_time", System.currentTimeMillis()); log.set("is_deleted", 0); log.set("description", description + " " + percentage.intValue() + "%"); // 描述中带上百分比 log.set("amount", shareAmount); log.set("user_id", referrerUser.getLong("id")); if (!log.save()) { throw new RuntimeException("保存分润日志失败 for user " + referrerUser.getMobileNumber()); } AppConfig.LOGGER.info("{} {}: {}", description, referrerUser.getMobileNumber(), shareAmount); } /** * 第三类、NFT持有者分润 * 1. 10%的部分 * 2. 4%的部分 */ public void class3() { String queryUserIdSql = "SELECT user_id, COUNT(id) AS valid_order_count " + "FROM t_order " + "WHERE order_status = 60 AND delivery_status = 1 " + "GROUP BY user_id HAVING COUNT(id) > 0"; List userOrderCounts = Db.find(queryUserIdSql); // 提取所有唯一的 user_id,用于下一步批量查询 User 对象 Set eligibleUserIds = userOrderCounts.stream() .map(record -> record.getLong("user_id")) .collect(Collectors.toSet()); // 如果没有符合条件的用户,直接返回 if (eligibleUserIds.isEmpty()) { AppConfig.LOGGER.info("没有符合分润条件的用户。"); return; } // 第二步:根据这些 user_id,批量查询出对应的 User 对象 // JFinal 的 IN 操作比较方便,但是当列表过大时,分批查询 User 仍然是最佳实践 // 假设 eligibleUserIds 不会过大,可以直接查询 String inSql = eligibleUserIds.stream() .map(String::valueOf) .collect(Collectors.joining(",")); AppConfig.LOGGER.info("开始查询用户"); List userList = User.dao.find("SELECT id, balance FROM t_user WHERE id IN (" + inSql + ")"); AppConfig.LOGGER.info("用户查询完毕"); // 为了方便处理,将 list 转换为 map,userOrderCounts 也转换为 map Map userMap = userList.stream() .collect(Collectors.toMap(User::getId, user -> user)); Map userValidOrderCountMap = userOrderCounts.stream() .collect(Collectors.toMap( record -> record.getLong("user_id"), record -> record.getLong("valid_order_count").intValue() )); // 现在,你可以遍历 userOrderCounts,结合 userMap 进行分润处理 List usersToUpdate = new ArrayList<>(); List depositLogsToSave = new ArrayList<>(); // 假设分润规则是:每个用户获得的总利润 = (该用户的有效订单数量 / 所有用户的总有效订单数量) * 总利润 // 或者:简单地每个用户的固定份额 // 这里我们用总订单数量来计算每个用户的分润比例 long totalValidOrders = userValidOrderCountMap.values().stream().mapToLong(Integer::longValue).sum(); // 避免除以零 if (totalValidOrders == 0) { AppConfig.LOGGER.warn("所有符合条件用户的总订单数为0,无法按订单比例分润。"); return; } // 总价值 BigDecimal totalPrice = BigDecimal.valueOf(order.getTotalPrice()); Nftt nftt = Nftt.dao.findById(10000); for (Record record : userOrderCounts) { // 参与分润的用户 Long userId = record.getLong("user_id"); // 当前用户的有效订单数量 int validOrderCount = record.getLong("valid_order_count").intValue(); User user = userMap.get(userId); if (user == null) { AppConfig.LOGGER.warn("未找到用户ID为 {} 的User对象,跳过分润。", userId); continue; } // 分润1,其中4%作为NFT持有者分享(0.04 * validOrderCount / totalValidOrders) BigDecimal shareArtio1 = BigDecimal.valueOf(0.04); BigDecimal userShareRatio1 = shareArtio1.multiply(BigDecimal.valueOf(validOrderCount)) .divide(BigDecimal.valueOf(totalValidOrders), 8, RoundingMode.HALF_UP);// 确保高精度 BigDecimal shareAmount1 = totalPrice.multiply(userShareRatio1).setScale(2, RoundingMode.HALF_UP); // 四舍五入到两位小数 // 分润2,NFT持有者共享总数的10%,还没售卖出去的部分归平台所有 (0.1 * validOrderCount / Nftt.maxQuantity) BigDecimal shareArtio2 = BigDecimal.valueOf(0.1); BigDecimal userShareRatio2 = shareArtio2.multiply(BigDecimal.valueOf(validOrderCount)) .divide(BigDecimal.valueOf(nftt.getMaxQuantity()), 8, RoundingMode.HALF_UP);// 确保高精度,注意这里的底数是MaxQuantity BigDecimal shareAmount2 = totalPrice.multiply(userShareRatio2).setScale(2, RoundingMode.HALF_UP); // 四舍五入到两位小数 // 更新用户余额 BigDecimal currentBalance = user.getBigDecimal("balance"); if (currentBalance == null) { currentBalance = BigDecimal.ZERO; } user.set("balance", currentBalance.add(shareAmount1).add(shareAmount2).floatValue()); usersToUpdate.add(user); // 创建分润日志 DepositLog log1 = new DepositLog(); log1.set("create_time", System.currentTimeMillis()); log1.set("is_deleted", 0); log1.set("description", "4%作为NFT持有者分享," + validOrderCount + " / " + totalValidOrders); // 加入订单数量信息 log1.set("amount", shareAmount1); log1.set("user_id", userId); depositLogsToSave.add(log1); DepositLog log2 = new DepositLog(); log2.set("create_time", System.currentTimeMillis()); log2.set("is_deleted", 0); log2.set("description", "NFT持有者共享总数的10%," + validOrderCount + " / " + nftt.getMaxQuantity()); // 加入订单数量信息 log2.set("amount", shareAmount1); log2.set("user_id", userId); depositLogsToSave.add(log2); } // 在一个事务中执行所有批量更新和插入 Db.tx(() -> { int[] userUpdateResult = Db.batchUpdate(usersToUpdate, 1000); // 批量更新用户 int[] logSaveResult = Db.batchSave(depositLogsToSave, 1000); // 批量保存日志 AppConfig.LOGGER.info("已批量更新 {} 个用户余额,批量保存 {} 条分润日志。", userUpdateResult.length, logSaveResult.length); return true; // 事务成功 }); } /** * 第四类、持有者激励分润,这个最复杂最后做,可能需要为user加一个时间戳字段 */ public void class4() {} @Override public void run() { // 确保整个分润过程在一个数据库事务中 // 因为操作涉及到多个 User 和 DepositLog 的更新和插入 Db.tx(() -> { try { this.class1(); this.class2(); this.class3(); this.class4(); return true; // 事务成功 } catch (Exception e) { AppConfig.LOGGER.error("订单分润处理异常,订单ID: {}, 错误: {}", order.getLong("id"), e.getMessage(), e); return false; // 事务失败,回滚 } }); } }