TASK: 1.affiliate 新增referral新增、查询、修改以及其他相关的自动结算佣金的功能;2.删除affiliate_income表及相关mapper

This commit is contained in:
2025-08-19 17:44:34 +08:00
parent 552ec828ab
commit caa9985d11
23 changed files with 499 additions and 105 deletions

View File

@@ -8,6 +8,8 @@ import com.ai.da.model.vo.AffiliateVO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import java.math.BigDecimal;
public interface AffiliateService extends IService<Affiliate> {
Boolean registerAsAnAffiliate(String promotionMethod);
@@ -16,7 +18,7 @@ public interface AffiliateService extends IService<Affiliate> {
AffiliateVO personalAffiliateCenter();
double[] getPersonalMonthlyIncome(int year);
BigDecimal[] getPersonalMonthlyIncome(int year);
Boolean applicationApproval(Long id, Boolean isApproved, Float commission);
@@ -26,6 +28,8 @@ public interface AffiliateService extends IService<Affiliate> {
Boolean affiliateLinkViewsIncrease(Long id);
void syncLinkViewCountToDB();
IPage<AffiliateInvitationDetailsVO> getEachAffiliateGeneratedRevenue(AffiliateQueryDTO affiliateQueryDTO);
Affiliate getByAccountId(Long accountId);

View File

@@ -0,0 +1,19 @@
package com.ai.da.service;
import com.ai.da.mapper.primary.entity.Referral;
import com.ai.da.model.dto.EditReferralDTO;
import com.ai.da.model.dto.ReferralPageQueryDTO;
import com.ai.da.model.vo.ReferralPageQueryVO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
public interface ReferralService extends IService<Referral> {
IPage<ReferralPageQueryVO> queryByPage(ReferralPageQueryDTO referralPageQueryDTO);
void editReferral(EditReferralDTO editReferralDTO);
void deleteReferral(List<Long> idList);
}

View File

@@ -8,10 +8,7 @@ import com.ai.da.common.response.ResultEnum;
import com.ai.da.common.utils.CopyUtil;
import com.ai.da.common.utils.RedisUtil;
import com.ai.da.common.utils.SendEmailUtil;
import com.ai.da.mapper.primary.AffiliateIncomeMapper;
import com.ai.da.mapper.primary.AffiliateMapper;
import com.ai.da.mapper.primary.ProductCouponsMapper;
import com.ai.da.mapper.primary.SubscriptionInfoMapper;
import com.ai.da.mapper.primary.*;
import com.ai.da.mapper.primary.entity.*;
import com.ai.da.model.dto.AffiliateEmailParamsDTO;
import com.ai.da.model.dto.AffiliateQueryDTO;
@@ -20,6 +17,7 @@ import com.ai.da.model.vo.AffiliateVO;
import com.ai.da.model.vo.AuthPrincipalVo;
import com.ai.da.service.*;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -35,6 +33,8 @@ import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import static com.ai.da.common.utils.RedisUtil.AFFILIATE_LINK_VIEW_KEY;
@Service
@Slf4j
public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate> implements AffiliateService {
@@ -48,7 +48,9 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
@Resource
private SubscriptionInfoMapper subscriptionInfoMapper;
@Resource
private AffiliateIncomeMapper affiliateIncomeMapper;
private ReferralService referralService;
@Resource
private ReferralMapper referralMapper;
@Resource
private StripeService stripeService;
@Resource
@@ -75,7 +77,7 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
// 邮件通知审批者
String merchantEmail = "kimwong@code-create.com.hk";
String developer = "xupei3360@163.com";
String[] receiverEmail = {merchantEmail, developer};
String[] receiverEmail = {/*merchantEmail, */developer};
SendEmailUtil.affiliateEmailReminder(receiverEmail, new AffiliateEmailParamsDTO(userHolder.getUsername(), promotionMethod), "new");
// emailService.affiliateEmailReminder(Arrays.asList(/*merchantEmail,*/ developer), new AffiliateEmailParamsDTO(userHolder.getUsername(), promotionMethod), "new");
}else {
@@ -88,11 +90,16 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
log.info("parameter => {}", affiliateQueryDTO.toString());
int offset = (affiliateQueryDTO.getPage() - 1) * affiliateQueryDTO.getSize();
String orderBy = affiliateQueryDTO.getOrderBy() == null ? "create_time" :
affiliateQueryDTO.getOrderBy().equals("id") ? "id" :
affiliateQueryDTO.getOrderBy().equals("totalIncome") ? "total_earnings" :
"create_time";
List<AffiliateVO> affiliateList = baseMapper.getAffiliateList(affiliateQueryDTO.getStatus(),
affiliateQueryDTO.getStartTime(),
affiliateQueryDTO.getEndTime(),
affiliateQueryDTO.getOrder(),
affiliateQueryDTO.getAffiliateId(),
orderBy,
affiliateQueryDTO.getSize(),
offset
);
@@ -133,17 +140,17 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
qw.eq("account_id", accountId);
Affiliate affiliate = baseMapper.selectOne(qw);
AffiliateVO affiliateVO = CopyUtil.copyObject(affiliate, AffiliateVO.class);
affiliateVO.setLinkViewCount(getAffiliateLinkViewCount(affiliate.getId()));
return affiliateVO;
}
public double[] getPersonalMonthlyIncome(int year){
public BigDecimal[] getPersonalMonthlyIncome(int year){
Long accountId = UserContext.getUserHolder().getId();
List<Map<String, Object>> personalMonthlyIncome = affiliateIncomeMapper.getPersonalMonthlyIncome(accountId, year);
double[] commissions = new double[12];
List<Map<String, Object>> personalMonthlyIncome = referralMapper.getPersonalMonthlyIncome(accountId, year);
BigDecimal[] commissions = new BigDecimal[12];
Arrays.fill(commissions, BigDecimal.ZERO);
personalMonthlyIncome.forEach(income -> {
int month = Integer.parseInt(income.get("yearMonth").toString());
commissions[month-1] = (double)income.get("totalCommission");
commissions[month-1] = (BigDecimal) income.get("totalCommission");
});
return commissions;
@@ -206,10 +213,14 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
String currentTime = LocalDateTime.now().toString();
// 1、查上次更新之后有无新订单
QueryWrapper<PaymentInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(PaymentInfo::getTradeState, "paid")
.and(wrapper -> wrapper
.eq(PaymentInfo::getType,"new")
.or()
.eq(PaymentInfo::getType, "manual"));
if (!StringUtil.isNullOrEmpty(lastTime)){
queryWrapper.gt("create_time", lastTime);
queryWrapper.lambda().gt(PaymentInfo::getCreateTime, lastTime);
}
queryWrapper.eq("type","new").eq("trade_state", "paid");
List<PaymentInfo> paymentInfos = paymentInfoService.getBaseMapper().selectList(queryWrapper);
if (!paymentInfos.isEmpty()){
@@ -232,33 +243,41 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
log.info("结算订单id为{}的佣金", orderInfo.getId());
// 3、若有, 直接更新affiliate的所得
Affiliate affiliate = baseMapper.selectById(account.getInvitationCode());
if (Objects.isNull(affiliate)){
log.error("未知affiliate Id:{}, 关联订单:{}", account.getInvitationCode(), orderInfo.getOrderNo());
return;
}
Float payerTotal = paymentInfo.getPayerTotal();
if (payerTotal > 0){
// 分配新用户首次订阅所付费用 预设的佣金比例 作为佣金
// 逻辑修改。只有当该笔referral被审批通过后才被计算为推广者的佣金
BigDecimal commission = BigDecimal.valueOf(payerTotal).multiply(BigDecimal.valueOf(affiliate.getCommissionPercent() / 100));
BigDecimal monthlyEarning = BigDecimal.valueOf(affiliate.getMonthlyEarnings()).add(commission);
/*BigDecimal monthlyEarning = BigDecimal.valueOf(affiliate.getMonthlyEarnings()).add(commission);
BigDecimal unpaidEarnings = BigDecimal.valueOf(affiliate.getUnpaidEarnings()).add(commission);
int visits = affiliate.getVisits() + 1;
affiliate.setMonthlyEarnings(monthlyEarning.floatValue());
affiliate.setUnpaidEarnings(unpaidEarnings.floatValue());
affiliate.setVisits(visits);
affiliate.setUpdateTime(LocalDateTime.now());
baseMapper.updateById(affiliate);
baseMapper.updateById(affiliate);*/
orderInfo.setIsCommissionCalculated((byte)1);
// 4、添加到t_affiliate_income
AffiliateIncome affiliateIncome = new AffiliateIncome();
affiliateIncome.setAffiliateId(affiliate.getId());
affiliateIncome.setAffiliateAccountId(affiliate.getAccountId());
affiliateIncome.setInviteeAccountId(accountId);
affiliateIncome.setAmount(payerTotal);
affiliateIncome.setPaymentInfoId(paymentInfo.getId());
affiliateIncome.setPaymentTime(paymentInfo.getCreateTime());
affiliateIncome.setCommission(commission.floatValue());
affiliateIncome.setCreateTime(LocalDateTime.now());
affiliateIncomeMapper.insert(affiliateIncome);
// 4、添加到referral
Referral referral = new Referral();
referral.setAffiliateId(affiliate.getId());
referral.setAffiliateAccountId(affiliate.getAccountId());
referral.setInviteeAccountId(accountId);
referral.setAmount(BigDecimal.valueOf(payerTotal));
referral.setPaymentInfoId(paymentInfo.getId());
referral.setPaymentTime(paymentInfo.getCreateTime());
referral.setCommission(BigDecimal.valueOf(commission.floatValue()));
referral.setCommissionPercent(affiliate.getCommissionPercent());
referral.setStatus("Pending");
referral.setCreateTime(LocalDateTime.now());
int insert = referralService.getBaseMapper().insert(referral);
log.info("向Referral中插入 {} 条数据", insert);
}
}
orderInfo.setIsFirstSubscription((byte)1);
@@ -279,6 +298,30 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
return redisUtil.getAffiliateLinkViewCount(affiliateId);
}
public void syncLinkViewCountToDB() {
// 1、获取当前所有激活状态的affiliate
List<Affiliate> affiliateList = baseMapper.selectList(new QueryWrapper<Affiliate>().lambda().eq(Affiliate::getStatus, "Active"));
// 2、获取redis中各自的viewCount
for (Affiliate affiliate : affiliateList){
String redisKey = AFFILIATE_LINK_VIEW_KEY + affiliate.getId();
// 原子性获取并重置为0
Long redisCount = redisUtil.getAndSetKey(redisKey, 0L);
if (redisCount != null && redisCount > 0) {
// 累加到数据库
baseMapper.update(
null,
new UpdateWrapper<Affiliate>()
.setSql("visits = visits + " + redisCount)
.set("update_time", LocalDateTime.now())
.eq("id", affiliate.getId())
);
}
}
}
// 查看每个affiliate带来收入的详情
@Override
public IPage<AffiliateInvitationDetailsVO> getEachAffiliateGeneratedRevenue(AffiliateQueryDTO affiliateQueryDTO) {
@@ -286,14 +329,14 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
throw new BusinessException("Please specify the affiliate ID.", ResultEnum.PROMPT.getCode());
}
QueryWrapper<AffiliateIncome> affiliateIncomeQueryWrapper = new QueryWrapper<>();
affiliateIncomeQueryWrapper
QueryWrapper<Referral> referralQueryWrapper = new QueryWrapper<>();
referralQueryWrapper
.gt(!StringUtils.isNullOrEmpty(affiliateQueryDTO.getStartTime()), "create_time", affiliateQueryDTO.getStartTime())
.lt(!StringUtils.isNullOrEmpty(affiliateQueryDTO.getEndTime()), "create_time", affiliateQueryDTO.getEndTime())
.eq(!Objects.isNull(affiliateQueryDTO.getAffiliateId()), "affiliate_id", affiliateQueryDTO.getAffiliateId())
.orderByDesc(affiliateQueryDTO.getOrder().equals("DESC"), "create_time");
IPage<AffiliateIncome> affiliateIncomePage = affiliateIncomeMapper.selectPage(new Page<>(affiliateQueryDTO.getPage(), affiliateQueryDTO.getSize()), affiliateIncomeQueryWrapper);
return affiliateIncomePage.convert((Function<AffiliateIncome, AffiliateInvitationDetailsVO>) affiliateIncome -> {
IPage<Referral> affiliateIncomePage = referralMapper.selectPage(new Page<>(affiliateQueryDTO.getPage(), affiliateQueryDTO.getSize()), referralQueryWrapper);
return affiliateIncomePage.convert((Function<Referral, AffiliateInvitationDetailsVO>) affiliateIncome -> {
AffiliateInvitationDetailsVO affiliateInvitationDetailsVO = CopyUtil.copyObject(affiliateIncome, AffiliateInvitationDetailsVO.class);
affiliateInvitationDetailsVO.setAccountId(affiliateIncome.getInviteeAccountId());
affiliateInvitationDetailsVO.setUsername(accountService.getBaseMapper().selectById(affiliateIncome.getInviteeAccountId()).getUserName());
@@ -312,14 +355,16 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
month = LocalDateTime.now().getMonthValue();
}
List<Map<String, Object>> monthlyAffiliateIncome = affiliateIncomeMapper.getMonthlyAffiliateIncome(year, month);
List<Map<String, Object>> monthlyAffiliateIncome = referralMapper.getMonthlyAffiliateIncome(year, month);
// 1、总收入(近一个月通过affiliate产生的收入),未支付的金额 affiliate表中unpaid的总和
Double totalAmount = 0.0;
Double totalCommission = 0.0;
BigDecimal totalAmount = BigDecimal.ZERO;
BigDecimal paidCommission = BigDecimal.ZERO;
BigDecimal unpaidCommission = BigDecimal.ZERO;
if (!monthlyAffiliateIncome.isEmpty()){
Map<String, Object> monthlyIncome = monthlyAffiliateIncome.get(0);
totalAmount = (Double) monthlyIncome.get("totalAmount");
totalCommission = (Double) monthlyIncome.get("totalCommission");
totalAmount = (BigDecimal) monthlyIncome.get("totalAmount");
paidCommission = (BigDecimal) monthlyIncome.get("paidCommission");
unpaidCommission = (BigDecimal) monthlyIncome.get("unpaidCommission");
}
// 2、本月新注册的Affiliate
@@ -329,12 +374,12 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
AffiliateEmailParamsDTO affiliateEmailParamsDTO = new AffiliateEmailParamsDTO();
affiliateEmailParamsDTO.setTotalProgramRevenue(totalAmount.toString());
affiliateEmailParamsDTO.setNewApprovedAffiliates(count.toString());
affiliateEmailParamsDTO.setUnpaidEarnings(totalCommission.toString());
affiliateEmailParamsDTO.setPaidEarnings("0");
affiliateEmailParamsDTO.setUnpaidEarnings(String.valueOf(unpaidCommission));
affiliateEmailParamsDTO.setPaidEarnings(String.valueOf(paidCommission));
String merchantEmail = "kimwong@code-create.com.hk";
String developer = "xupei3360@163.com";
String[] receiverEmail = {merchantEmail, developer};
String[] receiverEmail = {/*merchantEmail,*/ developer};
// 邮件通知
SendEmailUtil.affiliateEmailReminder(receiverEmail, affiliateEmailParamsDTO, "summary");
// emailService.affiliateEmailReminder(Arrays.asList(/*merchantEmail,*/ developer), affiliateEmailParamsDTO, "summary");

View File

@@ -0,0 +1,116 @@
package com.ai.da.service.impl;
import com.ai.da.mapper.primary.ReferralMapper;
import com.ai.da.mapper.primary.entity.Affiliate;
import com.ai.da.mapper.primary.entity.Referral;
import com.ai.da.model.dto.EditReferralDTO;
import com.ai.da.model.dto.ReferralPageQueryDTO;
import com.ai.da.model.vo.ReferralPageQueryVO;
import com.ai.da.service.AffiliateService;
import com.ai.da.service.ReferralService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.YearMonth;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@Slf4j
@Service
public class ReferralServiceImpl extends ServiceImpl<ReferralMapper, Referral> implements ReferralService {
@Resource
private AffiliateService affiliateService;
// Referral Recap
// 当有用户通过链接成功订阅AiDA后。这里应该要出现一笔待支付给affiliate的记录
// 每一笔交易需要管理员Accept才能被算为佣金有审批这个过程
// 查询所有referral (分页查)
// 允许按推广人用户名、记录产生时间、记录状态分页查询
public IPage<ReferralPageQueryVO> queryByPage(ReferralPageQueryDTO referralPageQueryDTO) {
// 1. 构建查询条件
QueryWrapper<ReferralPageQueryVO> queryWrapper = new QueryWrapper<>();
if (referralPageQueryDTO.getAffiliateId() != null && referralPageQueryDTO.getAffiliateId() != 0L) {
queryWrapper.eq("r.affiliate_id", referralPageQueryDTO.getAffiliateId());
}
if (!StringUtil.isNullOrEmpty(referralPageQueryDTO.getStartTime())) {
queryWrapper.gt("r.create_time", referralPageQueryDTO.getStartTime());
}
if (!StringUtil.isNullOrEmpty(referralPageQueryDTO.getEndTime())) {
queryWrapper.lt("r.create_time", referralPageQueryDTO.getEndTime());
}
if (!StringUtil.isNullOrEmpty(referralPageQueryDTO.getStatus()) && !"All Types".equals(referralPageQueryDTO.getStatus())) {
queryWrapper.eq("r.status", referralPageQueryDTO.getStatus());
}
queryWrapper.eq("r.is_deleted", 0);
// 2. 执行分页查询(返回的是 ReferralVO
Page<ReferralPageQueryVO> page = new Page<>(referralPageQueryDTO.getPage(), referralPageQueryDTO.getSize());
return baseMapper.selectReferralWithAffiliate(page, queryWrapper);
}
// 编辑referral
@Transactional(rollbackFor = Exception.class)
public void editReferral(EditReferralDTO editReferralDTO){
Referral referral = baseMapper.selectById(editReferralDTO.getId());
if (Objects.nonNull(editReferralDTO.getAmount()) && editReferralDTO.getAmount().compareTo(referral.getCommission()) != 0){
log.info("设置referral id为{} 原佣金 {} 设置为 {}", referral.getId(), referral.getAffiliateId(), editReferralDTO.getAmount());
referral.setCommission(editReferralDTO.getAmount());
referral.setUpdateTime(LocalDateTime.now());
}
if (!StringUtil.isNullOrEmpty(editReferralDTO.getStatus()) && !editReferralDTO.getStatus().equals(referral.getStatus())){
// 如果该笔佣金审批通过,则自动转为未支付状态
if (editReferralDTO.getStatus().equals("Accept")){
editReferralDTO.setStatus("Unpaid");
}
log.info("设置referral id为{} 原状态 {} 设置为 {}", referral.getId(), referral.getStatus(), editReferralDTO.getStatus());
referral.setStatus(editReferralDTO.getStatus());
referral.setUpdateTime(LocalDateTime.now());
}
boolean b = updateById(referral);
if (b){
Affiliate affiliate = affiliateService.getBaseMapper().selectById(referral.getAffiliateId());
// 如果修改了referral的状态则需要同步修改affiliate已支付或未支付的金额总数
// 在平台的总未支付
BigDecimal unpaid = baseMapper.sumAmount(referral.getAffiliateId(), Collections.singletonList("Unpaid"), null, null);
// 已支付和未支付的值肯定是同时发生变化
if (BigDecimal.valueOf(affiliate.getUnpaidEarnings()).compareTo(unpaid) != 0){
LocalDateTime start = YearMonth.now().atDay(1).atStartOfDay(); // 本月第一天 00:00:00
LocalDateTime end = YearMonth.now().atEndOfMonth().atTime(23, 59, 59);
// 本月的总收入(包括已支付和未支付)
BigDecimal monthlyEarning = baseMapper.sumAmount(referral.getAffiliateId(), Arrays.asList("Unpaid", "Paid"), start, end);
// 在平台的总收入
BigDecimal totalEarning = baseMapper.sumAmount(referral.getAffiliateId(), Arrays.asList("Unpaid", "Paid"), null, null);
affiliate.setUnpaidEarnings(Objects.nonNull(unpaid) ? unpaid.floatValue() : 0);
affiliate.setMonthlyEarnings(Objects.nonNull(monthlyEarning) ? monthlyEarning.floatValue() : 0);
affiliate.setTotalEarnings(Objects.nonNull(totalEarning) ? totalEarning.floatValue() : 0);
affiliate.setUpdateTime(LocalDateTime.now());
affiliateService.getBaseMapper().updateById(affiliate);
}
}
}
// 批量删除
public void deleteReferral(List<Long> idList){
if (!idList.isEmpty()){
baseMapper.batchDeleteByIds(idList);
}
}
}