| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- 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<Record> userOrderCounts = Db.find(queryUserIdSql);
- // 提取所有唯一的 user_id,用于下一步批量查询 User 对象
- Set<Long> 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<User> userList = User.dao.find("SELECT id, balance FROM t_user WHERE id IN (" + inSql + ")");
- AppConfig.LOGGER.info("用户查询完毕");
- // 为了方便处理,将 list 转换为 map,userOrderCounts 也转换为 map
- Map<Long, User> userMap = userList.stream()
- .collect(Collectors.toMap(User::getId, user -> user));
- Map<Long, Integer> userValidOrderCountMap = userOrderCounts.stream()
- .collect(Collectors.toMap(
- record -> record.getLong("user_id"),
- record -> record.getLong("valid_order_count").intValue()
- ));
- // 现在,你可以遍历 userOrderCounts,结合 userMap 进行分润处理
- List<User> usersToUpdate = new ArrayList<>();
- List<DepositLog> 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; // 事务失败,回滚
- }
- });
- }
- }
|