|
|
@@ -49,16 +49,14 @@ import com.fs.course.service.IFsCourseLinkService;
|
|
|
import com.fs.course.service.IFsUserCompanyBindService;
|
|
|
import com.fs.course.service.IFsUserCompanyUserService;
|
|
|
import com.fs.course.service.IFsUserCourseVideoService;
|
|
|
+import com.fs.course.utils.luckyDraw.LotteryUtil;
|
|
|
+import com.fs.course.utils.luckyDraw.Prize;
|
|
|
import com.fs.course.vo.*;
|
|
|
import com.fs.course.vo.newfs.*;
|
|
|
import com.fs.enums.ExceptionCodeEnum;
|
|
|
import com.fs.his.config.AppConfig;
|
|
|
-import com.fs.his.domain.FsUser;
|
|
|
-import com.fs.his.domain.FsUserIntegralLogs;
|
|
|
-import com.fs.his.domain.FsUserWx;
|
|
|
-import com.fs.his.mapper.FsPackageMapper;
|
|
|
-import com.fs.his.mapper.FsUserIntegralLogsMapper;
|
|
|
-import com.fs.his.mapper.FsUserMapper;
|
|
|
+import com.fs.his.domain.*;
|
|
|
+import com.fs.his.mapper.*;
|
|
|
import com.fs.his.param.WxSendRedPacketParam;
|
|
|
import com.fs.his.service.IFsStorePaymentService;
|
|
|
import com.fs.his.service.IFsUserIntegralLogsService;
|
|
|
@@ -76,11 +74,9 @@ import com.fs.qw.mapper.*;
|
|
|
import com.fs.qw.param.FsUserCourseRedPageParam;
|
|
|
import com.fs.qw.service.IQwCompanyService;
|
|
|
import com.fs.qw.service.IQwExternalContactService;
|
|
|
-import com.fs.qw.vo.QwSopCourseFinishTempSetting;
|
|
|
import com.fs.qwApi.Result.QwAddContactWayResult;
|
|
|
import com.fs.qwApi.param.QwAddContactWayParam;
|
|
|
import com.fs.qwApi.service.QwApiService;
|
|
|
-import com.fs.sop.domain.QwSopLogs;
|
|
|
import com.fs.sop.domain.SopUserLogsInfo;
|
|
|
import com.fs.sop.mapper.SopUserLogsInfoMapper;
|
|
|
import com.fs.sop.service.ISopUserLogsInfoService;
|
|
|
@@ -106,7 +102,6 @@ import org.springframework.data.redis.core.RedisTemplate;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
import org.springframework.web.bind.annotation.RequestBody;
|
|
|
-
|
|
|
import java.math.BigDecimal;
|
|
|
import java.math.RoundingMode;
|
|
|
import java.time.*;
|
|
|
@@ -136,6 +131,14 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
|
|
|
@Autowired
|
|
|
private OpenIMService openIMService;
|
|
|
@Autowired
|
|
|
+ private FsCouponMapper fsCouponMapper;
|
|
|
+ @Autowired
|
|
|
+ private FsUserCouponMapper fsUserCouponMapper;
|
|
|
+ @Autowired
|
|
|
+ private FsCourseRewardVideoRelationMapper videoRelationMapper;
|
|
|
+ @Autowired
|
|
|
+ private FsCourseRewardRoundMapper roundMapper;
|
|
|
+ @Autowired
|
|
|
private LuckyBagMapper luckyBagMapper;
|
|
|
@Autowired
|
|
|
private LuckyBagCollectRecordMapper luckyBagCollectRecordMapper;
|
|
|
@@ -4952,9 +4955,11 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
|
|
|
if (sourceApp == param.getSource() /*&& !CloudHostUtils.hasCloudHostName("中康")*/) {
|
|
|
return sendIntegralReward(param, user, log, config);
|
|
|
}
|
|
|
-
|
|
|
+ if (ObjectUtils.isEmpty(param.getRewardType())){
|
|
|
+ param.setRewardType(config.getRewardType());
|
|
|
+ }
|
|
|
// 根据奖励类型发放不同奖励
|
|
|
- switch (config.getRewardType()) {
|
|
|
+ switch (param.getRewardType()) {
|
|
|
// 红包奖励
|
|
|
case 1:
|
|
|
//来源是小程序切换openId
|
|
|
@@ -4971,18 +4976,234 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
|
|
|
// 积分奖励
|
|
|
case 2:
|
|
|
return sendIntegralReward(param, user, log, config);
|
|
|
- // 红包+积分
|
|
|
+ // 转盘
|
|
|
case 3:
|
|
|
- R sendRed = sendRedPacketRewardFsUser(param, user, log, video, config);
|
|
|
- if (!Objects.equals(sendRed.get("code"), 200)) {
|
|
|
- return sendRed;
|
|
|
- }
|
|
|
- return sendIntegralReward(param, user, log, config);
|
|
|
+ return drawTurntable(param, user, log);
|
|
|
+ // 保底转盘
|
|
|
+ case 4:
|
|
|
+ return drawTurntableGuarantee(param, user, log);
|
|
|
default:
|
|
|
return R.error("参数错误!");
|
|
|
}
|
|
|
}
|
|
|
+ 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 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);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 抽奖
|
|
|
+ */
|
|
|
+ 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 = buildPrizes(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 sendCoupon(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 R sendCoupon(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("奖励发放成功");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建奖品列表
|
|
|
+ */
|
|
|
+ private List<Prize> buildPrizes(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));
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("解析奖品配置失败,跳过: {}", JSON.toJSONString(o), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return prizes;
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * 安全解析概率值
|
|
|
+ */
|
|
|
+ private double parseProbability(String probabilityStr) {
|
|
|
+ try {
|
|
|
+ if (StringUtils.isBlank(probabilityStr)) {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 移除百分号并转换
|
|
|
+ String cleanStr = probabilityStr.replace("%", "").trim();
|
|
|
+ return Double.parseDouble(cleanStr);
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ log.error("概率格式错误: {}", probabilityStr, e);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
private R sendAppRedPacket(WxSendRedPacketParam packetParam,FsCourseWatchLog log,FsUserCourseVideo video,CourseConfig config) {
|
|
|
FsUserCoursePeriodDays periodDays = new FsUserCoursePeriodDays();
|