使用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>
至此,全部前后端程序结束。
全部资源文件下载