源码仓库地址:https://github.com/WTiffan/SoftWare-Engineering-Work2
前端队友博客:https://blog.csdn.net/Tiffany_HAHA/article/details/89456933
目录
一、项目前后端框架结构介绍
系统由Vue+Springboot的前后端框架搭建,传值方式采用异步交互方式(ajax)。
二、需求分析
1. 定制出题要求。每次出题时用户都可以在界面上定制如下参数:题目数量,算式数值范围(仅包括原始题目与最终结果的绝对值的数值范围),题目中最多有多少个运算符,题目中是否包含乘除法,题目中是否包含括号。在点击相应出题按钮后将生成题目文件(不包含答案)。
2. 做题功能。出题后,用户可以开始答题。
3. 判题功能。用户答题过程中或者全部完成后可以判断对错,并统计分数和时间。
4. 本次界面可以用网页版或客户端任何一种形式完成。
三、功能设计
本系统在完成以上需求分析的全部需求外,并附加了文件下载(下载题目文件)与文件上传(上传本地题目至网页端)功能。
四、后端设计实现
后端框架功能类的设计如下:
1. ExerciseController类:控制器,使用service层的方法,controller通过接收前端传过来的参数进行业务操作,再返回前端。在Controller中共有四个方法,分别为:
public String CreateFile();
public String downloadFile(HttpServletResponse response);
public String file();
public String fileUpload(@RequestParam("fileName") MultipartFile file);
以下为具体代码:
/**
* 产生练习题
* @return
*/
@ResponseBody
@RequestMapping("/toCreate")
public String CreateFile(String _n, String _max, String _min, String _maxOper, String _is_Bracket, String _is_Mul){
int n = Integer.parseInt(_n);
int max = Integer.parseInt(_max);
int min = Integer.parseInt(_min);
int maxOper = Integer.parseInt(_maxOper);
int is_Bracket = Integer.parseInt(_is_Bracket);
int is_Mul = Integer.parseInt(_is_Mul);
JSONArray jsonArray = new JSONArray();
try {
File file = new File("../result.txt");
//如果根目录中存在名为result.txt文件,则删除
if (file.exists()) {
file.delete();
}
//创建文件
file.createNewFile();
//写文件
FileWriter fw = new FileWriter(file);
BufferedWriter bw = new BufferedWriter(fw);
for (int i = 0; i < n; i++) {
String[] strArr = new String[2];
strArr= exerciseService.create(min, max, maxOper, is_Mul, is_Bracket);
String ex = strArr[0];
String ans = strArr[1];
//将题目(无答案)写进文件
bw.write(ex);
bw.newLine();
JSONObject jsonObject = new JSONObject();
jsonObject.put("Exercise", ex);
// jsonObject.put("Answer", ans);
jsonArray.add(jsonObject);
}
bw.flush();
fw.close();
} catch(IOException e) {
e.printStackTrace();
}
return jsonArray.toString();
}
/**
* 文件下载
* @param response
* @return
*/
@RequestMapping("/download")
public String downloadFile(HttpServletResponse response) {
//被下载文件的名称
String fileName = "result.txt";
if (fileName != null) {
//被下载的文件在服务器中的路径
String realPath = "D:\\IdeaProjects";
File file = new File(realPath, fileName);
if (file.exists()) {
// 设置强制下载不打开
response.setContentType("application/force-download");
// 设置文件名
response.addHeader("Content-Disposition", "attachment;fileName=" + fileName);
byte[] buffer = new byte[1024];
FileInputStream fis = null;
BufferedInputStream bis = null;
try {
fis = new FileInputStream(file);
bis = new BufferedInputStream(fis);
OutputStream os = response.getOutputStream();
int i = bis.read(buffer);
while (i != -1) {
os.write(buffer, 0, i);
i = bis.read(buffer);
}
System.out.println("success");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
return null;
}
/**
* 跳转到文件上传处理页面
* @return
*/
@RequestMapping("file")
public String file(){
return "/test";
}
/**
* 文件上传业务逻辑
* @param file
* @return
*/
@RequestMapping("fileUpload")
@ResponseBody
public String fileUpload(@RequestParam("fileName") MultipartFile file){
if(file.isEmpty()){
return "false";
}
String fileName = file.getOriginalFilename();
int size = (int) file.getSize();
System.out.println(fileName + "-->" + size);
String path = "D:\\IdeaProjects\\calculator\\src\\main\\resources" ;
File dest = new File(path + "/" + fileName);
//判断文件父目录是否存在
if(!dest.getParentFile().exists()){
dest.getParentFile().mkdir();
}
try {
//保存文件
file.transferTo(dest);
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
InputStream inputStream = classLoader.getResourceAsStream("result.txt");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
JSONArray jsonArray = new JSONArray();
StringBuffer buffer = new StringBuffer();
String line = null;
while ((line = bufferedReader.readLine()) != null){
buffer.append(line);
JSONObject jsonObject = new JSONObject();
jsonObject.put("Exercise",line);
jsonArray.add(jsonObject);
}
inputStream.close();
return jsonArray.toString();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return "false";
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return "false";
}
}
2. User类:存放实体类,与数据库中的属性值基本保持一致。
3. service类:业务层,进行四则计算的业务方法处理,有接口还有接口的实现方法。
① service 接口如下:
package com.example.demo.service;
/**
* @Author:Liangll
* @Description:
* @Date: 19:50 2019/4/6
*/
public interface ExerciseService {
/**
* 随机产生操作符的下标数组
* @param n
* @param m
* @return
*/
public int[] index(int n, int m);
/**
* 整数生成器
* @param min 数值的最小范围
* @param max 数值的最大范围
* @param maxOper 最大操作符数
* @param is_Mul 是否包含乘除法
* @param is_Bracket 是否包含括号
* @return
*/
public String[] create(int min, int max, int maxOper, int is_Mul, int is_Bracket);
}
② ExerciseServiceImpl 类为 service 接口的具体实现方法。
4. Calculator类:工具类,存放调度场算法和逆波兰表达式(即后缀表达式)的求值方法处理。详见博客:https://blog.csdn.net/liangllhahaha/article/details/88833220
五、前端页面及功能展示
1. 计算器首页
2. 给定题目要求
3. 在线答题界面
4. 答题结果:
5. 下载题集:
6. 扩充题库:
六、总结与感受
1. 后端:在本次项目的开发中,后端队员采用的是第一次接触的springboot框架,因此在实现功能的时候遇到不少坑,例如
① 在实现文件上传功能时控制台出现异常:Error resolving template "xxx", template might not exist or might not be accessible by any of the configured Template Resolvers
解决方法及个人理解:在报出这个异常的方法上添加注解@ResponseBody。此注解用来区别方法的返回值字符串和视图解析器解析的页面名字字符串的冲突。举个栗子,方法A返回的字符串"success",如果和ajax的回调函数里的msg匹配,就弹出一个“执行成功”,因为success只是普通的字符串,所以要在方法上边加上@ResponseBody;方法B的返回值是“/contextList”,而这个就是一个页面contextList.jsp或者是contextList.html。另外,@RestController的作用就相当于@Controller+@ResponseBody的结合体。
② 启动springboot时出现的端口占用问题:由于springboot框架中内置有Tomcat服务器,因此不需在自己的IDE(IDEA)的配置中重新配置Tomcat,直接运行SpringApplication.run(DemoApplication.class, args);即可。
③ 使用注解@CrossOrigin解决前后端跨端口异步传值问题。
感受:此次项目运用了当前后端开发的主流框架springboot,让我不得不惊奇此框架的高效之处,使我不用再耗费大量的时间在框架的配置文件上,而是更专注于核心代码的实现。此外,项目采用的框架组合Vue+Springboot使得前后端代码彻底分离,不会再出现此前在前后端套页的时候只能在同一台电脑上debug的问题,这使得项目开发更加高效。
2. 前端:在此次项目中,前端开发采用的是新框架:vue+element-ui,由于vue.js与后端是分离式开发,免去了套页这一流程,因此在一些功能的实现上也是遇到了很多问题。
① 前后端数据交互:
首先是遇到跨域问题:在同一台电脑上同时启动前端、后端服务器——前端使用4040端口,后端使用8080端口,当前端通过url地址去请求后端传递计算式时出现了跨域问题阻止请求。
解决:后端添加了@CrossOrigin问题解决。
再一个就是前端向后端传值的问题:前端数据是以json形式传递的,但是在后端只能接收固定数据类型的值。
解决:通过写在this.$http()方法中的函数将数据强制进行格式化转换,再修改后端接收的值的类型改为String,在后端代码中若需要的值不是String类型,那么再重新转换。
② 计算后端传递的计算式:
后端负责把计算式生成,通过url传递给前端,前端拿到计算式,负责式子的页面渲染、式子的答案计算【用eval()】以及输入的答案与正确答案做对比。
问题就在于后端传来的式子的“除”是“÷”的形式,然后eval()只认识“/”不认识“÷”。
解决:使用js自带的replace()函数,==> replace(/÷/g,‘/’) 就能将传过来的式子中“÷”转换成“/”,再放入eval()中进行计算即可。
感受:最深的感受还是通过这个项目学到的东西是真的会很多,但是也是耗费了大量的时间和精力。代码这东西如果写不出来就会觉得越写越烦躁,越写越难受。vue这东西新出来没有太久,大家也都是在学习,因此能借鉴和学习的博客不算多质量也不算精。但我还算幸运,绝大多数遇到的问题都是前人所遇到的,问题基本都能通过博客或者一些优秀的学长学姐、同学能帮忙解决。还好我的伙伴很给力,我想要放弃的时候这个伙伴都回来问我“写出来啦没~”,确实在开发的过程中有一个人一直在提醒你、拽着你往前走,真的是让我觉得找到了一个好伙伴!