This commit is contained in:
sion
2026-04-07 01:05:05 +08:00
parent edad10ff06
commit 5ca1274607
83 changed files with 1561 additions and 1241 deletions

View File

@@ -36,7 +36,7 @@ public class BonusController {
/**
* 领取奖励
* body: { type: "new_user"|"referral", referredUserId?, milestone? }
* body: { type: "new_user"|"referral"|"indirect_referral", referredUserId?, milestone?, directReferralId? }
*/
@PostMapping("/claim")
public Result<Map<String, Object>> claim(@RequestBody Map<String, Object> params) {
@@ -62,6 +62,18 @@ public class BonusController {
Long referredUserId = Long.valueOf(referredUserIdObj.toString());
int milestone = Integer.parseInt(milestoneObj.toString());
return Result.success("领取成功", bonusService.claimReferralBonus(userId, referredUserId, milestone));
} else if ("indirect_referral".equals(type)) {
Object directReferralIdObj = params.get("directReferralId");
Object indirectReferredUserIdObj = params.get("indirectReferredUserId");
Object milestoneObj = params.get("milestone");
if (directReferralIdObj == null || indirectReferredUserIdObj == null || milestoneObj == null) {
return Result.fail("缺少参数");
}
Long directReferralId = Long.valueOf(directReferralIdObj.toString());
Long indirectReferredUserId = Long.valueOf(indirectReferredUserIdObj.toString());
int milestone = Integer.parseInt(milestoneObj.toString());
return Result.success("领取成功", bonusService.claimIndirectReferralBonus(
userId, directReferralId, indirectReferredUserId, milestone));
} else {
return Result.fail("无效的奖励类型");
}

View File

@@ -22,17 +22,20 @@ import java.util.*;
*
* 规则:
* 1. 首充福利新用户首次充值完成后可领取100u一次性
* 2. 推广奖励被推广人累计充值每满1000u推广人领100u多次触发,最高8次/人)
* 2. 直接推广奖励被推广人累计充值每满1000u推广人领100u最高8次/人)
* 3. 间接推广奖励被推广人推广的用户充值每满1000u推广人也领50u最高8次/人间接上限)
*/
@Service
public class BonusService {
private static final BigDecimal BONUS_AMOUNT = new BigDecimal("100");
private static final BigDecimal INDIRECT_BONUS_AMOUNT = new BigDecimal("50");
private static final BigDecimal MILESTONE_UNIT = new BigDecimal("1000");
private static final int MAX_MILESTONES = 8;
private static final String NEW_USER_REMARK = "新人首充福利";
private static final String REFERRAL_REMARK_PREFIX = "邀请奖励-";
private static final String INDIRECT_REFERRAL_REMARK_PREFIX = "间接推广奖励-";
@Autowired
private AccountFundMapper accountFundMapper;
@@ -114,15 +117,60 @@ public class BonusService {
.count();
referralInfo.put("claimableCount", claimable);
// === 间接推广奖励 ===
// 查询 B 推广的所有人C, D, E...
LambdaQueryWrapper<User> indirectWrapper = new LambdaQueryWrapper<>();
indirectWrapper.eq(User::getReferredBy, referred.getId());
List<User> indirectReferred = userMapper.selectList(indirectWrapper);
referralInfo.put("indirectRefCount", indirectReferred.size());
// 计算间接奖励里程碑
int indirectClaimable = 0;
List<Map<String, Object>> indirectMilestones = new ArrayList<>();
for (User ir : indirectReferred) {
AccountFund irFund = assetService.getOrCreateFundAccount(ir.getId());
BigDecimal irTotalDeposit = irFund.getTotalDeposit() != null
? irFund.getTotalDeposit() : BigDecimal.ZERO;
int irEarned = irTotalDeposit.divide(MILESTONE_UNIT, 0, RoundingMode.DOWN).intValue();
irEarned = Math.min(irEarned, MAX_MILESTONES);
Set<Integer> irClaimed = getIndirectClaimedMilestones(userId, referred.getId(), ir.getId());
for (int i = 1; i <= MAX_MILESTONES; i++) {
boolean earned = i <= irEarned;
boolean iClaimed = irClaimed.contains(i);
boolean isClaimable = earned && !iClaimed;
if (isClaimable) indirectClaimable++;
Map<String, Object> im = new HashMap<>();
im.put("indirectReferredUserId", ir.getId());
im.put("indirectUsername", ir.getUsername());
im.put("milestone", i);
im.put("amount", INDIRECT_BONUS_AMOUNT);
im.put("threshold", MILESTONE_UNIT.multiply(new BigDecimal(i)));
im.put("earned", earned);
im.put("claimed", iClaimed);
im.put("claimable", isClaimable);
indirectMilestones.add(im);
}
}
referralInfo.put("indirectClaimableCount", indirectClaimable);
referralInfo.put("indirectMilestones", indirectMilestones);
referralRewards.add(referralInfo);
}
result.put("referralRewards", referralRewards);
// 总可领取数量
// 总可领取数量(直接 + 间接)
int totalClaimable = (Boolean.TRUE.equals(newUserBonus.get("eligible")) ? 1 : 0);
totalClaimable += referralRewards.stream()
.mapToInt(r -> r.get("claimableCount") instanceof Number
? ((Number) r.get("claimableCount")).intValue() : 0)
.mapToInt(r -> {
int direct = r.get("claimableCount") instanceof Number
? ((Number) r.get("claimableCount")).intValue() : 0;
int indirect = r.get("indirectClaimableCount") instanceof Number
? ((Number) r.get("indirectClaimableCount")).intValue() : 0;
return direct + indirect;
})
.sum();
result.put("totalClaimable", totalClaimable);
@@ -205,6 +253,66 @@ public class BonusService {
return result;
}
/**
* 领取间接推广奖励
* A 推广 BB 推广 C → A 领取 C 充值里程碑的间接奖励50 USDT
*
* @param referrerId A领取人
* @param directReferralId BA 的直接推广人)
* @param indirectReferredUserId CB 的推广人)
* @param milestone 里程碑序号
*/
@Transactional(rollbackFor = Exception.class)
public Map<String, Object> claimIndirectReferralBonus(Long referrerId, Long directReferralId,
Long indirectReferredUserId, int milestone) {
if (milestone < 1 || milestone > MAX_MILESTONES) {
throw new RuntimeException("无效的里程碑");
}
// 验证推广链A→B→C
User directReferral = userMapper.selectById(directReferralId);
if (directReferral == null || !referrerId.equals(directReferral.getReferredBy())) {
throw new RuntimeException("直接推广关系不存在");
}
User indirectReferred = userMapper.selectById(indirectReferredUserId);
if (indirectReferred == null || !directReferralId.equals(indirectReferred.getReferredBy())) {
throw new RuntimeException("间接推广关系不存在");
}
// 检查里程碑是否已达到
AccountFund irFund = assetService.getOrCreateFundAccount(indirectReferredUserId);
BigDecimal irTotalDeposit = irFund.getTotalDeposit() != null
? irFund.getTotalDeposit() : BigDecimal.ZERO;
int earnedMilestones = irTotalDeposit.divide(MILESTONE_UNIT, 0, RoundingMode.DOWN).intValue();
if (milestone > earnedMilestones) {
throw new RuntimeException("该里程碑尚未达到");
}
// 检查是否已领取
String remark = INDIRECT_REFERRAL_REMARK_PREFIX + directReferralId + "-" + indirectReferredUserId + "-" + milestone;
if (hasClaimedByRemark(referrerId, remark)) {
throw new RuntimeException("该间接奖励已领取");
}
// 发放奖励
AccountFund referrerFund = assetService.getOrCreateFundAccount(referrerId);
BigDecimal balanceBefore = referrerFund.getBalance();
BigDecimal balanceAfter = balanceBefore.add(INDIRECT_BONUS_AMOUNT);
updateFundBalance(referrerFund, balanceAfter);
assetService.createFlow(referrerId, 1, INDIRECT_BONUS_AMOUNT, balanceBefore, balanceAfter,
"USDT", null, remark);
Map<String, Object> result = new HashMap<>();
result.put("amount", INDIRECT_BONUS_AMOUNT);
result.put("balance", balanceAfter);
result.put("directReferralId", directReferralId);
result.put("indirectReferredUserId", indirectReferredUserId);
result.put("milestone", milestone);
return result;
}
/**
* 更新资金账户余额
*/
@@ -250,4 +358,28 @@ public class BonusService {
}
return milestones;
}
/**
* 获取已领取的间接推广里程碑集合
*/
private Set<Integer> getIndirectClaimedMilestones(Long referrerId, Long directReferralId, Long indirectReferredUserId) {
String prefix = INDIRECT_REFERRAL_REMARK_PREFIX + directReferralId + "-" + indirectReferredUserId + "-";
LambdaQueryWrapper<AccountFlow> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(AccountFlow::getUserId, referrerId)
.likeRight(AccountFlow::getRemark, prefix);
List<AccountFlow> flows = accountFlowMapper.selectList(wrapper);
Set<Integer> milestones = new HashSet<>();
for (AccountFlow flow : flows) {
String remark = flow.getRemark();
if (remark != null && remark.startsWith(prefix)) {
try {
int m = Integer.parseInt(remark.substring(prefix.length()));
milestones.add(m);
} catch (NumberFormatException ignored) {
}
}
}
return milestones;
}
}