(四)基于区块链的自动抽奖系统从0到1实现

上一章节我们已经编写了核心的预言机合约,并且部署测试成功,现在我们就来写基于区块链的自动抽奖系统DAPP,java语言开发,项目功能比较少,所以采用框架springboot+thymeleaf。

前言

上一章节我们已经编写了核心的预言机合约,并且部署测试成功,现在我们就来写基于区块链的自动抽奖系统DAPP,java语言开发,项目功能比较少,所以采用框架springboot+thymeleaf。

一、核心功能

1.页面展示

页面展示主要是显示实时的投票排名的列表信息,还有就是对抽奖中奖信息的展示,核心代码如下:

<div class="detail-title"></div>
  <div class="time">基于区块链的自动抽奖系统</div>
            <div class="time">1月11日——1月24日投票贡献排行榜,投票结束!当博主进入top100,会触发智能合约,系统自动抽奖一位用户中奖88现金红包</div>
        <div class="time"><a href="https://bss.csdn.net/m/topic/blog_star2020/detail?username=ws327443752" >点我进入投票</a></div>
<div class="time" th:text="${userName}"></div>
  </div>
 <div class="vote-leaderboard-wrapper page-container">
    <div class="title"></div>
    <ul id="voteLeaderboardList">
              <li class="best-blogger-wrapper" th:each="cs ,csStat: ${lst}">
              <div class="left">
                  <span class="text" th:text="${cs.number}"></span>
                <span class="text" th:text="${cs.name}"></span>

              </div>
              <div class="right">
                <span class="code-age" th:text="码龄+${cs.codeLevel}+年"></span>
                <span class="vote-num"th:text="中奖概率 +${cs.vote} +'/'+ ${sum}"></span>
              </div>
            </li>
          </ul>
  </div>
/**
     * 展示页面
     * @param request
     * @return
     */
    @RequestMapping("blockchain")
    public String index(HttpServletRequest request){
        csdnService.getCsdnContent();
        request.setAttribute("lst",csdnService.csdnList);
        request.setAttribute("sum",csdnService.sum);
        request.setAttribute("userName",csdnService.userName);
        return "2020csdn";
    }

    /**
     * 获取csdn投票相关数据
     */
    public void getCsdnContent() {
        csdnList=new ArrayList<Csdn>();
        sum=0;
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("username" , "ws327443752");
        params.put("page" , "1");
        params.put("pageSize" , "200");
        String json = HttpUtil.post("https://bss.csdn.net/m/topic/blog_star2020/getRanking", params);
        JSONObject jsonObject= JSONUtil.parseObj(json);
        JSONArray ja=  jsonObject.getJSONArray("data");

        for (int i = 0; i < ja.size(); i++) {
            Csdn csdn=new Csdn();
            JSON jsonStr = (JSON) ja.get(i);
            String uName = jsonStr.getByPath("nick_name").toString();
            csdn.setName(uName);
            String voteNum = jsonStr.getByPath("voteNum").toString();
            csdn.setVote(Integer.parseInt(voteNum));
            sum+=csdn.getVote();
            String level = jsonStr.getByPath("level").toString();
            csdn.setLevel(Integer.parseInt(level));
            String codeLevel = jsonStr.getByPath("codeLevel").toString();
            csdn.setCodeLevel(Integer.parseInt(codeLevel));
            int number = Integer.parseInt(jsonStr.getByPath("number").toString());
            csdn.setNumber(number);
            csdnList.add(csdn);
        }
    }

1.抽奖接口

抽奖接口主要是保证投票数和中奖率的正比关系。代码如下所示:

/**
     * 抽奖接口
     * @return
     */
    @RequestMapping("lottery")
    @ResponseBody
    public String lottery(){
        String id = csdnService.handleLottery();
        return id;
    }

   /**
     * 抽奖方法
     * @param orignalRates 商品中奖概率列表,保证顺序和实际物品对应
     * @return 中奖商品索引
     */
    public  int lottery(List<Double> orignalRates) {

        if (orignalRates == null || orignalRates.isEmpty()) {
            return -1;
        }

        int size = orignalRates.size();

        // 计算总概率,这样可以保证不一定总概率是1
        double sumRate = 0d;
        for (double rate : orignalRates) {
            sumRate += rate;
        }

        // 计算每个物品在总概率的基础下的概率情况
        List<Double> sortOrignalRates = new ArrayList<>(size);
        Double tempSumRate = 0d;
        for (double rate : orignalRates) {
            tempSumRate += rate;
            sortOrignalRates.add(tempSumRate / sumRate);
        }

        // 根据区块值来获取抽取到的物品索引
        double nextDouble = Math.random();
        sortOrignalRates.add(nextDouble);
        Collections.sort(sortOrignalRates);

        return sortOrignalRates.indexOf(nextDouble);
    }

    /**
     * 触发抽奖逻辑
     * @return
     */
    public String handleLottery() {
        List<NameDTO> nameDTOS = new ArrayList<>();

        for (int i = 0; i < csdnList.size(); i++) {
            double f1 = new BigDecimal((float)csdnList.get(i).getVote()/sum).setScale(4, BigDecimal.ROUND_HALF_UP).doubleValue();
            nameDTOS.add(new NameDTO(i + 1, csdnList.get(i).getName(), csdnList.get(i).getNumber() + "", f1));
        }

        // 存储概率
        List<Double> orignalRates = new ArrayList<>(nameDTOS.size());
        for (NameDTO gift : nameDTOS) {
            double probability = gift.getProbability();
            if (probability < 0) {
                probability = 0;
            }
            orignalRates.add(probability);
        }

        // 统计
        Map<Integer, Integer> count = new HashMap<>();

        // 次数
        double num = 1;
        for (int i = 0; i < num; i++) {
            int orignalIndex = lottery(orignalRates);
            Integer value = count.get(orignalIndex);
            count.put(orignalIndex, value == null ? 1 : value + 1);
        }

        String id=null;
        for (Map.Entry<Integer, Integer> entry : count.entrySet()) {
            id=nameDTOS.get(entry.getKey()).getNo();
        }
        return id;
    }

3.定时触发智能合约抽奖,并自动公布结果

这里是结合宝塔的定时器进行时间的调度,不单独在项目中进行时间调度工作。注意我们这里的url为bclottery_wa7ndmvt2,相当于加了一个密码,来定时触发智能合约抽奖,并自动公布结果。如下图所示: 在这里插入图片描述 我们宝塔设置指定的时间来访问url触发智能合约。宝塔安装教程参考:

/**
     * 定时触发智能合约抽奖,并自动公布结果
     *      这里是结合宝塔的定时器进行时间的调度,不单独在项目中进行时间调度工作。注意我们这里的url为bclottery_wa7ndmvt2,相当于加了一个密码
     * @return
     */
    @RequestMapping("bclottery_wa7ndmvt2")
    @ResponseBody
    public void bcLottery(HttpServletRequest request){
        // 先判断是否进入top100
      if(csdnService.getRanking()) {
          // 触发智能合约抽奖
          String res = csdnService.handle("request");
          if (res.contains("transactionHash")) {
              // 获取合约结果
              res = csdnService.handle("get");
              // 拿到结果进行对比后进行页面公布
              res = csdnService.getUserNameById(Integer.parseInt(res));
              csdnService.userName = "中奖用户为:"+res;
          }
      }else {
          csdnService.userName="博主未进top100,没有触发抽奖合约,本次抽奖失效!";
        }
    }

    /**
     * 执行合约
     *
     * @param param 合约参数
     * @return
     */
    public String handle(String param) {
        String url =WEBASE_FRONT_URL+"/WeBASE-Front/trans/handle";
        String body = "{\n" +
                "    \"user\":\"0xf0d04e0cc9b16528207027f1d5020e402096b44e\",\n" +
                "    \"contractName\":\"APISampleOracle\",\n" +
                "    \"contractAddress\":\"0x5ffbf18cfbe8c5b8fee09ccde4f5165007a6043e\",\n" +
                "    \"funcName\":\"" + param + "\",\n" +
                "    \"contractAbi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"oracleAddress\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"}],\"name\":\"Fulfilled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"}],\"name\":\"Requested\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"EXPIRY_TIME\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_requestId\",\"type\":\"bytes32\"},{\"internalType\":\"int256\",\"name\":\"_result\",\"type\":\"int256\"}],\"name\":\"__callback\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"}],\"name\":\"checkIdFulfilled\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"get\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"}],\"name\":\"getById\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getUrl\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"request\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"result\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_url\",\"type\":\"string\"}],\"name\":\"setUrl\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}] ,\n" +
                "    \"groupId\" :\"1\",\n" +
                "    \"useCns\": false\n" +
                "}";
        String res = HttpUtil.post(url, body);
        if (param.equals("get")) {
            // 处理格式
            res = res.replace("[", "").replace("]", "").replace("000000000000000000", "");
        }
        return res;
    }

    /**
     * 通过用户id获取用户名(csdn)
     * @param id 用户的排名id
     * @return
     */
    public String getUserNameById(int id) {
        String[] name = {null};
        csdnList.stream().filter(csdn -> csdn.getNumber() == id).forEach(csdn -> {
            name[0] = csdn.getName();
        });
        return name[0];
    }

 /**
     * 获取排名,看看是否进top100
     * @return
     */
    public boolean getRanking() {
        String html = HttpUtil.get("https://csdn.itrhx.com/");
        Document doc = Jsoup.parse(html);
        Elements els = doc.getElementsByTag("tr");
        for (int i=0;i<els.size();i++
        ) {
            if (els.get(i).html().contains("ws327443752")&&i<=100) {
                return true;
            }
        }
        return false;
    }

下图是模拟测试的抽奖结果展示: 在这里插入图片描述 在这里插入图片描述

如果合约被定时执行了,那么我们可以在Truora中看到如下图所示的信息: 在这里插入图片描述

4.项目整体代码地址

总结

我们的抽奖的项目核心就是抽奖的逻辑和页面的展示以及最关键的智能合约的调用。到本篇文章就完结了,项目代码已经开源到了gitee, 方便大家研究,有兴趣的小伙伴可以关注一下!

本文参与2022世界杯预选赛赛程直播社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

1 条评论

请先 登录 后评论
向彪
向彪

区块链应用架构师

19 篇文章, 2282 学分