任意整数以内的加减法口算练习题生成web服务源码及搭建过程




提示:写这个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后台服务。

部署过程参考资料:

  1. 使用bottle.py体验WSGI服务
  2. Nginx 部署Bottle + uwsgi

使用nginx的原因是我的博客就是用的它,跟博客部署在一起了,只是端口不同。

源码:

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了:

#!/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

 

#!/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/')
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 = '''
        
        
        

%s以内加减法口算练习

学号:%s 姓名:%s 用时:%s
''' % (num, space*6, space*16, space*12) for l in content.splitlines(): l = l.strip()[:-2] line = ' \n' html_content += line html_content += '''
' + l.replace(',', '     ') line += '    
''' return html_content application=mybottle #run(host='0.0.0.0', port=3389)

html格式很简单,就没用专门的模板渲染框架如jinjia2等。

uwsgi配置文件:

[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配置文件:

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更新:

  1.  修改了题目生成方法,大幅减少了包含0的题目的数量
#!/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

 

其他:

生成题目时是暴力穷举符合条件的题目,其实可以根据每个题目的类型(加法或减法)以及生成的第一个数字,来限定第二个数字的随机范围,保证一次就可以生成符合条件的题目,可以很大程度减少计算量,不过对于这么小的程序和用户量的场景来说,这一点点计算量也就无所谓了。