做一个 拜年手势红包,从此红包不被白嫖「可体验」(一)

1,135 阅读3分钟

PK创意闹新春,我正在参加「春节创意投稿大赛」,详情请看:春节创意投稿大赛

效果

体验发送页面领取页面拜年姿势识别页面
gh_15bfd4eab8ac_258.jpgimage.pngimage.pngimage.png

介绍

项目主要分为三部分 - 小程序 发红包、领红包、姿势采集 - 后端 登陆、发红包、支付、领红包、发钱、验证姿势是否正确 - 人体识别 采集到图片时识别人体关键点

1.小程序

  • 使用 uni-app 开发
  • ui使用 uview
  • 目前只兼容 微信小程序
  • 一共3个页面

2.后端

  • 使用 java 开发
  • 框架主要是要 springboot + mybatis-plus + IJPay-WxPay + redisson

3.人体识别

  • 使用 python 开发
  • 框架主要是要 Django + PaddlePaddle

一、发红包

image.png

  • 发送人 默认微信获取的昵称,可以自定义,方便不同人写不同的名
  • 领取方式目前分为3个
    • 打开即可:点开红包直接领取
    • 写祝福语:需填写完祝福语,就可以打开红包
    • 手势拜年:需做出拜年动作,做对后即可领取
  • 思路
    • 小程序填写红包信息,请求服务端
    • 服务端保存红包,调用微信支付生成微信支付订单,返回给小程序
    • 小程序拿到微信支付所需的参数,唤起微信支付
    • 小程序支付成功后,询问服务端结果,确定支付成功后,弹出成功消息
  • 代码实现
    • 页面 (用的ui框架很简单)
    <view style="margin: 20rpx;">
        <u--form labelPosition="left" labelWidth="80" :model="from_data">
                <u-form-item label="发送人">
                        <u--input v-model="from_data.nickName" border="none" maxlength="20" color="#FFFFFF">
                        </u--input>
                </u-form-item>
                <u-form-item label="压岁钱个数">
                        <u--input v-model="from_data.num" border="none" type="number" maxlength="3" color="#FFFFFF">
                        </u--input>
                </u-form-item>
                <u-form-item label="压岁钱金额">
                        <u--input v-model="from_data.amount" border="none" type="digit" maxlength="7" color="#FFFFFF">
                        </u--input>
                </u-form-item>
                <u-form-item label="压岁钱类型">
                        <u-radio-group v-model="from_data.redPacketType" placement="row">
                                <u-radio :customStyle="{marginRight: '16rpx'}" v-for="(item, index) in redPacket_type"
                                        :key="index" :label="item.name" :name="item.name" labelColor="#FFFFFF">
                                </u-radio>
                        </u-radio-group>
                </u-form-item>
                <u-form-item label="领取方式">
                        <u-radio-group v-model="from_data.receivingMethod" placement="row">
                                <u-radio :customStyle="{marginRight: '16rpx'}" v-for="(item, index) in receiving_method"
                                        :key="index" :label="item.name" :name="item.name" :disabled="item.disabled"
                                        labelColor="#FFFFFF">
                                </u-radio>
                        </u-radio-group>
                </u-form-item>
                <u-form-item label="留言祝福">
                        <u--input v-model="from_data.redPacketBlessing" border="none" maxlength="30" color="#FFFFFF">
                        </u--input>
                </u-form-item>
        </u--form>
        <u-alert title="需支付 3% 手续费 包含(微信支付和运营费用)" type="error" effect="dark" closable></u-alert>
        <u-button type="primary" :loading="pay_loading_show" loadingText="支付中..." text="支付" throttleTime="1000"
                customStyle="margin-top: 10rpx" @click="pay">
        </u-button>
    </view>
    
    
    • js(发红包、支付)
    pay() {
        //校验 表单
        let num = this.from_data.num
        let fee = this.from_data.amount * 100
        let redPacketType = this.from_data.redPacketType
        if (num < 1) {
            uni.showToast({
                    title: '最少发1份压岁钱'
            })
            return
        } else if (num > 500) {
            uni.showToast({
                    title: '最多发500份压岁钱'
            })
            return
        }
        if (redPacketType == '拼手气') {
            if (fee / num < 30) {
                uni.showToast({
                        title: '单人最少0.3元'
                })
                return
            } else if (fee / num > 10000) {
                uni.showToast({
                        title: '单人最多100元'
                })
                return
            }
        } else if (redPacketType == '平分') {
            if (fee < 30) {
                uni.showToast({
                        title: '单人最少0.3元'
                })
                return
            } else if (fee > 10000) {
                uni.showToast({
                        title: '单人最多100元'
                })
                return
            }
        }
    
        // 表单
        let that = this
        this.pay_loading_show = true
        let from = {
            nickName: this.from_data.nickName,
            num: this.from_data.num,
            amount: this.from_data.amount,
            redPacketBlessing: this.from_data.redPacketBlessing
        }
        if (this.from_data.redPacketType == '拼手气') {
                from.redPacketType = 1
        } else if (this.from_data.redPacketType == '平分') {
                from.redPacketType = 2
        }
        if (this.from_data.receivingMethod == '打开即可') {
                from.receivingMethod = 1
        } else if (this.from_data.receivingMethod == '写祝福语') {
                from.receivingMethod = 2
        } else if (this.from_data.receivingMethod == '手势拜年') {
                from.receivingMethod = 3
        }
        // 生成红包订单
        uni.$u.http.post('/redPacket/send', from).then(res => {
                console.log(JSON.stringify(res))
                that.currentRedPacketId = res.redPacketId
                //res 是生成订单后 返回的结果 用于支付的参数
                // 返回唤起微信支付
                wx.requestPayment({
                        timeStamp: res.timeStamp,
                        nonceStr: res.nonceStr,
                        package: res.package,
                        signType: res.signType,
                        paySign: res.paySign,
                        success(res) {
                                console.log('支付成功')
                                that.currentRedPacket = that.from_data
                                //支付成功后 查询支付结果
                                that.queryPayRes(1)
                        },
                        fail(res) {
                                console.log('支付失败')
                                that.pay_loading_show = false
                                that.refreshRedPacket()
                                uni.showToast({
                                        title: '支付失败'
                                })
                        }
                })
        })
    },
    // 查询支付结果
    // 这里是轮训去查询,查询30次没500ms查一下
    queryPayRes(count) {
        if (count == null) {
                count = 1
        }
        uni.$u.http.post('/redPacket/queryPay', {
                redPacketId: this.currentRedPacketId
        }).then(res => {
                console.log('查询支付结果:' + JSON.stringify(res))
                console.log('查询支付结果:' + res)
                if (res == true) {
                        //支付成功后取消 loading ,弹出支付成功
                        this.pay_show = true
                        this.pay_loading_show = false
                        this.refreshRedPacket()
                } else {
                        if (count < 30) {
                                setTimeout(() => {
                                        this.queryPayRes(++count)
                                }, 500)
                        } else {
                                this.pay_fail_show = true
                                this.pay_loading_show = false
                                this.refreshRedPacket()
                        }
                }
        })
    },
    
    • java 后端发红包接口
    @Override
    public Result<?> send(UserBo user, RedPacketVo redPacketVo) {
        //校验参数
        double fee = NumberUtil.mul(redPacketVo.getAmount(), new Double(100.00));
        if (redPacketVo.getNum() > fee) {
            return Result.parameterError("每人最少1分钱");
        }
        Integer redPacketType = redPacketVo.getRedPacketType();
        Integer receivingMethod = redPacketVo.getReceivingMethod();
        if (redPacketType == null || receivingMethod == null) {
            return Result.parameterError();
        }
        //生成红包
        RedPacket redPacket = new RedPacket()
                .setNum(redPacketVo.getNum())
                .setRedPacketBlessing(redPacketVo.getRedPacketBlessing())
                .setRedPacketType(redPacketType)
                .setReceivingMethod(receivingMethod)
                .setNickName(redPacketVo.getNickName())
                .setUserId(user.getUserId())
                .setStatus(0)
                .setOutTradeNo(WxPayKit.generateStr());
        if (redPacketType.equals(1)) {
            拼手机红包,需算出每个红包的金额
            BigDecimal roundTotalFee = NumberUtil.round(fee, 0);
            redPacket.setTotalFee(roundTotalFee.intValue());
            redPacket.setPayTotalFee(NumberUtil.mul(roundTotalFee, 1.03).intValue());
            //红包算法 (可以有 公平版、也可以有手速版,所以不一定随先抢就是大红包,哈哈哈哈)
            List<Integer> redPacketFeeList = doubleMeanMethod(redPacket.getTotalFee(), redPacketVo.getNum());
            //每个红包的金额都存起来
            redPacket.setRedPacketFeeList(redPacketFeeList);
        } else if (redPacketType.equals(2)) {
            //平分红包 ,需要计算一下总金额
            double totalFee = NumberUtil.mul(fee, redPacketVo.getNum().longValue());
            BigDecimal roundTotalFee = NumberUtil.round(totalFee, 0);
            redPacket.setTotalFee(roundTotalFee.intValue());
            redPacket.setPayTotalFee(NumberUtil.mul(roundTotalFee, 1.03).intValue());
            //每个红包的金额都存起来
            List<Integer> redPacketFeeList = new ArrayList<>();
            int roundFee = NumberUtil.round(fee, 0).intValue();
            for (int i = 0; i < redPacketVo.getNum(); i++) {
                redPacketFeeList.add(roundFee);
            }
            redPacket.setRedPacketFeeList(redPacketFeeList);
        }
        //存下红包
        boolean save = save(redPacket);
        if (save) {
            //调用微信支付接口生产微信支付订单
            Map<String, String> data = weChatPayService.miniAppPay(user, redPacket);
            data.put("redPacketId", redPacket.getRedPacketId().toString());
            //返回微信支付需要的支付参数
            return Result.success(data);
        }
        return Result.exceptionError("发红包失败");
    }
    
    /**
     * 拼手气红包
     * 二倍均值+最小金额 算法(公平版)
     *
     * @param totalFee 红包总金额(分)
     * @param size     领取人数
     */
    public static List<Integer> doubleMeanMethod(int totalFee, int size) {
        List<Integer> result = new ArrayList<>();
        if (totalFee < 0 && size < 1) {
            return Collections.emptyList();
        }
        //每个红包最小金额
        int minFee = 30;
        int amount, sum = 0;
        int remainingNumber = size;
        int i = 1;
        while (remainingNumber > 1) {
            int maxFee = Math.min(2 * (totalFee / remainingNumber), totalFee - ((size - i + 1) * minFee) + minFee);
            amount = RandomUtils.nextInt(minFee, maxFee);
            sum += amount;
            System.out.println("第" + i + "个人领取的红包金额为:" + amount);
            totalFee -= amount;
            remainingNumber--;
            result.add(amount);
            i++;
        }
        result.add(totalFee);
        System.out.println("第" + i + "个人领取的红包金额为:" + totalFee);
        sum += totalFee;
        System.out.println("验证发出的红包总金额为:" + sum);
        return result;
    
    }
    

二、领红包

文章地址

三、拜年手势领红包

文章地址