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

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

1、前言

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

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

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

2、思路

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

pages/redPacket/redPacket.html?redPacketId=123456

image.png

image.png

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

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

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

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

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、给前端判断是弹出抢红包的窗口还是弹出其他提示窗口(你已抢过该红包了、手慢了,红包已过期、手慢了,红包派完了)。

image.png

 * 进入红包页面判断是否可以抽红包
 * @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、这个是重中之重,就是点击抢红包的接口。

image.png

/**
 * 抽红包
 * @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、最后一个是抢红包记录列表。

image.png

 * 抽红包记录
 * @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) 
}‍‍

4、总结

思路就是那样的思路,至于实现方式,代码逻辑有很多种,可以自行实现。

山水有相逢,来日皆可期,谢谢阅读,我们再会

我手中的金箍棒,上能通天,下能探海

上一篇:一个字段,就可以判断是否关注公众号

相关文章
ly~
|
7天前
|
存储 供应链 小程序
除了微信小程序,PHP 还可以用于开发哪些类型的小程序?
除了微信小程序,PHP 还可用于开发多种类型的小程序,包括支付宝小程序、百度智能小程序、抖音小程序、企业内部小程序及行业特定小程序。在电商、生活服务、资讯、工具、娱乐、营销等领域,PHP 能有效管理商品信息、订单处理、支付接口、内容抓取、复杂计算、游戏数据、活动规则等多种业务。同时,在企业内部,PHP 可提升工作效率,实现审批流程、文件共享、生产计划等功能;在医疗和教育等行业,PHP 能管理患者信息、在线问诊、课程资源、成绩查询等重要数据。
ly~
40 6
|
7天前
|
JSON 小程序 前端开发
创建一个属于自己的小程序(注册开发账号)
介绍如何创建微信小程序账号,包括注册流程、下载安装微信开发者工具、创建项目以及项目结构介绍。
创建一个属于自己的小程序(注册开发账号)
ly~
|
7天前
|
开发框架 小程序 前端开发
抖音小程序的开发难度大吗?
抖音小程序的开发难度因人而异,主要取决于开发者经验、技能及功能需求。技术上需掌握前端技术及抖音开发框架,了解平台生态与规则;设计上需符合用户审美和习惯,具备创新性和实用性。此外,严格的审核标准和激烈的市场竞争增加了开发难度,开发者需制定有效推广策略并持续优化小程序以保持竞争力。
ly~
41 4
|
6天前
|
算法 JavaScript 前端开发
切西瓜法实现微信抢红包功能
该文章介绍了使用“切西瓜法”和“栅栏法”两种算法来模拟微信抢红包的随机分配机制,并通过具体的JavaScript代码实现了红包金额的公平随机分配过程。
切西瓜法实现微信抢红包功能
|
7天前
|
小程序 JavaScript API
微信小程序开发学习之页面导航(声明式导航和编程式导航)
这篇文章介绍了微信小程序中页面导航的两种方式:声明式导航和编程式导航,包括如何导航到tabBar页面、非tabBar页面、后退导航,以及如何在导航过程中传递参数和获取传递的参数。
微信小程序开发学习之页面导航(声明式导航和编程式导航)
|
21天前
|
存储 移动开发 监控
微信支付开发避坑指南
【9月更文挑战第11天】在进行微信支付开发时,需遵循官方文档,确保权限和参数配置正确。开发中应注重安全,验证用户输入,合理安排接口调用顺序,并处理异常。上线后需实时监控支付状态,定期检查配置,关注安全更新,确保系统稳定运行。
|
28天前
|
人工智能 前端开发 JavaScript
MacTalk 测评通义灵码,实现“微信表情”小功能
墨问西东创始人池建强分享了团队使用通义灵码的经验。
|
27天前
|
移动开发 小程序 JavaScript
uni-app开发微信小程序
本文详细介绍如何使用 uni-app 开发微信小程序,涵盖需求分析、架构思路及实施方案。主要功能包括用户登录、商品列表展示、商品详情、购物车及订单管理。技术栈采用 uni-app、uView UI 和 RESTful API。文章通过具体示例代码展示了从初始化项目、配置全局样式到实现各页面组件及 API 接口的全过程,并提供了完整的文件结构和配置文件示例。此外,还介绍了微信授权登录及后端接口模拟方法,确保项目的稳定性和安全性。通过本教程,读者可快速掌握使用 uni-app 开发微信小程序的方法。
57 3
|
1月前
|
小程序 API 开发工具
使用python 实现微信签到提醒功能
【9月更文挑战第4天】使用python 实现微信签到提醒功能
51 2
|
16天前
|
小程序 前端开发 JavaScript
Java开发工程师转小程序开发的前景如何?
Java开发工程师转小程序开发的前景如何?
27 0
下一篇
无影云桌面