使用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>

至此,全部前后端程序结束。
全部资源文件下载