微信小程序直播间开发抽红包功能
发布于 4 年前 作者 chaopeng 4005 次浏览 来自 分享

1、前言

微信小程序直播间开发抽红包功能

微信小程序直播是微信官方提供的商家经营工具,商家可通过在小程序内直播实现用户互动与商品销售的闭环,无需任何的跳转,提高下单转化率,直播更是成为链接商家和消费者的重要销售渠道!

小程序直播具备评论、点赞、连麦、拍一拍等丰富的互动功能,抽奖、优惠券等高效的营销功能,以及成员管理、评论管理、推流直播、数据看板等完善商家工具。通过引入小程序直播组件,商家自有小程序可快速具备直播能力,提升经营效率。

虽然有抽奖,优惠券的营销功能,但是却没有红包功能,如果有红包功能,增加了和用户的互动,更能吸引用户留下来观看直播。其实,我们是可以自己在直播间开发红包功能的。当然,要实现这个功能,小程序要先开通直播权限,开通直播权限需满足小程序近90天内有过支付行为,如果因为这个无法开通的联系我,可以快速开通。

2、思路

说一下这个功能实现的思路,首先后台做一个录红包的菜单,字段包括主播名称、主播头像、标语(恭喜发财,大吉大利)、有效时间、红包金额、红包个数、剩余现金红包金额、剩余现金红包个数、创建时间、版本号(乐观锁),还要有一个抢红包记录表,字段包括红包id、抢到红包用户的id、抢到红包用户的名称、抢到红包用户的头像、抢到的红包金额、创建时间。然后去小程序直播后台录商品,商品路径字段填写要跳转的小程序红包页面路径,需要在后面拼接红包id参数,比如像这样,

pages/redPacket/redPacket.html?redPacketId=123456

当用户在直播页面点击该商品进入红包页面,前端就可以拿到红包id传给后台接口,查到该红包的相关信息,做各种操作了,比如生成随机金额,扣减红包金额和个数等等。这个需要主播引导用户做好抢红包的准备,然后直播间助理通过上架商品来显示红包商品。

思路很简单,代码实现起来也很简单,但是我们需要考虑几个问题,

1、抢红包就像秒杀商品一样,是拼手速的,要考虑并发,不能出现超卖(这里是超抢)的现象,不然亏的是老板的💰,就该找你聊天了。这里我们采用简单的乐观锁来解决这个问题。

2、如果乐观锁更新失败,直接返回给用户提示抢到空红包或其他友好提示也是可以的,但是如果我们增加重试机制的话,体验会更好点。这里我们采用注解的方式进行重试,默认重试3次。

3、实现

我们先来实现注解重试的功能,且看代码如下

/**
 * 定义重试机制注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public [@interface](/user/interface) ApiRetry {
    /**
     * 默认次数
     * [@return](/user/return)
     */
    int value() default 3;
}


/**
 * 定义一个重试机制切面类
 */
[@Slf](/user/Slf)4j
[@Aspect](/user/Aspect)
[@Component](/user/Component)
public class RetryAspect {

[@Pointcut](/user/Pointcut)("[@annotation](/user/annotation)(com.redPacket.common.apiIdempotent.annotation.ApiRetry)")
public void retryPointcut() {

}

[@Around](/user/Around)("retryPointcut() && [@annotation](/user/annotation)(retry)")
[@Transactional](/user/Transactional)(isolation = Isolation.READ_COMMITTED)
public Object tryAgain(ProceedingJoinPoint joinPoint, ApiRetry retry) throws Throwable {
    int count = 0;
    do {
        count++;
        try {
            return joinPoint.proceed();
        } catch (ApiRetryException e) {
            if (count > retry.value()) {
                log.error("重试失败!");
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                throw new ApiRetryException("当前用户较多,请稍后重试");
            } else {
                log.info("=====正在重试,第{}次=====",count);
            }
        }
    } while (true);
}
}
/**
 * 定义一个重试机制的异常
 */
public class ApiRetryException extends RuntimeException {
  private static final long serialVersionUID = 1L;

  private Integer status = 0;
  private String msg;

    public ApiRetryException(String msg) {
    super(msg);
    this.msg = msg;
  }

  public ApiRetryException(String msg, Throwable e) {
    super(msg, e);
    this.msg = msg;
  }

  public ApiRetryException(String msg, Integer status) {
    super(msg);
    this.msg = msg;
    this.status = status;
  }

  public ApiRetryException(String msg, Integer status, Throwable e) {
    super(msg, e);
    this.msg = msg;
    this.status = status;
  }

  public Integer getStatus() {
    return status;
  }

  public void setStatus(Integer status) {
    this.status = status;
  }

  public String getMsg() {
    return msg;
  }

  public void setMsg(String msg) {
    this.msg = msg;
  }
}
/**
 * 定义异常处理器
 */
[@RestControllerAdvice](/user/RestControllerAdvice)
public class RestExceptionHandler {
  private Logger logger = LoggerFactory.getLogger(getClass());

  /**
   * 处理重试机制异常
   */
  [@ExceptionHandler](/user/ExceptionHandler)(ApiRetryException.class)
  public JsonModel handleApiRetryException(ApiRetryException e){
    JsonModel jsonModel = new JsonModel();
    jsonModel.setStatus(e.getStatus());
    jsonModel.setMsg(e.getMsg());
    return jsonModel;
  }
}

关于红包接口就三个,

1、给前端判断是弹出抢红包的窗口还是弹出其他提示窗口(你已抢过该红包了、手慢了,红包已过期、手慢了,红包派完了)。

/**
 * 进入红包页面判断是否可以抽红包
 * [@param](/user/param) redPacketId
 * [@param](/user/param) userId
 * [@return](/user/return)
 */
[@Override](/user/Override)
public JsonModel intoRedPacket(String redPacketId, String userId) {
    ChatroomRedPacketEntity redPacket = baseMapper.selectById(redPacketId);
    if (redPacket == null) {
        throw new JsonModelException("id为【"+redPacketId+"】的红包不存在");
    }
    ChatroomRedPacketRecordEntity hasRecord = chatroomRedPacketRecordService.getOne(Wrappers.<ChatroomRedPacketRecordEntity>lambdaQuery()
            .eq(ChatroomRedPacketRecordEntity::getRedPacketId, redPacketId)
            .eq(ChatroomRedPacketRecordEntity::getUserId, userId));
    if (hasRecord != null) {
        return JsonModel.toFail(10001,"你已抢过该红包了");
    }
    redPacket = baseMapper.selectOne(Wrappers.<ChatroomRedPacketEntity>lambdaQuery()
            .eq(ChatroomRedPacketEntity::getId,redPacketId)
            .apply("date_add(create_date, interval valid_time hour) >= current_timestamp"));
    if (redPacket == null) {
            return JsonModel.toFail(10002,"手慢了,红包已过期");
    }
    if (redPacket.getCashNum()+redPacket.getCouponNum() <= 0) {
            return JsonModel.toFail(10003,"手慢了,红包派完了");
    }
    return JsonModel.toSuccess(200,"弹出抽红包窗口");
  }
    
[@ApiOperation](/user/ApiOperation)(value = "进入红包页面判断是否可以抽红包")
[@ApiImplicitParams](/user/ApiImplicitParams)({
    @ApiImplicitParam(name = "redPacketId", value = "红包id", required = true, paramType = "body", dataType = "String"),
    @ApiImplicitParam(name = "userId", value = "用户id", required = true, paramType = "body", dataType = "String")
})
[@PostMapping](/user/PostMapping)("/intoRedPacket/{redPacketId}/{userId}")
public JsonModel intoRedPacket([@PathVariable](/user/PathVariable)("redPacketId")String redPacketId,
                 [@PathVariable](/user/PathVariable)("userId")String userId) {
  return chatroomRedPacketService.intoRedPacket(redPacketId,userId); 
}

2、这个是重中之重,就是点击抢红包的接口。

/**
 * 抽红包
 * [@param](/user/param) redPacketId
 * [@param](/user/param) userId
 * [@return](/user/return)
 */
[@Override](/user/Override)
[@ApiRetry](/user/ApiRetry)
[@Transactional](/user/Transactional)(rollbackFor = Exception.class)
public JsonModel grabRedPacket(String redPacketId, String userId) {
    ChatroomRedPacketEntity redPacket = baseMapper.selectById(redPacketId);
    if (redPacket == null) {
        throw new JsonModelException("id为【"+redPacketId+"】的红包不存在");
    }
    ChatroomRedPacketRecordEntity hasRecord = chatroomRedPacketRecordService.getOne(Wrappers.<ChatroomRedPacketRecordEntity>lambdaQuery()
            .eq(ChatroomRedPacketRecordEntity::getRedPacketId, redPacketId)
            .eq(ChatroomRedPacketRecordEntity::getUserId, userId));
    if (hasRecord != null) {
        return JsonModel.toFail(10001,"你已抢过该红包了");
    }
    redPacket = baseMapper.selectOne(Wrappers.<ChatroomRedPacketEntity>lambdaQuery()
            .eq(ChatroomRedPacketEntity::getId,redPacketId)
            .apply("date_add(create_date, interval valid_time hour) >= current_timestamp"));
    if (redPacket == null) {
        return JsonModel.toFail(10002,"手慢了,红包已过期");
    }
    if (redPacket.getCashNum()+redPacket.getCouponNum() <=0) {
        return JsonModel.toFail(10003,"手慢了,红包派完了");
    }
        long money = 0;
    long restCashNum = redPacket.getRestCashNum();
        long restCashAmount = redPacket.getRestCashAmount().longValue();
    if (restCashNum >= 1) {
        restCashNum = restCashNum - 1;
        if (restCashNum == 0) {
            money = restCashAmount;
        } else {
            money = ThreadLocalRandom.current().nextInt((int) (restCashAmount / (restCashNum+1) * 2 - 1)) + 1;
        }
        restCashAmount = restCashAmount - money;
        }
    result.put("money",money);
    // 更新红包剩余个数和剩余金额
    boolean isUpdate = chatroomRedPacketService.update(Wrappers.<ChatroomRedPacketEntity>lambdaUpdate()
                .set(ChatroomRedPacketEntity::getRestCashNum,restCashNum)
                .set(ChatroomRedPacketEntity::getRestCashAmount,restCashAmount)
                .set(ChatroomRedPacketEntity::getVersion,redPacket.getVersion() + 1)
                .eq(ChatroomRedPacketEntity::getId,redPacket.getId())
                .eq(ChatroomRedPacketEntity::getVersion,redPacket.getVersion()));
            optimisticHandler(redPacket,isUpdate,userId,money);
    return JsonModel.toSuccess(result);
}

private void optimisticHandler(ChatroomRedPacketEntity redPacket,boolean isUpdate,
                                  String userId,long money)
    if (!isUpdate) {
        throw new ApiRetryException("更新失败,开始重试");
    } else {
        // 入库抽红包记录
        UserEntity user = userService.getById(userId);
        ChatroomRedPacketRecordEntity record = new ChatroomRedPacketRecordEntity();
        record.setRedPacketId(redPacket.getId().toString())
                .setUserId(userId)
                    .setAmount(new BigDecimal(money))
                    .setCreateDate(new Date())
                .setUserName(user.getNickName())
                .setUserAvatar(user.getAvatarImageId());
        chatroomRedPacketRecordService.save(record);
        // 这里写其他业务逻辑,比如给用户账上增加相应的红包金额,可以提现          
    }
  }

3、最后一个是抢红包记录列表。

/**
 * 抽红包记录
 * [@param](/user/param) redPacketId
 * [@return](/user/return)
 */
[@Override](/user/Override)
public JsonModel redPacketRecord(String redPacketId) {
    ChatroomRedPacketEntity redPacket = baseMapper.selectById(redPacketId);
    if (redPacket == null) {
        throw new JsonModelException("id为【"+redPacketId+"】的红包不存在");
    }
    Map<String,Object> result = new HashMap<>();
    //红包信息
    result.put("redPacket",redPacket);
    List<ChatroomRedPacketRecordEntity> recordList = chatroomRedPacketRecordService.list(Wrappers.<ChatroomRedPacketRecordEntity>lambdaQuery()
            .eq(ChatroomRedPacketRecordEntity::getRedPacketId, redPacketId)
            .ne(ChatroomRedPacketRecordEntity::getAmount, 0)
            .orderByDesc(ChatroomRedPacketRecordEntity::getCreateDate));
    //领红包记录
    result.put("recordList",recordList);
    return JsonModel.toSuccess(result);
  }
    
[@ApiOperation](/user/ApiOperation)(value = "抽红包记录")
[@ApiImplicitParams](/user/ApiImplicitParams)({
    @ApiImplicitParam(name = "redPacketId", value = "红包id", required = true, paramType = "body", dataType = "String"),
    @ApiImplicitParam(name = "userId", value = "用户id", required = true, paramType = "body", dataType = "String")
})
[@PostMapping](/user/PostMapping)("/redPacketRecord/{redPacketId}")
public JsonModel redPacketRecord([@PathVariable](/user/PathVariable)("redPacketId")String redPacketId) {
  return chatroomRedPacketService.redPacketRecord(redPacketId) 
}


4、总结

思路就是那样的思路,至于实现方式,代码逻辑有很多种,可以自行实现。有其他方案的同学可以评论区一起交流下^_^。

上一篇:电商收付通系列⑦,合单下单之小程序支付

回到顶部