四则运算网页版--结对项目
前言:
这次项目是用前后台分离,后台代码由servlet编写。项目部署于我的个人服务器。
github仓库地址https://github.com/LeonP3ng/CalculateWeb.git
服务器项目地址http://rammsteinlp.cn:8080/CalculateProject/
前台搭档陈慧妮博客:
https://blog.****.net/olivia_chn/article/details/89184165
二、项目功能描述
本项目是网页版的四则运算出题软件,此软件为学生用户提供以下功能:
1、定制出题要求。
每次出题时,学生用户在页面输入学号和所需生成题目的要求:题目数量,算式数值范围(仅整数运算实现了),题目中运算符个数,题目中是否包含括号,题目中是否含分数。输入完要求后再点击开始生成题目按钮即可生成题目(不包含答案)。
2、做题功能。
出题完毕后,学生用户可以答题并开始计时。
3、判题功能。
学生用户作答完毕后点击在线验证答案即可查看正确答案、错题、正确率和用时。
4、下载功能。
学生用户可以点击下载题目按钮将本次所做的题目和答案下载到本地。
三、后台代码分析:
3.1接收参数
int n = Integer.parseInt(request.getParameter("n")); //出题数目 int ifBrack = Integer.parseInt(request.getParameter("ifBrack")); //是否带有括号 int ifScore = Integer.parseInt(request.getParameter("ifScore")); //是否含分数 int flagNumber = Integer.parseInt(request.getParameter("flagNumber")); //运算符数量 int studentId = Integer.parseInt(request.getParameter("studentId")); //学生学号,和生成的题目一同写入服务器,后期可以本地下载。 int lowLimit = Integer.parseInt(request.getParameter("lowLimit")); //产生数字最小范围 int highLimit = Integer.parseInt(request.getParameter("highLimit")); //产生数字最大范围
这里对于产生数字结果范围我只限制了普通运算和带括号的运算,对于分数没有做到限制,首先是分数一定是真分数,即值小于1。但是如果对分子分母进行限制的话,我会一直报stack overflow也就是栈溢出。看了下报错日志,主要集中于我的判断分子分母是否最简时会调用求最大公因数函数,然后这个过程若分子分母不符合要求就会反复调用自身,直到生成符合要求的数,然后这块就很容易报栈溢出,因此我对这里就没有限制数值范围。
int[] num = new int[m + 1]; // 数字 for (j = 0; j <= m; j++) { num[j] = (int) (Math.random() * (highLimit - lowLimit) + lowLimit); //因为random()函数返回值是0~1,最大减最小再乘上random产生的伪随机数,加上最小范围就是我们想要生成的范围。 }
/*对于生成式子的结果也有一个判断,是否介于lowLimit~highLimit,返回false的话重新调用函数生成新式子,直到符合要求 ** */ static boolean ifAnswerLegal(String answer,int lowLimit,int highLimit){ int value = Integer.parseInt(answer); return value <= highLimit && value >= lowLimit; }
3.2 判断生成式子类型
对于生成什么类型的式子,我采用了定义一个level作为权重,
if (ifBrack == 1) {
if (ifScore == 1) {
level = 4; //普通,带括号,带分数
}else{
level = 2; //普通,带括号
}
}else{
if (ifScore == 1){
level = 3; //普通,带分数
}
}
//若都不符合 level=1,只生成普通的算式
接下来用switch判断,根据level不同执行不同函数。
private static List judgeQuestion(int n, int flag,int flagNumber,int lowLimit,int highLimit){ List<Map<String,String>> list = new ArrayList<>(); switch (flag){ //只有简单的四则运算 case 1: { for (int i = 0; i < n; i++) { list.add(CalService.SimpleArithmetic(flagNumber,lowLimit,highLimit)); } break; } //有带括号的四则运算 case 2:{ for (int i = 0; i < n; i++) { if ((int)(Math.random()*2) == 1) { list.add(CalService.SimpleArithmetic(flagNumber,lowLimit,highLimit)); }else { list.add(CalService.bracketArithmetic(flagNumber,lowLimit,highLimit)); } } break; } //有分数的四则运算 case 3: { for (int i = 0; i < n; i++) { if ((int)(Math.random()*2) == 1) { list.add(CalService.SimpleArithmetic(flagNumber,lowLimit,highLimit)); }else { list.add(CalService.score(flagNumber)); } } break; } //分数括号都有的四则运算 case 4: { for (int i = 0; i < n; i++) { int type = (int) (Math.random()*3); if (type == 1) { list.add(CalService.bracketArithmetic(flagNumber,lowLimit,highLimit)); }else if (type == 2){ list.add(CalService.score(flagNumber)); }else { list.add(CalService.SimpleArithmetic(flagNumber,lowLimit,highLimit)); } } break; } } return list; }
3.3 对于数据的处理
本项目我认为个人最好的地方还是在于自己使用了map来处理式子。一开始我是整个运算式子作为一个字符串丢给前台,然后前台同学自己解析题目和答案,后来想想既然题目答案都是我这里生成的,我干脆使用hashmap存储就好了。
题目作为key,题目答案作为value,一道题是一个map集合,生成式子式 的函数返回map类型,规定泛型为<String,String>类型然后所有题目构成一个list,和其他状态值数据一起用json打包,传递给前台。
List<Map<String,String>> list = new ArrayList<>(); 主方法里: //只有简单的四则运算 case 1: { for (int i = 0; i < n; i++) { list.add(CalService.SimpleArithmetic(flagNumber,lowLimit,highLimit)); } break; } //生成简单四则运算题目方法,返回Map<String,String>类型。 static Map<String,String> SimpleArithmetic(int flagNumber,int lowLimit,int highLimit); 这里我用的是阿里巴巴的json解析包 JSONObject jsonObject = new JSONObject(); List list = judgeQuestion(n,level,flagNumber,lowLimit,highLimit); jsonObject.put("questions",list); jsonObject.put("status",true); response.getWriter().write(jsonObject.toString());
3.4 跨域问题
跨域问题,之前使用spring框架都是通过注解解决,这次我是百度看了一篇博客,通过在服务器修改response的消息响应头。
在servlet上加这些代码就解决了。
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin")); response.setHeader("Access-Control-Allow-Credentials", "true"); response.setHeader("P3P", "CP=CAO PSA OUR"); if (request.getHeader("Access-Control-Request-Method") != null && "OPTIONS".equals(request.getMethod())) { response.addHeader("Access-Control-Allow-Methods", "POST,GET,TRACE,OPTIONS"); response.addHeader("Access-Control-Allow-Headers", "Content-Type,Origin,Accept"); response.addHeader("Access-Control-Max-Age", "120"); }
但其实自己还是没太懂这其中原理,以后一定要好好补下跨域问题这方面的知识!
3.5文件下载以及路径问题
然后就是文件下载功能,这个相比于文件上传功能简单很多,用起来就一二十行代码。
我在前台每次发送生成题目请求的时候,将传来的学号写入文本,生成题目的时候除了放进list容器,还一并写入文本,最终将这个文本生成于服务器某个路径下。
至于路径问题,我研究了很久。我生成文件明明写的是 ../ 讲道理应该是项目的上一级目录,但我找了半天没找到,去target目录下也没找到,后来在三金的提示下,用System.out.println(file.getAbsolutePath());打印一下看看文件跑去哪了,后来发现跑到tomcat的目录了,查了以下弄明白了原理。https://bbs.****.net/topics/300011392
因为我写的是相对路径
File file = new File("../result.txt");
项目用的是tomcat启动,也就是通过startup.bat启动,因此/代表当前程序的目录,就是start.up程序。
然后../就代表上一级目录,也就是tomcat目录下,和bin目录同级,可以看到这个万恶的result.txt安详的躺在这里。
于此,我对tomcat文件存放有了进一步的了解。 但是,我在服务器部署上,遇到了另一个问题,就是它new file生成文件时候跑到了奇怪的位置,不在tomcat目录。跑到了和c盘另一个目录的文件中。百度了下没找到很好的解释,好像是“资源管理器”的问题。这个我后面再找资料查查。 没办法,那就下载文件的servlet中就写result.txt的绝对路径吧。 File file = new File("C:\\Users\\Administrator\\result.txt"); InputStream ins = new FileInputStream(file); /* 设置文件ContentType类型,这样设置,会自动判断下载文件类型 */ response.setContentType("multipart/form-data"); /* 设置文件头:最后一个参数是设置下载文件名 */ response.setHeader("Content-Disposition", "attachment;filename=" + file.getName()); try { OutputStream os = response.getOutputStream(); byte[] b = new byte[1024]; int len; while ((len = ins.read(b)) > 0) { os.write(b, 0, len); } os.flush(); os.close(); ins.close(); } catch (IOException ioe) { ioe.printStackTrace(); }
四、个人总结:
因为是做一个web项目,一开始纠结后台使用servlet,还是使用SSM框架技术整合纠结了半个多小时。但后来想想目标功能较少,接口就一两个,而且不涉及数据库,再使用框架技术的话就有点像用“高射炮打蚊子”。由于这项目是在之前的作业基础上进一步扩展,然后之前的作业我功能实现都较为完整,加上自己有一定的web开发经验,所以一开始我觉得这个作业非常简单,不用数据库的话,写几个接口丢给前台就完事了,加上和前台搭档合作过很多次了,对她的能力蛮相信的。所以我一开始非常错误的预估了我的用时,最初我预计半小时到一个小时就能完成。后来功能增增改改,我个人后台部分一共做了五个多小时。。 庆幸的是,我之前模块编写的比较明确,各个函数分工都是比较清楚的。因此,在这基础上进一步扩展时就方便了好多。
总之,这个项目收获匪浅!