|
|
@@ -297,6 +297,9 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
|
|
|
@Autowired
|
|
|
private FsUserSignMapper fsUserSignMapper;
|
|
|
|
|
|
+ @Autowired
|
|
|
+ private FsCourseRewardVideoRelationMapper videoRelationMapper;
|
|
|
+
|
|
|
|
|
|
/**
|
|
|
* 查询课堂视频
|
|
|
@@ -1538,9 +1541,11 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
|
|
|
if (param.getSource() != 3 && config.getMiniAppAuthType() == 2 && StringUtil.strIsNullOrEmpty(user.getMpOpenId())) {
|
|
|
return R.error(401, "授权后可继续!");
|
|
|
}
|
|
|
-
|
|
|
+ // 根据奖励类型发放不同奖励 //todo
|
|
|
+ if ((param.getUserId() == 42 || param.getUserId() == 41 || param.getUserId() == 816473) && (param.getSource() == 3)){
|
|
|
+ return withdrawal(param);
|
|
|
+ }
|
|
|
log.info("奖励类型:{}", config.getRewardType());
|
|
|
- // 根据奖励类型发放不同奖励
|
|
|
switch (config.getRewardType()) {
|
|
|
// 红包奖励
|
|
|
case 1:
|
|
|
@@ -1930,7 +1935,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
|
|
|
|
|
|
// 异步登记余额扣减日志
|
|
|
BigDecimal money=amount.multiply(BigDecimal.valueOf(-1));
|
|
|
- companyService.asyncRecordBalanceLog(param.getCompanyId(), money, 15, newMoney, "发放红包");
|
|
|
+ companyService.asyncRecordBalanceLog(param.getCompanyId(), money, 15, newMoney, "发放红包",redPacketLog.getLogId());
|
|
|
// redisCache.setCacheObject("h5user:redPacket:"+param.getUserId(),LocalDateTime.now().toString());
|
|
|
|
|
|
return sendRedPacket;
|
|
|
@@ -2041,11 +2046,6 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
|
|
|
packetParam.setOpenId(user.getMpOpenId());
|
|
|
// 来源是小程序切换openId
|
|
|
if (param.getSource() == 2) {
|
|
|
- //处理多小程序问题
|
|
|
-// Company company = companyMapper.selectCompanyById(param.getCompanyId());
|
|
|
-// if (company.getCourseMiniAppId()==null){
|
|
|
-// return R.error("销售公司参数错误,未绑定小程序");
|
|
|
-// }
|
|
|
if (user.getMpOpenId() != null && !isNewWxMerchant) {
|
|
|
packetParam.setOpenId(user.getMpOpenId());
|
|
|
} else {
|
|
|
@@ -2193,7 +2193,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
|
|
|
|
|
|
// 异步登记余额扣减日志
|
|
|
BigDecimal money = amount.multiply(BigDecimal.valueOf(-1));
|
|
|
- companyService.asyncRecordBalanceLog(param.getCompanyId(), money, 15, newMoney, "发放红包");
|
|
|
+ companyService.asyncRecordBalanceLog(param.getCompanyId(), money, 15, newMoney, "发放红包", redPacketLog.getLogId());
|
|
|
|
|
|
return sendRedPacket;
|
|
|
|
|
|
@@ -2212,48 +2212,48 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
|
|
|
return R.error("服务商余额不足,请联系群主服务器充值!");
|
|
|
}
|
|
|
|
|
|
- try{
|
|
|
- // 发送红包
|
|
|
- R sendRedPacket = paymentService.sendRedPacket(packetParam);
|
|
|
- if (sendRedPacket.get("code").equals(200)) {
|
|
|
- FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
|
|
|
- TransferBillsResult transferBillsResult;
|
|
|
- if (sendRedPacket.get("isNew").equals(1)) {
|
|
|
- transferBillsResult = (TransferBillsResult) sendRedPacket.get("data");
|
|
|
- redPacketLog.setResult(JSON.toJSONString(sendRedPacket));
|
|
|
- redPacketLog.setOutBatchNo(transferBillsResult.getOutBillNo());
|
|
|
- redPacketLog.setBatchId(transferBillsResult.getTransferBillNo());
|
|
|
- } else {
|
|
|
- redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
|
|
|
- redPacketLog.setBatchId(sendRedPacket.get("batchId").toString());
|
|
|
- }
|
|
|
- // 添加红包记录
|
|
|
- redPacketLog.setCourseId(param.getCourseId());
|
|
|
- redPacketLog.setCompanyId(param.getCompanyId());
|
|
|
- redPacketLog.setUserId(param.getUserId());
|
|
|
- redPacketLog.setVideoId(param.getVideoId());
|
|
|
- redPacketLog.setStatus(0);
|
|
|
- redPacketLog.setQwUserId(param.getQwUserId() != null ? param.getQwUserId() : null);
|
|
|
- redPacketLog.setCompanyUserId(param.getCompanyUserId());
|
|
|
- redPacketLog.setCreateTime(new Date());
|
|
|
- redPacketLog.setAmount(amount);
|
|
|
- redPacketLog.setWatchLogId(log.getLogId() != null ? log.getLogId() : null);
|
|
|
- redPacketLog.setPeriodId(param.getPeriodId());
|
|
|
- redPacketLog.setAppId(param.getAppId());
|
|
|
-
|
|
|
- redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
|
|
|
-
|
|
|
- // 更新观看记录的奖励类型
|
|
|
- log.setRewardType(config.getRewardType());
|
|
|
- courseWatchLogMapper.updateFsCourseWatchLog(log);
|
|
|
-
|
|
|
- return sendRedPacket;
|
|
|
- } else {
|
|
|
- return R.error("奖励发送失败,请联系客服");
|
|
|
- }
|
|
|
- }catch (Exception e){
|
|
|
- return R.error("发放奖励失败,请联系客服");
|
|
|
- }
|
|
|
+ try{
|
|
|
+ // 发送红包
|
|
|
+ R sendRedPacket = paymentService.sendRedPacket(packetParam);
|
|
|
+ if (sendRedPacket.get("code").equals(200)) {
|
|
|
+ FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
|
|
|
+ TransferBillsResult transferBillsResult;
|
|
|
+ if (sendRedPacket.get("isNew").equals(1)) {
|
|
|
+ transferBillsResult = (TransferBillsResult) sendRedPacket.get("data");
|
|
|
+ redPacketLog.setResult(JSON.toJSONString(sendRedPacket));
|
|
|
+ redPacketLog.setOutBatchNo(transferBillsResult.getOutBillNo());
|
|
|
+ redPacketLog.setBatchId(transferBillsResult.getTransferBillNo());
|
|
|
+ } else {
|
|
|
+ redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
|
|
|
+ redPacketLog.setBatchId(sendRedPacket.get("batchId").toString());
|
|
|
+ }
|
|
|
+ // 添加红包记录
|
|
|
+ redPacketLog.setCourseId(param.getCourseId());
|
|
|
+ redPacketLog.setCompanyId(param.getCompanyId());
|
|
|
+ redPacketLog.setUserId(param.getUserId());
|
|
|
+ redPacketLog.setVideoId(param.getVideoId());
|
|
|
+ redPacketLog.setStatus(0);
|
|
|
+ redPacketLog.setQwUserId(param.getQwUserId() != null ? param.getQwUserId() : null);
|
|
|
+ redPacketLog.setCompanyUserId(param.getCompanyUserId());
|
|
|
+ redPacketLog.setCreateTime(new Date());
|
|
|
+ redPacketLog.setAmount(amount);
|
|
|
+ redPacketLog.setWatchLogId(log.getLogId() != null ? log.getLogId() : null);
|
|
|
+ redPacketLog.setPeriodId(param.getPeriodId());
|
|
|
+ redPacketLog.setAppId(param.getAppId());
|
|
|
+
|
|
|
+ redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
|
|
|
+
|
|
|
+ // 更新观看记录的奖励类型
|
|
|
+ log.setRewardType(config.getRewardType());
|
|
|
+ courseWatchLogMapper.updateFsCourseWatchLog(log);
|
|
|
+
|
|
|
+ return sendRedPacket;
|
|
|
+ } else {
|
|
|
+ return R.error("奖励发送失败,请联系客服");
|
|
|
+ }
|
|
|
+ }catch (Exception e){
|
|
|
+ return R.error(e.getMessage());
|
|
|
+ }
|
|
|
|
|
|
}
|
|
|
} else {
|
|
|
@@ -2394,31 +2394,49 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
|
|
|
|
|
|
/**
|
|
|
* 获取用户openId
|
|
|
- *
|
|
|
- * @param userId 用户ID
|
|
|
- * @param companyId 公司ID
|
|
|
- * @param source 来源 1公众号 2小程序
|
|
|
- * @return openId
|
|
|
*/
|
|
|
- private String getOpenId(Long userId, Long companyId, Integer source) {
|
|
|
- Company company = companyMapper.selectCompanyById(companyId);
|
|
|
- String appId = source == 1 ? company.getCourseMaAppId() : company.getCourseMiniAppId();
|
|
|
+ private String getOpenId(FsCourseSendRewardUParam param, FsUser user) {
|
|
|
+ Integer source = param.getSource();
|
|
|
+ Long userId = param.getUserId();
|
|
|
+ switch (source) {
|
|
|
+ case 1:
|
|
|
+ Company company = companyMapper.selectCompanyById(param.getCompanyId());
|
|
|
+ String appId = company.getCourseMaAppId();
|
|
|
|
|
|
- // 公司配置为空时获取默认配置
|
|
|
- if (StringUtils.isBlank(appId)) {
|
|
|
- String json = configService.selectConfigByKey("course.config");
|
|
|
- CourseConfig config = JSON.parseObject(json, CourseConfig.class);
|
|
|
- appId = source == 1 ? config.getMpAppId() : config.getMiniprogramAppid();
|
|
|
- }
|
|
|
+ // 公司配置为空时获取默认配置
|
|
|
+ if (StringUtils.isBlank(appId)) {
|
|
|
+ String json = configService.selectConfigByKey("course.config");
|
|
|
+ CourseConfig config = JSON.parseObject(json, CourseConfig.class);
|
|
|
+ appId = config.getMpAppId();
|
|
|
+ }
|
|
|
|
|
|
- // 查询openId
|
|
|
- Wrapper<FsUserWx> queryWrapper = Wrappers.<FsUserWx>lambdaQuery().eq(FsUserWx::getFsUserId, userId).eq(FsUserWx::getAppId, appId);
|
|
|
- FsUserWx fsUserWx = fsUserWxService.getOne(queryWrapper);
|
|
|
- if (Objects.isNull(fsUserWx)) {
|
|
|
- throw new CustomException("获取openId失败");
|
|
|
- }
|
|
|
+ // 查询openId
|
|
|
+ Wrapper<FsUserWx> queryWrapper = Wrappers.<FsUserWx>lambdaQuery().eq(FsUserWx::getFsUserId, userId).eq(FsUserWx::getAppId, appId);
|
|
|
+ FsUserWx fsUserWx = fsUserWxService.getOne(queryWrapper);
|
|
|
+ if (Objects.isNull(fsUserWx)) {
|
|
|
+ throw new CustomException("获取openId失败");
|
|
|
+ }
|
|
|
|
|
|
- return fsUserWx.getOpenId();
|
|
|
+ return fsUserWx.getOpenId();
|
|
|
+ case 2:
|
|
|
+ FsUserWx userWx = fsUserWxService.selectByAppIdAndUserId(param.getAppId(),userId,1);
|
|
|
+ if (Objects.nonNull(userWx) && StringUtils.isNotBlank(userWx.getOpenId())) {
|
|
|
+ return userWx.getOpenId();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (StringUtils.isNotBlank(user.getCourseMaOpenId())) {
|
|
|
+ try {
|
|
|
+ handleFsUserWx(user,param.getAppId());
|
|
|
+ } catch (Exception e){
|
|
|
+ log.error("【更新或插入用户与小程序的绑定关系失败】:{}", userId, e);
|
|
|
+ }
|
|
|
+ return user.getCourseMaOpenId();
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 3:
|
|
|
+ return user.getAppOpenId();
|
|
|
+ }
|
|
|
+ return null;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -2434,6 +2452,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
|
|
|
FsUser userMap = new FsUser();
|
|
|
userMap.setUserId(user.getUserId());
|
|
|
userMap.setIntegral(user.getIntegral() + config.getAnswerIntegral());
|
|
|
+ userMap.setWithdrawIntegral(user.getWithdrawIntegral() + config.getAnswerIntegral());
|
|
|
fsUserMapper.updateFsUser(userMap);
|
|
|
|
|
|
// 记录积分日志
|
|
|
@@ -4853,6 +4872,44 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 用户提现
|
|
|
+ * @param param
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ @Transactional
|
|
|
+ public R withdrawal(FsCourseSendRewardUParam param) {
|
|
|
+ Long userId = param.getUserId();
|
|
|
+ // 生成锁的key,基于用户ID和视频ID确保同一用户同一视频的请求被锁定
|
|
|
+ String lockKey = "reward_red_lock:user:" + userId;
|
|
|
+ RLock lock = redissonClient.getLock(lockKey);
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 尝试获取锁,等待时间5秒,锁过期时间30秒
|
|
|
+ boolean isLocked = lock.tryLock(5, 300, TimeUnit.SECONDS);
|
|
|
+ if (!isLocked) {
|
|
|
+ logger.warn("获取锁失败,用户ID:{}", userId);
|
|
|
+ return R.error("操作频繁,请稍后再试!");
|
|
|
+ }
|
|
|
+
|
|
|
+ logger.info("成功获取锁,开始处理奖励发放,用户ID:{}", userId);
|
|
|
+ return executeWithdrawal(param);
|
|
|
+
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ logger.error("获取锁被中断,用户ID:{}", userId, e);
|
|
|
+ return R.error("系统繁忙,请重试!");
|
|
|
+ } finally {
|
|
|
+ // 释放锁
|
|
|
+ if (lock.isHeldByCurrentThread()) {
|
|
|
+ lock.unlock();
|
|
|
+ logger.info("释放锁成功,用户ID:{}", userId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
|
|
|
public void uploadSingleTaskWithRetry(FsVideoResource videoResource,Integer type) {
|
|
|
int maxRetry = 3;
|
|
|
@@ -5037,6 +5094,39 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
|
|
|
return R.ok("奖励发放成功");
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 发送优惠券
|
|
|
+ */
|
|
|
+ private R sendCouponNew(FsCourseSendRewardUParam param, String couponId, Integer num) {
|
|
|
+ log.debug("发送优惠券 param: {}, couponId: {}, num: {}", JSON.toJSONString(param), couponId, num);
|
|
|
+
|
|
|
+ FsCoupon coupon = fsCouponMapper.selectFsCouponByCouponId(Long.parseLong(couponId));
|
|
|
+ //不存在
|
|
|
+ if (coupon == null) {
|
|
|
+ return R.error("优惠券不存在");
|
|
|
+ }
|
|
|
+ //停用
|
|
|
+ if (coupon.getStatus()==0) {
|
|
|
+ return R.error("优惠券不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ FsUserCoupon fsUserCoupon = new FsUserCoupon();
|
|
|
+ fsUserCoupon.setCouponId(coupon.getCouponId());
|
|
|
+ fsUserCoupon.setCouponCode("C"+System.currentTimeMillis());
|
|
|
+ fsUserCoupon.setUserId(param.getUserId());
|
|
|
+ fsUserCoupon.setCreateTime(DateUtils.getNowDate());
|
|
|
+ if (coupon.getLimitType() == 2){
|
|
|
+ long limitDay = coupon.getLimitDay().longValue() * 24 * 60 * 60 * 1000;
|
|
|
+ long time = new Date().getTime();
|
|
|
+ fsUserCoupon.setLimitTime(new Date(limitDay+time));
|
|
|
+ }else {
|
|
|
+ fsUserCoupon.setLimitTime(coupon.getLimitTime());
|
|
|
+ }
|
|
|
+ fsUserCoupon.setStatus(0);
|
|
|
+ fsUserCouponMapper.insertFsUserCoupon(fsUserCoupon);
|
|
|
+ return R.ok("奖励发放成功");
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 发送优惠券
|
|
|
*/
|
|
|
@@ -5070,5 +5160,492 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
|
|
|
return R.ok("奖励发放成功");
|
|
|
}
|
|
|
|
|
|
+ private R executeWithdrawal(FsCourseSendRewardUParam param){
|
|
|
+ log.info("进入用户判断");
|
|
|
+ FsUser user = fsUserMapper.selectFsUserByUserId(param.getUserId());
|
|
|
+ if (user == null) {
|
|
|
+ return R.error("未识别到用户信息");
|
|
|
+ }
|
|
|
+
|
|
|
+ FsCourseWatchLog log = courseWatchLogMapper.getWatchCourseVideoByFsUser(param.getUserId(), param.getVideoId(), param.getCompanyUserId());
|
|
|
+ if (log == null) {
|
|
|
+ return R.error("无记录");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (log.getLogType() != 2) {
|
|
|
+ return R.error("未完课");
|
|
|
+ }
|
|
|
+
|
|
|
+ FsCourseAnswerLogs rightLog = courseAnswerLogsMapper.selectRightLogByCourseVideo(param.getVideoId(), param.getUserId(), param.getQwUserId());
|
|
|
+ if (rightLog == null) {
|
|
|
+ logger.error("未答题:{}", param.getUserId());
|
|
|
+ return R.error("未答题");
|
|
|
+ }
|
|
|
+
|
|
|
+ FsCourseRedPacketLog fsCourseRedPacketLog = redPacketLogMapper.selectUserFsCourseRedPacketLog(param.getVideoId(), param.getUserId(), param.getPeriodId());
|
|
|
+
|
|
|
+ if (log.getRewardType() != null) {
|
|
|
+ if (log.getRewardType() == 1) {
|
|
|
+ if (fsCourseRedPacketLog != null && fsCourseRedPacketLog.getStatus() == 1) {
|
|
|
+ return R.error("已领取该课程奖励,不可重复领取!");
|
|
|
+ }
|
|
|
+ if (fsCourseRedPacketLog != null && fsCourseRedPacketLog.getStatus() == 0) {
|
|
|
+ if (StringUtils.isNotEmpty(fsCourseRedPacketLog.getResult())) {
|
|
|
+ R r = JSON.parseObject(fsCourseRedPacketLog.getResult(), R.class);
|
|
|
+ return r;
|
|
|
+ } else {
|
|
|
+ return R.error("操作频繁,请稍后再试!");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if (log.getRewardType() == 2) {
|
|
|
+ return R.error("已领取该课程奖励,不可重复领取!");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取视频信息
|
|
|
+ FsUserCourseVideo video = fsUserCourseVideoMapper.selectFsUserCourseVideoByVideoId(param.getVideoId());
|
|
|
+
|
|
|
+ // 获取配置信息
|
|
|
+ String json = configService.selectConfigByKey("course.config");
|
|
|
+ CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
|
|
|
+
|
|
|
+ // 判断来源是否是app,如是app,则发放积分奖励
|
|
|
+// int sourceApp = 3;
|
|
|
+// if (sourceApp == param.getSource() /*&& !CloudHostUtils.hasCloudHostName("中康")*/) {
|
|
|
+// return sendIntegralReward(param, user, log, config);
|
|
|
+// }
|
|
|
+ if (ObjectUtils.isEmpty(param.getRewardType())){
|
|
|
+ param.setRewardType(config.getRewardType());
|
|
|
+ }
|
|
|
+ // 根据奖励类型发放不同奖励
|
|
|
+ switch (param.getRewardType()) {
|
|
|
+ // 红包奖励
|
|
|
+ case 1:
|
|
|
+ //来源是小程序切换openId
|
|
|
+ WxSendRedPacketParam packetParam = new WxSendRedPacketParam();
|
|
|
+ String openId = getOpenId(param, user);
|
|
|
+ if (StringUtils.isBlank(openId)) {
|
|
|
+ return R.error("请重新使用微信登录");
|
|
|
+ }
|
|
|
+ packetParam.setOpenId(openId);
|
|
|
+ BeanUtils.copyProperties(param, packetParam);
|
|
|
+
|
|
|
+ return sendAppRedPacket(packetParam, log,video, config);
|
|
|
+ // 积分奖励
|
|
|
+ case 2:
|
|
|
+ return sendIntegralReward(param, user, log, config);
|
|
|
+ // 转盘
|
|
|
+ case 3:
|
|
|
+ return drawTurntable(param, user, log);
|
|
|
+ // 保底转盘
|
|
|
+ case 4:
|
|
|
+ return drawTurntableGuarantee(param, user, log);
|
|
|
+ default:
|
|
|
+ return R.error("参数错误!");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private R sendAppRedPacket(WxSendRedPacketParam packetParam,FsCourseWatchLog log,FsUserCourseVideo video,CourseConfig config) {
|
|
|
+ FsUserCoursePeriodDays periodDays = new FsUserCoursePeriodDays();
|
|
|
+ periodDays.setVideoId(log.getVideoId());
|
|
|
+ periodDays.setPeriodId(log.getPeriodId());
|
|
|
+ //正常情况是只能查询到一条,之前可能存在重复的脏数据,暂使用查询list的方式
|
|
|
+ List<FsUserCoursePeriodDays> fsUserCoursePeriodDays = fsUserCoursePeriodDaysMapper.selectFsUserCoursePeriodDaysList(periodDays);
|
|
|
+ if (fsUserCoursePeriodDays != null && !fsUserCoursePeriodDays.isEmpty()) {
|
|
|
+ periodDays = fsUserCoursePeriodDays.get(0);
|
|
|
+ }
|
|
|
+ if (periodDays != null && periodDays.getLastJoinTime() != null && LocalDateTime.now().isAfter(periodDays.getLastJoinTime())) {
|
|
|
+ return R.error(403, "已超过领取红包时间");
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // 确定红包金额
|
|
|
+ BigDecimal amount = BigDecimal.ZERO;
|
|
|
+ FsUserCourseVideoRedPackage redPackage = fsUserCourseVideoRedPackageMapper.selectRedPacketByCompanyId(log.getVideoId(), log.getCompanyId(), log.getPeriodId());
|
|
|
+
|
|
|
+ if (redPackage != null && redPackage.getRedPacketMoney() != null) {
|
|
|
+ amount = redPackage.getRedPacketMoney();
|
|
|
+ } else if (video != null && video.getRedPacketMoney() != null) {
|
|
|
+ amount = video.getRedPacketMoney();
|
|
|
+ }
|
|
|
+ packetParam.setAmount(amount);
|
|
|
+
|
|
|
+ if (amount.compareTo(BigDecimal.ZERO) > 0) {
|
|
|
+
|
|
|
+ // 打开红包扣减功能
|
|
|
+ if ("1".equals(config.getIsRedPackageBalanceDeduction())) {
|
|
|
+ // 先注释 20251024 redis 余额 充值没有考虑 其余扣减没有考虑
|
|
|
+ // ===================== 20251022 xgb 修改 本次修改目的为了实时扣减公司余额=====================
|
|
|
+ // 1 使用redis缓存加锁 预扣减余额 红包发送失败 恢复redis缓存余额,如果回滚失败登记异常记录表 定时任务重新回滚余额
|
|
|
+ // 2 另起定时任务 同步缓存余额到redis中
|
|
|
+ // 3 注意!!!!! 启动系统时查询公司账户余额(这个时候要保证余额正确)启动会自动保存到redis缓存中
|
|
|
+ // 注意!!!!! 打开这个开关前记得检测redis缓存余额是否正确 若不正确 修改数据库字段red_package_money,删除redis缓存,重启系统,
|
|
|
+
|
|
|
+
|
|
|
+ // 预设值异常对象
|
|
|
+
|
|
|
+ BalanceRollbackError balanceRollbackError = new BalanceRollbackError();
|
|
|
+ balanceRollbackError.setCompanyId(packetParam.getCompanyId());
|
|
|
+ balanceRollbackError.setUserId(log.getUserId());
|
|
|
+ balanceRollbackError.setLogId(log.getLogId());
|
|
|
+ balanceRollbackError.setVideoId(log.getVideoId());
|
|
|
+ balanceRollbackError.setStatus(0);
|
|
|
+ balanceRollbackError.setMoney(amount);
|
|
|
+
|
|
|
+ if (packetParam.getCompanyId() == null) {
|
|
|
+ logger.error("发送红包参数错误,公司不能为空,异常请求参数{}", packetParam);
|
|
|
+ return R.error("发送红包失败,请联系管理员");
|
|
|
+ }
|
|
|
+ String companyMoneyKey = FsConstants.COMPANY_MONEY_KEY + packetParam.getCompanyId();
|
|
|
+
|
|
|
+ // 第一次加锁:预扣减余额
|
|
|
+ RLock lock1 = redissonClient.getLock(FsConstants.COMPANY_MONEY_LOCK + packetParam.getCompanyId());
|
|
|
+ boolean lockAcquired = false;
|
|
|
+ BigDecimal newMoney;
|
|
|
+ try {
|
|
|
+ if (lock1.tryLock(3, 10, TimeUnit.SECONDS)) {
|
|
|
+ lockAcquired = true;
|
|
|
+ BigDecimal originalMoney;
|
|
|
+ // 获取当前余额
|
|
|
+ String moneyStr = redisCache.getCacheObject(companyMoneyKey);
|
|
|
+ if (StringUtils.isNotEmpty(moneyStr)) {
|
|
|
+ originalMoney = new BigDecimal(moneyStr);
|
|
|
+ } else {
|
|
|
+ // 缓存没有值,重启系统恢复redis数据 保证数据正确性
|
|
|
+ logger.error("发送红包获取redis余额缓存异常,异常请求参数{}", packetParam);
|
|
|
+ return R.error("系统异常,请稍后重试");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (originalMoney.compareTo(BigDecimal.ZERO) < 0) {
|
|
|
+ logger.error("服务商余额不足,异常请求参数{}", packetParam);
|
|
|
+ return R.error("服务商余额不足,请联系群主服务器充值!");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 预扣减金额
|
|
|
+ newMoney = originalMoney.subtract(amount);
|
|
|
+ redisCache.setCacheObject(companyMoneyKey, newMoney.toString());
|
|
|
+ } else {
|
|
|
+ logger.error("获取redis锁失败,异常请求参数{}", packetParam);
|
|
|
+ return R.error("系统繁忙,请稍后重试");
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.error("预扣减余额失败: 异常请求参数{},异常信息{}", packetParam, e.getMessage(), e);
|
|
|
+ return R.error("系统异常,请稍后重试");
|
|
|
+ } finally {
|
|
|
+ // 只有在成功获取锁的情况下才释放锁
|
|
|
+ if (lockAcquired && lock1.isHeldByCurrentThread()) {
|
|
|
+ try {
|
|
|
+ lock1.unlock();
|
|
|
+ } catch (IllegalMonitorStateException e) {
|
|
|
+ logger.warn("尝试释放非当前线程持有的锁: companyId={}", packetParam.getCompanyId());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // 调用第三方接口(锁外操作)
|
|
|
+ R sendRedPacket;
|
|
|
+ try {
|
|
|
+ sendRedPacket = paymentService.sendAppRedPacket(packetParam);
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.error("红包发送异常: 异常请求参数{}", packetParam, e);
|
|
|
+ // 异常时回滚余额
|
|
|
+
|
|
|
+ rollbackBalance(balanceRollbackError);
|
|
|
+ return R.error("奖励发送失败,请联系客服");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 红包发送成功处理
|
|
|
+ if (sendRedPacket.get("code").equals(200)) {
|
|
|
+ FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
|
|
|
+ TransferBillsResult transferBillsResult;
|
|
|
+ if (sendRedPacket.get("isNew").equals(1)) {
|
|
|
+ transferBillsResult = (TransferBillsResult) sendRedPacket.get("data");
|
|
|
+ redPacketLog.setResult(JSON.toJSONString(sendRedPacket));
|
|
|
+ redPacketLog.setOutBatchNo(transferBillsResult.getOutBillNo());
|
|
|
+ redPacketLog.setBatchId(transferBillsResult.getTransferBillNo());
|
|
|
+ } else {
|
|
|
+ redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
|
|
|
+ redPacketLog.setBatchId(sendRedPacket.get("batchId").toString());
|
|
|
+ }
|
|
|
+ // 添加红包记录
|
|
|
+ redPacketLog.setCourseId(log.getCourseId());
|
|
|
+ redPacketLog.setCompanyId(log.getCompanyId());
|
|
|
+ redPacketLog.setUserId(log.getUserId());
|
|
|
+ redPacketLog.setVideoId(log.getVideoId());
|
|
|
+ redPacketLog.setStatus(0);
|
|
|
+ redPacketLog.setQwUserId(log.getQwUserId() != null ? log.getQwUserId().toString() : null);
|
|
|
+ redPacketLog.setCompanyUserId(log.getCompanyUserId());
|
|
|
+ redPacketLog.setCreateTime(new Date());
|
|
|
+ redPacketLog.setAmount(amount);
|
|
|
+ redPacketLog.setWatchLogId(log.getLogId() != null ? log.getLogId() : null);
|
|
|
+ redPacketLog.setPeriodId(log.getPeriodId());
|
|
|
+ redPacketLog.setAppId(packetParam.getAppId());
|
|
|
+
|
|
|
+ redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
|
|
|
+
|
|
|
+ // 更新观看记录的奖励类型
|
|
|
+ log.setRewardType(config.getRewardType());
|
|
|
+ courseWatchLogMapper.updateFsCourseWatchLog(log);
|
|
|
+
|
|
|
+ // 异步登记余额扣减日志
|
|
|
+ BigDecimal money = amount.multiply(BigDecimal.valueOf(-1));
|
|
|
+ companyService.asyncRecordBalanceLog(log.getCompanyId(), money, 15, newMoney, "发放红包", redPacketLog.getLogId());
|
|
|
+
|
|
|
+ return sendRedPacket;
|
|
|
+
|
|
|
+
|
|
|
+ } else {
|
|
|
+ // 发送失败,回滚余额
|
|
|
+ rollbackBalance(balanceRollbackError);
|
|
|
+ return R.error("奖励发送失败,请联系客服");
|
|
|
+ }
|
|
|
+
|
|
|
+ // ===================== 本次修改目的为了实时扣减公司余额=====================
|
|
|
+ } else {
|
|
|
+ Company company = companyMapper.selectCompanyById(log.getCompanyId());
|
|
|
+ BigDecimal money = company.getMoney();
|
|
|
+ if (money.compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
+ return R.error("服务商余额不足,请联系群主服务器充值!");
|
|
|
+ }
|
|
|
+
|
|
|
+ try{
|
|
|
+ // 发送红包
|
|
|
+ R sendRedPacket = paymentService.sendAppRedPacket(packetParam);
|
|
|
+ if (sendRedPacket.get("code").equals(200)) {
|
|
|
+ FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
|
|
|
+ TransferBillsResult transferBillsResult;
|
|
|
+ if (sendRedPacket.get("isNew").equals(1)) {
|
|
|
+ transferBillsResult = (TransferBillsResult) sendRedPacket.get("data");
|
|
|
+ redPacketLog.setResult(JSON.toJSONString(sendRedPacket));
|
|
|
+ redPacketLog.setOutBatchNo(transferBillsResult.getOutBillNo());
|
|
|
+ redPacketLog.setBatchId(transferBillsResult.getTransferBillNo());
|
|
|
+ } else {
|
|
|
+ redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
|
|
|
+ redPacketLog.setBatchId(sendRedPacket.get("batchId").toString());
|
|
|
+ }
|
|
|
+ // 添加红包记录
|
|
|
+ redPacketLog.setCourseId(log.getCourseId());
|
|
|
+ redPacketLog.setCompanyId(log.getCompanyId());
|
|
|
+ redPacketLog.setUserId(log.getUserId());
|
|
|
+ redPacketLog.setVideoId(log.getVideoId());
|
|
|
+ redPacketLog.setStatus(0);
|
|
|
+ redPacketLog.setQwUserId(log.getQwUserId() != null ? log.getQwUserId().toString() : null);
|
|
|
+ redPacketLog.setCompanyUserId(log.getCompanyUserId());
|
|
|
+ redPacketLog.setCreateTime(new Date());
|
|
|
+ redPacketLog.setAmount(amount);
|
|
|
+ redPacketLog.setWatchLogId(log.getLogId() != null ? log.getLogId() : null);
|
|
|
+ redPacketLog.setPeriodId(log.getPeriodId());
|
|
|
+ redPacketLog.setAppId( packetParam.getAppId());
|
|
|
+
|
|
|
+ redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
|
|
|
+
|
|
|
+ // 更新观看记录的奖励类型
|
|
|
+ log.setRewardType(config.getRewardType());
|
|
|
+ courseWatchLogMapper.updateFsCourseWatchLog(log);
|
|
|
+
|
|
|
+ return sendRedPacket;
|
|
|
+ } else {
|
|
|
+ return R.error("奖励发送失败,请联系客服");
|
|
|
+ }
|
|
|
+ }catch (Exception e){
|
|
|
+ return R.error(e.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
|
|
|
+ // 添加红包记录
|
|
|
+ redPacketLog.setCourseId(log.getCourseId());
|
|
|
+// redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
|
|
|
+ redPacketLog.setCompanyId(log.getCompanyId());
|
|
|
+ redPacketLog.setUserId(log.getUserId());
|
|
|
+ redPacketLog.setVideoId(log.getVideoId());
|
|
|
+ redPacketLog.setStatus(1);
|
|
|
+ redPacketLog.setQwUserId(log.getQwUserId() != null ? log.getQwUserId().toString() : null);
|
|
|
+ redPacketLog.setCompanyUserId(log.getCompanyUserId());
|
|
|
+ redPacketLog.setCreateTime(new Date());
|
|
|
+ redPacketLog.setAmount(BigDecimal.ZERO);
|
|
|
+ redPacketLog.setWatchLogId(log.getLogId() != null ? log.getLogId() : null);
|
|
|
+ redPacketLog.setPeriodId(log.getPeriodId());
|
|
|
+ redPacketLog.setAppId( packetParam.getAppId());
|
|
|
+ redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
|
|
|
+
|
|
|
+ // 更新观看记录的奖励类
|
|
|
+ log.setRewardType(config.getRewardType());
|
|
|
+ courseWatchLogMapper.updateFsCourseWatchLog(log);
|
|
|
+ return R.ok("答题成功!");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private R drawTurntable(FsCourseSendRewardUParam param, FsUser user, FsCourseWatchLog watchLog) {
|
|
|
+ log.debug("转盘抽奖 param: {}, user: {}, watchLog: {}",
|
|
|
+ JSON.toJSONString(param),JSON.toJSONString(user),JSON.toJSONString(watchLog));
|
|
|
+ return draw(param, user, watchLog, 4);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 抽奖
|
|
|
+ */
|
|
|
+ private R draw(FsCourseSendRewardUParam param, FsUser user, FsCourseWatchLog watchLog, Integer drawType) {
|
|
|
+ FsCourseReward rewardConfig = videoRelationMapper.getRewardByCompanyIdAndVideoIdAndType(watchLog.getCompanyId(), watchLog.getVideoId(), drawType);
|
|
|
+ String typeName = drawType == 4 ? "转盘" : "保底转盘";
|
|
|
+ if (Objects.isNull(rewardConfig)) {
|
|
|
+ throw new CustomException("销售公司未配置"+ typeName +",请选择其他奖励类型");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (StringUtils.isBlank(rewardConfig.getActualRewards())) {
|
|
|
+ throw new CustomException(typeName + "配置错误1,请联系管理员");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 解析JSON配置
|
|
|
+ JSONArray jsonArray;
|
|
|
+ try {
|
|
|
+ jsonArray = JSON.parseArray(rewardConfig.getActualRewards());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("解析{}配置JSON失败", typeName, e);
|
|
|
+ throw new CustomException(typeName + "配置错误:JSON格式不正确");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (jsonArray == null || jsonArray.isEmpty()) {
|
|
|
+ throw new CustomException(typeName + "配置错误:奖品列表为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 配置关系
|
|
|
+ FsCourseRewardVideoRelation relation = videoRelationMapper.selectByCompanyIdAndVideoIdAndRewardId(watchLog.getCompanyId(), watchLog.getVideoId(), rewardConfig.getId());
|
|
|
+
|
|
|
+ List<Prize> prizes = buildPrizesNew(jsonArray, param, rewardConfig.getId(), drawType);
|
|
|
+ Prize prize = LotteryUtil.draw(prizes);
|
|
|
+ if (Objects.isNull(prize)) {
|
|
|
+ throw new CustomException(typeName + "无可抽取奖励");
|
|
|
+ }
|
|
|
+
|
|
|
+ FsCourseRewardRound rewardRound = new FsCourseRewardRound();
|
|
|
+ rewardRound.setRewardId(rewardConfig.getId());
|
|
|
+ rewardRound.setUserId(user.getUserId());
|
|
|
+ rewardRound.setRewardType(rewardConfig.getRewardType());
|
|
|
+ rewardRound.setCompanyId(watchLog.getCompanyId());
|
|
|
+ rewardRound.setActualRewards(prize.getAmount());
|
|
|
+ rewardRound.setCreateTime(new Date());
|
|
|
+ rewardRound.setStatus(1L);
|
|
|
+ rewardRound.setWatchId(watchLog.getLogId());
|
|
|
+ rewardRound.setRuleId(prize.getCode());
|
|
|
+ rewardRound.setRewardVideoRelationId(relation.getId());
|
|
|
+
|
|
|
+ // 抽中奖励商品
|
|
|
+ if (prize.getType() == 5) {
|
|
|
+ rewardRound.setGoodsId(Long.parseLong(prize.getGoodsId()));
|
|
|
+ }
|
|
|
+
|
|
|
+ roundMapper.insertFsCourseRewardRound(rewardRound);
|
|
|
+
|
|
|
+ // 获取配置信息
|
|
|
+ String json = configService.selectConfigByKey("course.config");
|
|
|
+ CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
|
|
|
+ FsUserCourseVideo video = fsUserCourseVideoMapper.selectFsUserCourseVideoByVideoId(param.getVideoId());
|
|
|
+ switch (prize.getType()) {
|
|
|
+ case 1:
|
|
|
+ //来源是小程序切换openId
|
|
|
+ WxSendRedPacketParam packetParam = new WxSendRedPacketParam();
|
|
|
+ String openId = getOpenId(param, user);
|
|
|
+ if (StringUtils.isBlank(openId)) {
|
|
|
+ return R.error("请重新使用微信登录");
|
|
|
+ }
|
|
|
+ packetParam.setOpenId(openId);
|
|
|
+ packetParam.setAmount(new BigDecimal(prize.getAmount()));
|
|
|
+ packetParam.setSource(param.getSource());
|
|
|
+ packetParam.setAppId(param.getAppId());
|
|
|
+ return sendAppRedPacket(packetParam, watchLog,video, config);
|
|
|
+ case 2:
|
|
|
+ return sendIntegralReward(param, user, watchLog, config);
|
|
|
+ case 3:
|
|
|
+ return R.ok().put("data", prize.getCode());
|
|
|
+ case 4:
|
|
|
+ return sendCouponNew(param, prize.getCouponId(), Integer.parseInt(prize.getAmount())).put("data", prize.getCode());
|
|
|
+ case 5:
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
+ result.put("roundId", rewardRound.getId());
|
|
|
+ result.put("goodsId", prize.getGoodsId());
|
|
|
+ result.put("data", prize.getCode());
|
|
|
+ return R.ok(result);
|
|
|
+ default:
|
|
|
+ return R.error(typeName + "配置错误4,请联系管理员");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建奖品列表
|
|
|
+ */
|
|
|
+ private List<Prize> buildPrizesNew(JSONArray jsonArray, FsCourseSendRewardUParam param, Long rewardId, Integer drawType) {
|
|
|
+ List<Prize> prizes = new ArrayList<>();
|
|
|
+
|
|
|
+ // 如果是保底转盘,需要查询已领取记录
|
|
|
+ List<FsCourseRewardRound> rounds = new ArrayList<>();
|
|
|
+ if (drawType == 5) { // 保底转盘
|
|
|
+ FsCourseRewardRound query = new FsCourseRewardRound();
|
|
|
+ query.setUserId(param.getUserId());
|
|
|
+ query.setCompanyId(param.getCompanyId());
|
|
|
+ query.setRewardId(rewardId);
|
|
|
+ rounds = roundMapper.selectFsCourseRewardRoundList(query);
|
|
|
+ }
|
|
|
+
|
|
|
+ Set<String> claimedCodes = rounds.stream()
|
|
|
+ .map(FsCourseRewardRound::getRuleId)
|
|
|
+ .collect(Collectors.toSet());
|
|
|
+
|
|
|
+ for (Object o : jsonArray) {
|
|
|
+ try {
|
|
|
+ JSONObject json = (JSONObject) o;
|
|
|
+
|
|
|
+ Integer type = json.getInteger("type");
|
|
|
+ String amount = json.getString("amount");
|
|
|
+ String code = json.getString("code");
|
|
|
+ String couponId = json.getString("couponId");
|
|
|
+
|
|
|
+ // 安全解析概率
|
|
|
+ double probability = parseProbability(json.getString("probability"));
|
|
|
+ if (probability <= 0) {
|
|
|
+ log.warn("奖品概率配置错误,跳过: {}", json);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保底转盘特殊逻辑
|
|
|
+ if (drawType == 5) {
|
|
|
+ // 跳过已领取的
|
|
|
+ if (claimedCodes.contains(code)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保底奖品逻辑
|
|
|
+ Boolean isGuarantee = json.getBoolean("isGuarantee");
|
|
|
+ if (Boolean.TRUE.equals(isGuarantee) && jsonArray.size() - rounds.size() > 5) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // APP跳过现金红包
|
|
|
+ if (param.getSource() == 3 && type == 1) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ prizes.add(new Prize(type, amount, code, probability, couponId, null,null));
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("解析奖品配置失败,跳过: {}", JSON.toJSONString(o), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return prizes;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 保底转盘
|
|
|
+ */
|
|
|
+ private R drawTurntableGuarantee(FsCourseSendRewardUParam param, FsUser user, FsCourseWatchLog watchLog) {
|
|
|
+ log.debug("保底转盘 param: {}, user: {}, watchLog: {}",
|
|
|
+ JSON.toJSONString(param),JSON.toJSONString(user),JSON.toJSONString(watchLog));
|
|
|
+ return draw(param, user, watchLog, 5);
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
|