技术人生,  网络编程

使用Tornado编写活动抽奖网页版程序

引入所需的库,包含Tornado相关和random随机库用于抽奖。

import tornado.web
import tornado.ioloop
import tornado.httpserver
import random

定义一些全局变量,用于存储前端返回的一些信息并能在后端各函数之内调用。期中prize_number为一个字典,存储了每个奖项对应的人数,participants为列表,存储所有获奖候选人,fortunates为字典,存储每个奖项对应的获奖者,level和number用于每次抽奖临时存放当前抽取的奖项等级和人数。

global prize_number,participants,fortunates
global level,number

首先给出基本tornado框架和路径字典。可以看到一共四个Handler,但之后可以发现发挥关键作用的仅有其中两个分别为”/setup”和”/play”,对应奖项设置和抽奖。

app = tornado.web.Application(
        [
            (r"/", IndexHandler),
            tornado.web.url(r"/setup", setting_handler, name='setup'),
            tornado.web.url(r"/play", lottery_handler, name='play'),
            (r"/result", counting_handler),
        ],
        template_path='templates',
        static_path='statics'
    )

http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(8888)
tornado.ioloop.IOLoop.current().start()

首页空路径Handler没有任何处理,仅仅将页面内容重定向到”/setup”引导用户首先进行奖项设置。

class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        self.redirect('/setup')

setup页面的get方法返回设置页网页内容。

class setting_handler(tornado.web.RequestHandler):
    def get(self):
        self.render("setup.html")

对应的html如下。中间用到了javascript用于使用户个性化的设置候选人数量,通过按钮增加用于填写姓名的输入框。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>活动抽奖</title>
        <script>
        var num = 2;
        function content(){
            num += 1;
            return (num-1).toString()+"号:<input class=\"box\" placeholder=\"姓名\" type=\"input\" name=\"people\"><div id=\"participant"+num.toString()+"\"></div>"
        }
        function add_participant(){
            document.getElementById("participant"+num.toString()).innerHTML = content();
        }
        </script>
        <style>
            .box {width:150px;}
        </style>
    </head>
    <body background="/static/bg.jpg" style="background-size:100% 100%; background-repeat: no-repeat; background-attachment:fixed;">
        <marquee style="color:coral; font-size: 30px;">
            <b><i>活动抽奖</i></b>
        </marquee>
        <br>
        <br>
        <div style="text-align:center">
            <button onclick=add_participant()>添加人员</button>
            <br>
            <br>
            <form action="/setup" method="POST">
                一等奖:
                <input class="box" placeholder="人数" type="input" name="first_num">
                <br>
                二等奖:
                <input class="box" placeholder="人数" type="input" name="second_num">
                <br>
                三等奖:
                <input class="box" placeholder="人数" type="input" name="third_num">
                <br>
                <br>
                <div id="participant1">1号:<input class="box" placeholder="姓名" type="input" name="people">
                    <div id="participant2">
                    </div>
                </div>
                <br>
                <input value="提交" type="submit">
            </form>
        </div>
        <br>
    </body>
</html>
奖项设置页

设置页的post方法获取前端返回的数据,并进行合法性检查,若返回的输入数据非法(如输入非法数据或者候选人的数量小于奖项名额)则进行相应的错误提示,其中错误以弹窗方式提醒用户重新输入。若全部数据正确无误则返回设置结果页面。

    def post(self):
        try:
            global prize_number,participants,fortunates
            fortunates = {1:[],2:[],3:[]}
            data = self.request.body_arguments
            prize_number = {}
            prize_number[1] = int(data['first_num'][0].decode())
            prize_number[2] = int(data['second_num'][0].decode())
            prize_number[3] = int(data['third_num'][0].decode())
            participants = [x.decode() for x in data['people'] if x]
            if prize_number[1] < 0 or prize_number[2] < 0 or prize_number[3] < 0:
                raise Exception
            if prize_number[1] + prize_number[2] + prize_number[3] > len(participants):
                error_message = '获奖候选人数量不足!'
                self.render("error.html", error_alert=error_message, name=self.reverse_url('setup'))
                return
            self.render("setup_result.html", prize_number=prize_number, participants=participants)
        except:
            error_message = '输入数据不合法!'
            self.render("error.html", error_alert=error_message, name=self.reverse_url('setup'))

错误页html如下。这是一个模板页面,使用javascript完成弹窗操作并接收错误原因和跳转路径。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>活动抽奖</title>
        <script>
            window.alert("{{error_alert}}");
            window.location.href="{{name}}";
        </script>
    </head>
</html>
错误警告

设置结果页html如下。同样是模板页,会显示对应的奖项人数和候选人姓名。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>活动抽奖</title>
    </head>
    <body background="/static/bg.jpg" style="background-size:100% 100%; background-repeat: no-repeat; background-attachment:fixed;">
        <marquee style="color:coral; font-size: 30px;">
            <b><i>活动抽奖</i></b>
        </marquee>
        <br>
        <br>
        <div style="text-align:center">
            <p>设置成功!</p>
            <p>奖项信息设置如下:</p>
            <p>一等奖:{{prize_number[1]}}人</p>
            <p>二等奖:{{prize_number[2]}}人</p>
            <p>三等奖:{{prize_number[3]}}人</p>
            <p>候选获奖人名单:</p>
            {%for x in participants%}
                <p>{{x}}</p>
            {%end%}
            <button onclick="window.location.href='/play'">前往抽奖</button>
        </div>
        <br>
    </body>
</html>
设置结果页

通过点击设置结果页面的按钮即可到达抽奖页面,抽奖页面要求用户输入抽取奖项等级和抽取人数,首先是对应的get方法,返回抽奖页面。但是由于可以重复抽奖,所以在返回前首先判断了一下所有奖项是否全部抽取完毕,若全部抽取完毕则显示最终结果页面,反之显示正常抽奖页面。

class lottery_handler(tornado.web.RequestHandler):
    def get(self):
        global prize_number,participants,fortunates
        if prize_number[1] == 0 and prize_number[2] == 0 and prize_number[3] == 0:
            self.render("lottery_finish.html", fortunates=fortunates, participants=participants)
        else:
            self.render("lottery.html", fortunates=fortunates, prize_number=prize_number, participants=participants)

最终结果页面html如下。会显示最终的各个奖项的抽奖结果和未中奖结果。用户可以点击按钮返回奖项设置页新开一轮抽奖。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>活动抽奖</title>
    </head>
    <body background="/static/bg.jpg" style="background-size:100% 100%; background-repeat: no-repeat; background-attachment:fixed;">
        <marquee style="color:coral; font-size: 30px;">
            <b><i>活动抽奖</i></b>
        </marquee>
        <br>
        <br>
        <div style="text-align:center">
            <p>奖项全部抽取完毕,恭喜以下幸运儿:</p>
            <p>一等奖</p>
            {%for x in fortunates[1]%}
                <p>{{x+' '}}</p>
            {%end%}
            <br>
            <p>二等奖</p>
            {%for x in fortunates[2]%}
                <p>{{x+' '}}</p>
            {%end%}
            <br>
            <p>三等奖</p>
            {%for x in fortunates[3]%}
                <p>{{x+' '}}</p>
            {%end%}
            <br>
            <p>很遗憾,以下人员为中奖:</p>
            {%for x in participants%}
                <p>{{x+' '}}</p>
            {%end%}
            <button onclick="window.location.href='/setup'">再开一轮</button>
        </div>
        <br>
    </body>
</html>
最终结果页

抽奖页面html如下。页面允许用户设置本次抽奖的奖项等级和抽取人数,点击按钮后以post方式提交。并且页面下方还会动态的显示当前的中奖情况,奖项剩余名额和未中奖人员名单。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>活动抽奖</title>
        <style>
            .box {width:150px;}
        </style>
    </head>
    <body background="static/bg.jpg" style="background-size:100% 100%; background-repeat: no-repeat; background-attachment:fixed;">
        <marquee style="color:coral; font-size: 30px;">
            <b><i>活动抽奖</i></b>
        </marquee>
        <br>
        <br>
        <div style="text-align:center">
            <form action="/play" method="POST">
                奖项等级:
                <select class="box" name="level">
                    <option>1</option>
                    <option>2</option>
                    <option>3</option>
                </select>
                <br>
                抽取人数:
                <input class="box" placeholder="人数" type="input" name="number">
                <br>
                <br>
                <input value="现在开奖" type="submit">
            </form>
        </div>
        <br>
        <marquee style="color:red; background-color: lightgray; font-size: 10px;">
            当前中奖情况: 
            一等奖:{%if not fortunates[1]%}暂无 {%end%}{%for x in fortunates[1]%}{{x+' '}}{%end%} 
            二等奖:{%if not fortunates[2]%}暂无 {%end%}{%for x in fortunates[2]%}{{x+' '}}{%end%} 
            三等奖:{%if not fortunates[3]%}暂无 {%end%}{%for x in fortunates[3]%}{{x+' '}}{%end%} 
        </marquee>
        <marquee style="color:blue; background-color: lightgray; font-size: 10px;">
            剩余奖项名额: 
            一等奖:{{prize_number[1]}}人 二等奖:{{prize_number[2]}}人 三等奖:{{prize_number[3]}}人
        </marquee>
        <marquee style="color:green; background-color: lightgray; font-size: 10px;">
            剩余获奖候选人: 
            {%for x in participants%}
                {{x+' '}}
            {%end%}
        </marquee>
        <br>
    </body>
</html>
抽奖页

点击提交后,由后端对应的post方法进行处理。接收奖项等级和人数,并进行多重输入合法性检查,包括输入数据是否非法、是否有足够的候选人用于抽奖、是否有足够的剩余名额用于抽奖,若所有数据正确无误则返回抽奖开始的倒计时界面,反之调用错误页进行弹窗报错。

    def post(self):
        try:
            global prize_number,participants,fortunates
            global level,number
            level = int(self.get_body_argument('level'))
            number = int(self.get_body_argument('number'))
            if level not in [1,2,3]:
                error_message = '奖项等级设置错误!'
                self.render("error.html", error_alert=error_message, name=self.reverse_url('play'))
                return
            if number <= 0:
                raise Exception
            if len(participants) < number:
                error_message = '剩余候选人数量不足!'
                self.render("error.html", error_alert=error_message, name=self.reverse_url('play'))
                return
            if prize_number[level] < number:
                error_message = '该奖项剩余名额不足!'
                self.render("error.html", error_alert=error_message, name=self.reverse_url('play'))
                return
            self.render('lottery_count.html')
        except:
            error_message = '输入数据不合法!'
            self.render("error.html", error_alert=error_message, name=self.reverse_url('play'))

倒计时页面html如下。使用javascript进行5秒倒数同时自动播放对应的开奖音效,增加现场紧张气氛,倒计时结束后自动跳转抽奖结果页面。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>活动抽奖</title>
        <audio autoplay="autoplay">
            <source src="/static/lottery_sound.mp3">
        </audio>
        <script>
            function count_second()
            {
            var starttime = document.getElementById("id2").innerText;
            if(starttime == 1)
            {
                window.location.href="/result";
                return
            }
            setTimeout("count_second()",1000);
            starttime--;
            document.getElementById("id2").innerText = starttime;
            }
        </script>
    </head>
    <body background="/static/bg.jpg" style="background-size:100% 100%; background-repeat: no-repeat; background-attachment:fixed;">
        <marquee style="color:coral; font-size: 30px;">
            <b><i>活动抽奖</i></b>
        </marquee>
        <h1 id="id2" style="text-align:center;font-size: 1000%;color: red;">
            6
        </h1>
        <script>
            count_second()
        </script>
        <br>
    </body>
</html>
倒计时页

返回抽奖结果对应的get方法如下。这里使用random.choice()随机函数从候选人列表中随机选取中奖者,将此人从候选人列表中删除并保存到中奖人字典中,保证抽奖结果不会重复。随机选择num次,num为前端接收的设置值。抽取完成后返回抽奖结果页面。

class counting_handler(tornado.web.RequestHandler):
    def get(self):
        global prize_number,participants,fortunates
        global level,number
        lucky_guys = []
        for i in range(number):
            guy = random.choice(participants)
            participants.remove(guy)
            lucky_guys.append(guy)
        fortunates[level] += lucky_guys
        prize_number[level] -= number
        self.render("lottery_result.html", level=level, lucky_guys=lucky_guys)

抽奖结果页面对应的html如下。显示当前抽取的奖项和中奖人,用于可以点击按钮再次回到抽奖页进行下一轮抽奖。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>活动抽奖</title>
    </head>
    <body background="/static/bg.jpg" style="background-size:100% 100%; background-repeat: no-repeat; background-attachment:fixed;">
        <marquee style="color:coral; font-size: 30px;">
            <b><i>活动抽奖</i></b>
        </marquee>
        <br>
        <br>
        <div style="text-align:center">
            <p>抽奖结果:</p>
            <p>
                {%if level == 1%}
                    一等奖
                {%elif level == 2%}
                    二等奖
                {%else%}
                    三等奖
                {%end%}
            </p>
            {%for x in lucky_guys%}
                <p>{{x}}</p>
            {%end%}
            <button onclick="window.location.href='/play'">继续抽奖</button>
        </div>
        <br>
    </body>
</html>
抽奖结果页

至此,全部前后端程序结束。

全部资源文件下载

A WindRunner. VoyagingOne

留言

您的电子邮箱地址不会被公开。 必填项已用 * 标注