乐趣区

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

一、前言

更多文章返回 -> 微信小程序直播间开发抽红包性能

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

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

尽管有抽奖,优惠券的营销性能,然而却没有红包性能,如果有红包性能,减少了和用户的互动,更能吸引用户留下来观看直播。其实,咱们是能够本人在直播间开发红包性能的。当然,要实现这个性能,小程序要先开明直播权限,开明直播权限需满足小程序近 90 天内有过领取行为,如果因为这个无奈开明的分割我,能够疾速开明。

二、思路

说一下这个性能实现的思路,首先后盾做一个录红包的菜单,字段包含主播名称、主播头像、标语(恭喜发财,大吉大利)、无效工夫、红包金额、红包个数、残余现金红包金额、残余现金红包个数、创立工夫、版本号(乐观锁),还要有一个抢红包记录表,字段包含红包 id、抢到红包用户的 id、抢到红包用户的名称、抢到红包用户的头像、抢到的红包金额、创立工夫。而后去小程序直播后盾录商品,商品门路字段填写要跳转的小程序红包页面门路,须要在前面拼接红包 id 参数,比方像这样,

pages/redPacket/redPacket.html?redPacketId=123456


当用户在直播页面点击该商品进入红包页面,前端就能够拿到红包 id 传给后盾接口,查到该红包的相干信息,做各种操作了,比方生成随机金额,扣减红包金额和个数等等。这个须要主播疏导用户做好抢红包的筹备,而后直播间助理通过上架商品来显示红包商品。

思路很简略,代码实现起来也很简略,然而咱们须要思考几个问题,

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

2、如果乐观锁更新失败,间接返回给用户提醒抢到空红包或其余敌对提醒也是能够的,然而如果咱们减少重试机制的话,体验会更好点。这里咱们采纳注解的形式进行重试,默认重试 3 次。

三、实现

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

/**
 * 定义重试机制注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ApiRetry {
    /**
     * 默认次数
     * @return
     */
    int value() default 3;}
/**
 * 定义一个重试机制切面类
 */
@Slf4j
@Aspect
@Component
public class RetryAspect {@Pointcut("@annotation(com.redPacket.common.apiIdempotent.annotation.ApiRetry)")
public void retryPointcut() {}

@Around("retryPointcut() && @annotation(retry)")
@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
public class RestExceptionHandler {private Logger logger = LoggerFactory.getLogger(getClass());

  /**
   * 解决重试机制异样
   */
  @ExceptionHandler(ApiRetryException.class)
  public JsonModel handleApiRetryException(ApiRetryException e){JsonModel jsonModel = new JsonModel();
    jsonModel.setStatus(e.getStatus());
    jsonModel.setMsg(e.getMsg());
    return jsonModel;
  }
}

对于红包接口就三个,

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

/**
 * 进入红包页面判断是否能够抽红包
 * @param redPacketId
 * @param userId
 * @return
 */
@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(value = "进入红包页面判断是否能够抽红包")
@ApiImplicitParams({@ApiImplicitParam(name = "redPacketId", value = "红包 id", required = true, paramType = "body", dataType = "String"),
    @ApiImplicitParam(name = "userId", value = "用户 id", required = true, paramType = "body", dataType = "String")
})
@PostMapping("/intoRedPacket/{redPacketId}/{userId}")
public JsonModel intoRedPacket(@PathVariable("redPacketId")String redPacketId,
                 @PathVariable("userId")String userId) {return chatroomRedPacketService.intoRedPacket(redPacketId,userId); 
}

2、这个是重中之重,就是点击抢

/**
 * 抽红包
 * @param redPacketId
 * @param userId
 * @return
 */
@Override
@ApiRetry
@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 redPacketId
 * @return
 */
@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(value = "抽红包记录")
@ApiImplicitParams({@ApiImplicitParam(name = "redPacketId", value = "红包 id", required = true, paramType = "body", dataType = "String"),
    @ApiImplicitParam(name = "userId", value = "用户 id", required = true, paramType = "body", dataType = "String")
})
@PostMapping("/redPacketRecord/{redPacketId}")
public JsonModel redPacketRecord(@PathVariable("redPacketId")String redPacketId) {return chatroomRedPacketService.redPacketRecord(redPacketId) 
}‍‍

四、总结

思路就是那样的思路,至于实现形式,代码逻辑有很多种,能够自行实现。有其余计划的同学能够评论区

退出移动版