提示:写这个web服务是为了练手玩,实际上已经有很多web(基于html或其他)、桌面应用、手机APP等支持题目生成功能了,并且手机APP还支持OCR识别判断答案是否正确,比如我试了一个小猿口算APP就挺好:http://kousuan.yuanfudao.com/
先上服务链接:http://aspirer.wang:3389/kousuan/7
链接的最后一个数字是可以修改的,改成几就是生成几以内的加减法练习题(比如上面的链接就是生成7以内的加减法口算题目,每次刷新都是新的题目不会重复)。
儿子上一年级经常有口算练习题,老师发的是一张习题纸,一共100道题,需要家长复印,但是存在三个小问题:一是复印出来的题目完全一样(有一次我发现儿子做题居然在参考前面一张。。。);二是打印不方便,必须得复印,有些家长是没有复印机的(复印还要带上原件,老师发下来的时候家长不一定能及时拿到原件);三是想自己提前给孩子出题练习其他更大数字的加减法不方便。
有了这个web站,后面还可以稍微修改下,支持生成乘法、除法的口算题。
整体部署架构:nginx+uwsgi+bottle,python编写的web后台服务。
部署过程参考资料:
使用nginx的原因是我的博客就是用的它,跟博客部署在一起了,只是端口不同。
源码:
1 2 |
root@myblog:~/kousuan# ls *.py bottle.py gen.py kousuan.py |
共3个文件:bottle.py是bottle wsgi框架,可以pip install安装,也可以直接拷贝源文件过来,非常方便。gen.py是为了方便后台测试用的,python执行它可以直接打印出题目。kousuan.py是给uwsgi用的,算是wsgi配置文件,当然里面也有一些其他代码,主要是生成html模板文件,以及配置wsgi router。
文件都很短,这里直接贴出来,不放github了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
#!/usr/bin/python # -*- coding: UTF-8 -*- import random import sys def add(a, b): return a + b def sub(a, b): return a - b FUNC = {'+': add, '-': sub} def gen_one_question(upper): f = random.randint(0, len(FUNC.keys()) - 1) i = random.randint(0, upper) j = random.randint(0, upper) return f, i, j def gen(upper): content = '' for row in range(0, 25): line = '' for col in range(0, 4): r = -1 while (r < 0 or r > upper): f, a, b = gen_one_question(upper) fv = FUNC.values()[f] r = fv(a, b) fk = FUNC.keys()[f] line += ' '.join([str(a), fk, str(b), '=', ', ']) line += '\n' content += line return content if __name__ == '__main__': num = sys.argv[1] head = num + '以内加减法口算练习\n学号:, 姓名:, 用时:,\n' content = gen(int(num)) print head, content |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
#!/usr/bin/python # -*- coding: UTF-8 -*- import bottle from bottle import route, run, template from gen import gen mybottle = bottle.Bottle() @mybottle.route('/kousuan') @mybottle.route('/kousuan/') @mybottle.route('/') def index(): return '''网址有误,请使用"/kousuan/数字",如"/kousuan/7"''' @mybottle.route('/kousuan/<num>') def index(num): try: return _gen(num) except Exception as ex: print ex def _gen(num): if not num.isdigit(): return '''网址有误,最后一位必须是数字!''' content = gen(int(num)) space = ' ' html_content = ''' <html> <body> <h3>%s以内加减法口算练习</h3> <h5>学号:%s 姓名:%s 用时:%s </h5> <table> ''' % (num, space*6, space*16, space*12) for l in content.splitlines(): l = l.strip()[:-2] line = '<tr><td>' + l.replace(',', ' </td><td> ') line += ' </td></tr> \n' html_content += line html_content += ''' </table> </body> </html> ''' return html_content application=mybottle #run(host='0.0.0.0', port=3389)</num> |
html格式很简单,就没用专门的模板渲染框架如jinjia2等。
uwsgi配置文件:
1 2 3 4 5 6 7 8 |
[uwsgi] socket = 127.0.0.1:10059 chdir = /root/kousuan master = true plugins = python file = kousuan.py uid = root # 我这边用www-data报权限错误,就改成了root gid = root |
nginx配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
server { listen 3389; server_name aspirer.wang; root /root/kousuan; location / { try_files $uri @uwsgi; } location @uwsgi { include uwsgi_params; uwsgi_pass 127.0.0.1:10059; } } |
上面两个文件需要在对应的enabled目录下建立软链接,具体参考上面的部署过程参考资料(第二个链接)。
uwsgi和nginx的安装就不说了,apt就行。
部署好之后重启uwsgi和nginx服务就可以了。
监控:
为了及时发现web故障,用监控宝给3389 tcp端口和网站都加了监控,出现不可用会发短信和邮件通知。(我的博客用他们免费版用了这么久,也给人家打个广告)。
10.22更新:
- 修改了题目生成方法,大幅减少了包含0的题目的数量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
#!/usr/bin/python # -*- coding: UTF-8 -*- import random import sys def add(a, b): return a + b def sub(a, b): return a - b FUNC = {'+': add, '-': sub} def gen_one_question(lower, upper): f = random.randint(0, len(FUNC.keys()) - 1) i = random.randint(lower, upper) j = random.randint(lower, upper) return f, i, j def gen(upper): content = '' for row in range(0, 25): line = '' for col in range(0, 4): r = -1 while (r < 0 or r > upper): lower = random.randint(0, upper/2) f, a, b = gen_one_question(lower, upper) fv = FUNC.values()[f] r = fv(a, b) if ((a + b == 0) and (upper > 0)): r = -1 continue fk = FUNC.keys()[f] line += ' '.join([str(a), fk, str(b), '=', ', ']) line += '\n' content += line return content if __name__ == '__main__': num = sys.argv[1] head = num + '以内加减法口算练习\n学号:, 姓名:, 用时:,\n' content = gen(int(num)) print head, content |
其他:
生成题目时是暴力穷举符合条件的题目,其实可以根据每个题目的类型(加法或减法)以及生成的第一个数字,来限定第二个数字的随机范围,保证一次就可以生成符合条件的题目,可以很大程度减少计算量,不过对于这么小的程序和用户量的场景来说,这一点点计算量也就无所谓了。