第三次作业
0 小组成员
王田路 / 2017202110104
程环宇 / 2017202110110
1.项目Github地址
https://github.com/Cynnn/JavaWebArithmetic
2.题目
结对项目:四则运算题目生成程序(基于GUI)http://www.cnblogs.com/hyfatwhu/p/7605757.html
3.估计花费时间
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 10 | |
· Estimate | · 估计这个任务需要多少时间 | 10 | |
Development | 开发 | 1800 | |
· Analysis | · 需求分析 (包括学习新技术) | 240 | |
· Design Spec | · 生成设计文档 | 30 | |
· Design Review | · 设计复审 (和同事审核设计文档) | 30 | |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | |
· Design | · 具体设计 | 60 | |
· Coding | · 具体编码 | 1200 | |
· Code Review | · 代码复审 | 60 | |
· Test | · 测试(自我测试,修改代码,提交修改) | 150 | |
Reporting | 报告 | 180 | |
· Test Report | · 测试报告 | 120 | |
· Size Measurement | · 计算工作量 | 30 | |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | |
合计 | 1990 |
4.解题思路
l 整体架构:MVC
l 开发框架:Spring boot + Thymeleaf模板
l 前端:bookstrap + javascript +css
l 决定开发web应用后,我们通过上网查资料以及请教熟悉web开发的同学,选择使用Spring boot框架来完成本次项目。选择Spring boot框架的原因如下:
- Spring Boot可以以jar包的形式来运行,运行一个Spring Boot项目我们只需要通过java -jar xx.jar类运行,非常方便。
- Spring Boot可以内嵌Tomcat,这样我们无需以war包的形式部署项目。
- 使用Spring或者SpringMVC我们需要添加大量的依赖,而这些依赖很多都是固定的,这里Spring Boot 通过starter能够帮助我们简化Maven配置。
l 要求功能的解决思路:
1.记录用户的对错总数,程序退出再启动的时候,能把以前的对错数量保存并在此基础上增量计算。
- 每次用户做完题目后都将答题对错情况发送到服务器,由服务器记录来用户的对错数量,再将对错数量显示在页面上。
2.有计时功能,能显示用户开始答题后的消耗时间。
- 使用Javascript的计时器插件,用户点击开始答题时,计时器启动,点击结束答题时,计时器停止。
3.界面支持中文简体/中文繁体/英语,用户可以选择一种。
- 为每一个有文字的节点标注id,然后在xml文件里为这些节点赋值。用户点击按钮选择不同的语言后,将下载对应的xml文件,解析后显示用户想要的语言。
5.设计实现过程
Spring MVC架构图,如下:
后台运算器:
- Fractions用两个int变量分别表示分子分母,提供静态函数maxCommonDivisor(int,int)和minCommonMultiple(int, int),分别是求最大公约数函数和最小公倍数函数,还包含将可转化为整数的分数转化为整数的函数changeToInteger()。
- Question采用两种数组保存操作数,分别是分数操作数和整数操作数,又创建两个括号数组,分别是左括号和右括号,专门的乘除运算符数组以及用于计算的两个堆栈。包括检查括号约束情况函数checkBracket()、计算函数calculate()、优先级比较函数compare(str: char)等。
- Calculate包含四个静态函数,分别是加减乘除。Control包含main函数,从控制台读取到题目个数,与用户进行交互。
我们的四则运算流程图如下:
Spring MVC架构中的controller,service,application类图如下:
controller映射并处理用户请求,包括login,index,getnumber,cheform。
service为controller提供调用的方法,包括生成随机表达式、读写历史记录。
webapplication是主程序,程序的入口。
6.代码说明
6.1前端
6.1.1 js代码
1 //秒表函数 2 $('#runner').runner({ 3 4 milliseconds: false, 5 format: function millisecondsToString(milliseconds) { 6 var oneHour = 3600000; 7 var oneMinute = 60000; 8 var oneSecond = 1000; 9 var seconds = 0; 10 var minutes = 0; 11 var hours = 0; 12 var result; 13 14 if (milliseconds >= oneHour) { 15 hours = Math.floor(milliseconds / oneHour); 16 } 17 18 milliseconds = hours > 0 ? (milliseconds - hours * oneHour) : milliseconds; 19 20 if (milliseconds >= oneMinute) { 21 minutes = Math.floor(milliseconds / oneMinute); 22 } 23 24 milliseconds = minutes > 0 ? (milliseconds - minutes * oneMinute) : milliseconds; 25 26 if (milliseconds >= oneSecond) { 27 seconds = Math.floor(milliseconds / oneSecond); 28 } 29 30 milliseconds = seconds > 0 ? (milliseconds - seconds * oneSecond) : milliseconds; 31 32 if (hours > 0) { 33 result = (hours > 9 ? hours : "0" + hours) + ":"; 34 } else { 35 result = "00:"; 36 } 37 38 if (minutes > 0) { 39 result += (minutes > 9 ? minutes : "0" + minutes) + ":"; 40 } else { 41 result += "00:"; 42 } 43 44 if (seconds > 0) { 45 result += (seconds > 9 ? seconds : "0" + seconds); 46 } else { 47 result += "00"; 48 } 49 50 return result; 51 } 52 53 }); 54 55 //buttons 56 57 $('#startBtn').click(function() { 58 $('#runner').runner('start'); 59 $(this).addClass('activeBtn'); 60 $('#stopBtn').removeClass('activeBtn'); 61 62 }); 63 64 $('#stopBtn').click(function() { 65 $('#runner').runner('stop'); 66 $(this).addClass('activeBtn'); 67 $('#startBtn').removeClass('activeBtn'); 68 }); 69 70 $('#resetBtn').click(function() { 71 $('#runner').runner('reset'); 72 $('#stopBtn').removeClass('activeBtn'); 73 $('#startBtn').removeClass('activeBtn'); 74 }); 75 76 $('#enBtn').click(function() { 77 lang = "en"; 78 ChangeLanguage(); 79 }); 80 81 $('#chBtn').click(function() { 82 lang = "ch"; 83 ChangeLanguage(); 84 }); 85 86 $('#hkBtn').click(function() { 87 lang = "hk"; 88 ChangeLanguage(); 89 }); 90 //获得题目数的点击事件 91 $('#numberBtn').click(function(){ 92 var number = document.getElementById("numberText"); 93 var url = "http://localhost:8080/number?Action=getnumber&Number="+number.value; 94 top.location = url; 95 }); 96 //获取历史记录 97 $('#history').click(function(){ 98 var wrong = document.getElementById("wrongSpTxt"); 99 var right = document.getElementById("rightSpTxt"); 100 alert("right:"+right.innerHTML+"wrong:"+wrong.innerHTML); 101 }); 102 103 104 var flag = false;//判断标志 105 //检查结果 106 function chkform(){ 107 if(flag){ 108 alert("已经判断过对错,请刷新页面!"); 109 return; 110 } 111 flag=true; 112 var list = document.getElementById("list"); 113 var items = list.getElementsByTagName("span"); 114 var inputs = list.getElementsByTagName("input"); 115 var right = 0; 116 var wrong = 0; 117 for(var i=0;i<items.length;i++){ 118 if(i%2==1){ 119 var item = items[i]; 120 if(item.innerText == inputs[(i-1)/2].value){ 121 item.style.display=""; 122 right++; 123 }else{ 124 var item = items[i]; 125 item.style.display=""; 126 wrong++; 127 } 128 } 129 } 130 $.post("/index",{Action:"chkform",Right:right,Wrong:wrong},function(data,textStatus){ 131 alert("right:"+right+"wrong:"+wrong);}); 132 133 } 134 var lang = "hk";//语言变量 135 //节点内容更改 136 function ChangeLanguage() { 137 var langpath = lang + ".xml";//资源文件路径 138 TranslateElementsAsy(document, 'SPAN', 'innerHTML', langpath); 139 TranslateElementsAsy(document, 'INPUT', 'value', langpath); 140 TranslateElementsAsy(document, 'th', 'innerHTML', langpath); 141 TranslateElementsAsy(document, 'h3', 'innerHTML', langpath); 142 TranslateElementsAsy(document, 'h1', 'innerHTML', langpath); 143 TranslateElementsAsy(document, 'a', 'innerHTML', langpath); 144 145 } 146 //获取xml文件节点内容 147 function getString(path, req_name, xmlDoc) { 148 //解析XML 149 //var oError = xmlDoc.parseError; 150 var nodeName = xmlDoc.getElementsByTagName(req_name); 151 if (nodeName[0] == null || nodeName[0] == "undefined") { 152 return null; 153 } else { 154 return nodeName[0].childNodes[0].nodeValue; 155 } 156 } 157 158 //对不同节点,使用不同属性 159 function TranslateElementsAsy(targetDocument, tag, propertyToSet, path) { 160 $.ajax({ 161 url: path, 162 type: 'get', 163 async: false, 164 success: function (data) { 165 var e = targetDocument.getElementsByTagName(tag); 166 for (var i = 0 ; i < e.length ; i++) { 167 var sKey 168 sKey = e[i].getAttribute('id'); 169 if (sKey) { 170 var s = getString(path, sKey, data); 171 if (s) { 172 eval('e[i].' + propertyToSet + ' = s'); 173 } 174 } 175 } 176 } 177 }); 178 }
6.1.2主要网页代码
1 <html lang="en" xmlns:th="http://www.thymeleaf.org"> 2 <head> 3 <meta charset="UTF-8" /> 4 <title>Arithmetic</title> 5 <link th:href="@{bootstrap/css/bootstrap.min.css}" rel="stylesheet" /> 6 <link th:href="@{bootstrap/css/bootstrap-theme.min.css}" rel="stylesheet" /> 7 <link rel="stylesheet" th:src="@{style.css}"/> 8 <script type="text/javascript"> 9 function toTop(){ 10 window.scroll(0,0); 11 } 12 </script> 13 </head> 14 15 <body> 16 <nav class="navbar navbar-inverse navbar-fixed-top"> 17 <div class="container"> 18 <div class="navbar-header"> 19 <a class="navbar-brand" id="na1" href="#">四则运算生成程序</a> 20 </div> 21 <div> 22 <ul class="nav navbar-nav navbar-right"> 23 <li class="active"><a href="login" id="index">首页</a></li> 24 <li><a href="#" id="history">历史记录</a></li> 25 <li class="dropdown"> 26 <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="la"> 27 语言切换 <b class="caret"></b> 28 </a> 29 <ul class="dropdown-menu"> 30 <li><a id="chBtn" type="submit">中文简体</a></li> 31 <li><a id="hkBtn" type="submit">中文繁体</a></li> 32 <li><a id="enBtn" type="submit" >英文</a></li> 33 34 </ul> 35 </li> 36 </ul> 37 </div> 38 </div> 39 </nav> 40 <div class="jumbotron"> 41 <div class="container"> 42 <br></br><br></br> 43 <span style="display:inline-block;width:900px;text-align:left;font-size:50px;" id="head" th:text="欢迎来到Arithmetic"></span> 44 <span style="display:inline-block;text-align:left;font-size:25px;" id="text1" th:text="在这里,你可以进行四则运算练习,记录自己做题的时间。在这里,你能够查看做题的历史记录"></span><br></br> 45 <span style="display:inline-block;text-align:left;font-size:25px;" id="text2" th:text="这里是Arthimetic"></span><br></br><br></br> 46 <input class="btn btn-primary" id="startBtn" type="submit" value="开始答题>>"></input> 47 <input class="btn btn-primary" id="stopBtn" type="submit" value="结束答题>>"></input> 48 49 <span id="runner" style="font-size:30px;"></span> 50 51 </div> 52 </div> 53 <div class="container"> 54 <div th:if="${not #lists.isEmpty(expression)}"> 55 <div class="panel panel-default"> 56 <div class="panel-heading"> 57 <span class="panel-title" id="titleH3">本次测试题目数:</span> 58 <span id="questionNumber" th:text="${expression.size()}"></span> 59 <span style="display:inline-block;width:700px;text-align:left;"></span> 60 <input class="btn btn-default" id="judgeBtn" type="submit" onclick="return chkform()" value="判断正误"></input> 61 <input class="btn btn-default" id="refreshBtn" type="submit" onclick="location.reload(true)" value="刷新"></input> 62 </div> 63 64 65 <span id="rightSp" th:text="对:" style="display:none"></span><span id="rightSpTxt" th:text="${right}" style="display:none"></span> 66 <span id="wrongSp" th:text="错:" style="display:none"></span><span id="wrongSpTxt" th:text="${wrong}" style="display:none"></span> 67 68 <div class="panel-body"> 69 <ul class="list-group" id="list"> 70 <li class="list-group-item" th:each="arithmeticExpression:${expression}"> 71 <span th:text="${arithmeticExpression.expression}" style="display:inline-block;width:400px;text-align:left;"></span> 72 <input type="text" name="userresult" id="userresult"></input> 73 <span th:text="${arithmeticExpression.printResult()}" style="display:none" id="rightresult"></span> 74 </li> 75 </ul> 76 </div> 77 <div class="panel-footer"> 78 <span style="display:inline-block;width:950px;text-align:left;"></span> 79 <input class="btn btn-default" id="toTop" type="submit" onclick="toTop()" value="返回页面顶部"></input> 80 </div> 81 </div> 82 </div> 83 </div> 84 <script th:src="@{jquery-2.1.1.min.js}" type="text/javascript"></script> 85 <script th:src="@{jquery.runner-min.js}" type="text/javascript"></script> 86 <script th:src="@{bootstrap/js/bootstrap.min.js}" type="text/javascript"></script> 87 <script th:src="@{app.js}" type="text/javascript"></script> 88 </body> 89 </html>
6.2后台
6.2.1 application
1 package com.example.ArithmeticWeb; 2 3 import java.util.Arrays; 4 5 import org.springframework.boot.CommandLineRunner; 6 import org.springframework.boot.SpringApplication; 7 import org.springframework.boot.autoconfigure.SpringBootApplication; 8 import org.springframework.context.ApplicationContext; 9 import org.springframework.context.annotation.Bean; 10 @SpringBootApplication 11 public class ArithmeticWebApplication { 12 13 @Bean 14 public CommandLineRunner commandLineRunner(ApplicationContext ctx) { 15 return args -> { 16 17 System.out.println("Let's inspect the beans provided by Spring Boot:"); 18 19 String[] beanNames = ctx.getBeanDefinitionNames(); 20 Arrays.sort(beanNames); 21 for (String beanName : beanNames) { 22 System.out.println(beanName); 23 } 24 25 }; 26 } 27 public static void main(String[] args) { 28 SpringApplication.run(ArithmeticWebApplication.class, args); 29 } 30 }
6.2.2 controller
1 package com.example.ArithmeticWeb; 2 import java.util.ArrayList; 3 4 import org.springframework.beans.factory.annotation.Autowired; 5 import org.springframework.stereotype.Controller; 6 import org.springframework.ui.Model; 7 import org.springframework.web.bind.annotation.RequestMapping; 8 import org.springframework.web.bind.annotation.RequestMethod; 9 import org.springframework.web.bind.annotation.RequestParam; 10 import org.springframework.web.bind.annotation.ResponseBody; 11 12 import question.Question; 13 14 @Controller 15 public class ArithmeticController { 16 17 @Autowired 18 private ArthmeticService arthmeticService; 19 20 //获取传入个数的question 21 @RequestMapping(value = "/getNumber",method = RequestMethod.GET) 22 public String login(Model model,@RequestParam(value="Action",required=true) String action, 23 @RequestParam(value="Number",required=true)String number){ 24 int num = Integer.parseInt(number); 25 ArrayList<Question> Expression = arthmeticService.getQuestionList(num); 26 model.addAttribute("expression", Expression); 27 int right=0; 28 int wrong=0; 29 String str =arthmeticService.readTxtFile(); 30 if(str!=null&&!str.equals("")){ 31 String[] splits = str.split("\t"); 32 right = Integer.parseInt(splits[0]); 33 wrong = Integer.parseInt(splits[1]); 34 } 35 model.addAttribute("right", right); 36 model.addAttribute("wrong", wrong); 37 return "index"; 38 } 39 //登录界面映射 40 @RequestMapping(value = "/login") 41 public String login(){ 42 return "login"; 43 } 44 //主界面的传输 45 @RequestMapping(value ="/number",method = RequestMethod.GET) 46 public String getNumber(Model model,@RequestParam(value="Action",required=true) String action, 47 @RequestParam(value="Number",required=true)String number) { 48 int num = Integer.parseInt(number); 49 ArrayList<Question> Expression = arthmeticService.getQuestionList(num); 50 model.addAttribute("expression", Expression); 51 int right=0; 52 int wrong=0; 53 String str =arthmeticService.readTxtFile(); 54 if(str!=null&&!str.equals("")){ 55 String[] splits = str.split("\t"); 56 right = Integer.parseInt(splits[0]); 57 wrong = Integer.parseInt(splits[1]); 58 } 59 model.addAttribute("right", right); 60 model.addAttribute("wrong", wrong); 61 return "index"; 62 } 63 @RequestMapping(value ="/Arithmetic") 64 public String index(Model model) { 65 ArrayList<Question> Expression = arthmeticService.getQuestionList(10); 66 model.addAttribute("expression", Expression); 67 int right=0; 68 int wrong=0; 69 String str =arthmeticService.readTxtFile(); 70 if(str!=null&&!str.equals("")){ 71 String[] splits = str.split("\t"); 72 right = Integer.parseInt(splits[0]); 73 wrong = Integer.parseInt(splits[1]); 74 } 75 model.addAttribute("right", right); 76 model.addAttribute("wrong", wrong); 77 return "index"; 78 } 79 //获得用户的对错数 80 @RequestMapping(value ="/index",method = RequestMethod.POST) 81 public String chkform(@RequestParam(value="Action",required=true)String action, 82 @RequestParam(value="Right",required=true)int right, 83 @RequestParam(value="Wrong",required=true)int wrong) { 84 String str =arthmeticService.readTxtFile(); 85 if(str!=null&&!str.equals("")){ 86 String[] splits = str.split("\t"); 87 int r = Integer.parseInt(splits[0]); 88 int w = Integer.parseInt(splits[1]); 89 right+=r; 90 wrong+=w; 91 } 92 arthmeticService.writeTxtFile(right, wrong); 93 return "login"; 94 } 95 96 }
6.2.3 service
1 package com.example.ArithmeticWeb; 2 3 import java.io.BufferedReader; 4 import java.io.File; 5 import java.io.FileOutputStream; 6 import java.io.FileReader; 7 import java.util.ArrayList; 8 import java.util.Random; 9 10 import org.springframework.stereotype.Service; 11 12 import question.Question; 13 @Service 14 public class ArthmeticService { 15 16 public ArrayList<Question> getQuestionList(int size){ 17 ArrayList<Question> Expression = new ArrayList<>(); 18 Random random = new Random(); 19 for(int i=0;i<size;i++){ 20 int operators_num = random.nextInt(6)+1; 21 Expression.add(new Question(operators_num)); 22 } 23 return Expression; 24 } 25 26 public boolean writeTxtFile(int right,int wrong){ 27 File fileName = new File("d:/record.txt"); 28 String content = right +"\t"+wrong; 29 try{ 30 if(!fileName.exists()){ 31 fileName.createNewFile(); 32 } 33 FileOutputStream o=new FileOutputStream(fileName); 34 o.write(content.getBytes("GBK")); 35 o.close(); 36 return true; 37 }catch(Exception e){ 38 e.printStackTrace(); 39 return false; 40 } 41 } 42 43 public String readTxtFile(){ 44 String result=null; 45 FileReader fileReader=null; 46 BufferedReader bufferedReader=null; 47 File fileName = new File("d:/record.txt"); 48 System.out.println(fileName.getAbsolutePath()); 49 try{ 50 if(!fileName.exists()){ 51 fileName.createNewFile(); 52 return ""; 53 } 54 fileReader=new FileReader(fileName); 55 bufferedReader=new BufferedReader(fileReader); 56 String read=null; 57 while((read=bufferedReader.readLine())!=null){ 58 result=read; 59 } 60 if(bufferedReader!=null){ 61 bufferedReader.close(); 62 } 63 if(fileReader!=null){ 64 fileReader.close(); 65 } 66 }catch(Exception e){ 67 e.printStackTrace(); 68 } 69 System.out.println("璇诲彇鍑烘潵鐨勬枃浠跺唴瀹规槸锛�"+"\r\n"+result); 70 return result; 71 } 72 73 }
7.测试运行
7.1 欢迎页
7.2 主页面
7.3 多语言+判断正误
7.4 历史记录功能
7.5 计时功能
7.6 单元测试+代码覆盖率
8.合作情况
王田路:主要负责前端网页界面开发,运用html和JavaScript设计网站精美的界面,测试运行网站,优化网站,保证网站正常运行。
在领航员的陪伴下,前端排除掉了大量的语法错误和算法错误。后端作为领航员,解答驾驶员的问题,共同查找资料,一起学习新知识。
程环宇:主要负责后台服务器开发,映射并处理用户不同请求,返回前端需要的数据,完成用户功能。
在前端作为领航员,帮助驾驶员检查,做总体设计。在后端作为驾驶员,与领航员共同进步。
9.项目小结
9.1 王田路
首先通过这次的结对项目,我对两人合作开发项目的整个流程有了清晰的认识。结对编程是我之前没有接触过的,驾驶员和领航员角色的设置让整个编程过程更加顺利。在本次项目的开发过程中我负责的是前端的实现,之前对Javascript/css并不是特别熟悉,通过这次项目也使自己的能力得到了提升。
然后要感谢我的搭档,我之前的编程经验可能主要注重的是实现功能,对代码的结构和项目的架构考虑的比较少,而这也是一个软件非常重要的部分。这次是我的搭档首先提出使用Spring boot框架,并且他上一次作业的代码有非常好的封装性,并且在编码过程中就想到了提供接口以便于扩展,这些都是我需要学习的。
由于时间的问题,这次项目中用到的新知识我没有进行系统的学习,用到的会去网上重点查一下资料,今后有时间会继续学习,提高自己的能力。
9.2 程环宇
这次的作业给了我很大的启发和帮助。
通过这次的项目,不但学习了 spring boot架构,还熟悉了 JavaScript 的编写,大大提高了自己的编码能力。
结对编程可以提高代码的质量,以前被忽略的问题,partner会帮助找到并给出修改意见。代码复审阶段,partner会对代码结构提出意见,帮助修改,使得代码更加清晰易懂。
结对编程可以互补知识,一些我不懂的知识,partner可能会知道,省去了查阅知识的时间,大大提高了开发效率。
以后,我会和partner继续合作,共同学好这门课。
9.3 PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 10 | 10 |
· Estimate | · 估计这个任务需要多少时间 | 10 | 10 |
Development | 开发 | 1800 | 1900 |
· Analysis | · 需求分析 (包括学习新技术) | 240 | 300 |
· Design Spec | · 生成设计文档 | 30 | 35 |
· Design Review | · 设计复审 (和同事审核设计文档) | 30 | 35 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 40 |
· Design | · 具体设计 | 60 | 70 |
· Coding | · 具体编码 | 1200 | 1220 |
· Code Review | · 代码复审 | 60 | 60 |
· Test | · 测试(自我测试,修改代码,提交修改) | 150 | 150 |
Reporting | 报告 | 180 | 240 |
· Test Report | · 测试报告 | 120 | 180 |
· Size Measurement | · 计算工作量 | 30 | 30 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 30 |
合计 | 1990 | 2110 |
10.结对照片