基于Servlet框架的前后端交互web计算器
目录
这个作业属于哪个课程 | <班级的链接> |
---|---|
这个作业要求在哪里 | <作业要求的链接> |
这个作业的目标 | 完成体现体现前后端分离的web计算器 |
-
git仓库链接和代码规范链接
前端 | 102101422_calculator_frontend · master · 102101422肖宇龙 / 基于Servlet的前后端交互计算器 · GitCode |
后端 | 102101422_calculator_backend · master · 102101422肖宇龙 / 基于Servlet的前后端交互计算器 · GitCode |
-
PSP表格
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 60 | 60 |
• Estimate | • 估计这个任务需要多少时间 | 45 | 60 |
Development | 开发 | 1900 | 2100 |
• Analysis | • 需求分析 (包括学习新技术) | 600 | 850 |
• Design Spec | • 生成设计文档 | 30 | 60 |
• Design Review | • 设计复审 | 30 | 45 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 30 | 20 |
• Design | • 具体设计 | 60 | 75 |
• Coding | • 具体编码 | 600 | 700 |
• Code Review | • 代码复审 | 120 | 90 |
• Test | • 测试(自我测试,修改代码,提交修改) | 30 | 45 |
Reporting | 报告 | 60 | 65 |
• Test Repor | • 测试报告 | 30 | 25 |
• Size Measurement | • 计算工作量 | 20 | 15 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 10 | 25 |
合计 | 2020 | 2225 |
-
成品展示
1、基础计算器功能
- 功能1:加减乘除、取余、括号运算
- 基础+,-,* ,/,%,()运算,运算顺序正确,结果正确,并将输入的字符串和结果存储到后端json文件中
- 功能2:清零回退
- 实现字符串输入的清零和回退功能
- 功能3:错误提示
- 在第一个功能正常的情况下增加非法输入提示,例如:0不能作为除数,括号不匹配等
出现括号不匹配等错误会报ERROR,但是遇到除数为0错误会报Infinity
- 功能4:读取历史记录
- 能用返回上一个计算结果,并且查看历史记录读取最后十个字符串式子和对应的计算结果。
- 附加功能1:科学计算器
- 计算根号、三角函数、log、幂运算等附加功能
- 附加功能2:页面原型设计
- 仿照百度计算器进行原型设计,页面设计复现与现有的计算器页面相同。
比较完整复刻了百度计算器的风格以及按键等布局,符合现有计算器页面。
2、利率计算器功能
- 计算存款、贷款利息
- 实现利率计算器,数据库中存储利率表(可随时修改利率),对应利率如下图所示。用两个输入框分别读取前端的输入数据
- 输入存款/贷款金额,
- 存/贷时长(单位:年)。
1. 存款利息
存款利率计算器提供了两种计算方式,活期和定期。存款期限是一个下拉框的选择,用户根据实际情况选择存款期限,计算器将根据不同的存款方式进行计算得到所得利息和本息总计。
2. 贷款利息
贷款利息计算器可以通过存款利息计算器跳转,其使用方式与存款利息计算器相似,但是可以用户自行填写贷款期限。
-
设计实现过程
本次设计的web计算器是基于Servlet框架设计的,Servlet是由Java编写的服务器端程序,其主要功能在于交互式地浏览和修改数据,生成动态Web内容。一般情况下,将其理解为任何实现了这个Servlet接口的类。
后端开发语言为Java,同时使用了Springboot框架。前端则使用HTML+CSS+JS进行编写。对于数据要存储在后端这点要求一开始我是有一些困惑的,由于没有接触过任何数据库,也有一部分时间原因,所以并没有学习怎么通过数据库存储,但是也做到了将数据存储在后端,每次要使用时再加以调用。主要方法是自己编写写入json文件、读出json文件等功能接口,将计算器计算结果以及利率等信息存入后端json文件,在需要的时候调用接口从后端读出数据或者更改数据即可。
后端对于前端计算器传入的算式,使用了 JDK 底层调用 javaScript 的运算公式,JDK 自带的类可以实现调用 JS 的功能,可以实现执行字符串中的运算公式的功能,同时也直接避免精度丢失。
-
代码说明
前端
style.css
table{
margin:0 auto;
background-color: rgb(234,236,243);
padding: 10px 10px 10px 10px;
color: rgb(40,49,85);
border-radius: 20px;
box-shadow: 1.5px 1.5px 1.5px rgba(0, 0, 0, .2);
}
.but{
width: 80px;
height: 60px;
background-color : white;
font-size: 25px;
margin: 10px 10px 0 0;
border-radius: 20px;
background-color: rgb(250,250,250);
box-shadow: 1.5px 1.5px 1.5px rgba(0, 0, 0, .2);
border:none;
transition: all 0.15s;
}
.but:hover{
box-shadow: 4px 4px 4px rgba(0, 0, 0, .2);
}
.but_row, .but_column{
background-color: rgb(221,224,235);
}
.but_ac{
background-color: rgb(255,181,47);
color: rgb(248,248,249);
}
.but_back{
background-color: rgb(255,181,47);
color: rgb(248,248,249);
}
.but_eq{
background-color: rgb(79,110,242);
color: rgb(248,248,249);
}
.but_reverse{
background-color: rgba(10, 41, 236, 0.24);
color: rgb(248,248,249);
}
.screen{
width: 520px;
height: 120px;
font-size: 35px;
margin:0 auto;
padding: 0 15px 0 15px;
background-color: white;
text-align:right; /* 使用户输入的表达数从屏幕的右边开始显示 */
border-radius: 10px;
font-family: "Agency FB";
}
这段代码定义了前端计算器的界面风格以及按钮样式,其中利用阴影以及按钮去尖角使页面更加美观。
script.js
$(document).ready(function () {
let expression = "";
/*点击输入值*/
$(".but_val").click(function () {
let type = $(this).val();
if (expression.trim() === "ERROR".trim()) {
expression = type;
} else {
expression = expression + type;
}
$(".screen").val(expression);
})
/*点击清除*/
$(".but_back").click(function () {
expression = expression.slice(0, expression.length - 1);
$(".screen").val(expression);
})
/*点击清空*/
$(".but_ac").click(function () {
expression = "";
$(".screen").val(expression);
})
/*点击等号提交*/
$(".but_submit").click(function () {
if (expression == null || expression === '' || expression === 'ERROR' ||
!expression.includes('+') && !expression.includes('-') && !expression.includes('×') && !expression.includes('÷')
&& !expression.includes('sin') && !expression.includes('cos') && !expression.includes('tan')
&& !expression.includes('√') && !expression.includes('ln') && !expression.includes('%')
&& !expression.includes('pow')){
$(".screen").val(expression);
} else {
expression = $(".screen").val();
$.post("./calculate", {//!!!!!!!!注意./
"expression": expression
}, function (result) {
expression = result;
$(".screen").val(expression);
});
}
})
});
这段代码 定义了点击不同按钮时触发的事件,用来描述网页的行为,其中当按下=键会进行一个初步判断,判断是否需要进行计算。同时在这里用到了 Ajax 函数 post,post() 方法通过 HTTP POST 请求从服务器载入数据。其语法如下:
jQuery.post(url,data,success(data, textStatus, jqXHR),dataType)
HTML文件
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>计算器</title>
<link rel="stylesheet" type="text/css" href="./css/style.css">
<script type="text/javascript" src="./js/jquery-3.6.1.min.js"></script>
<script type="text/javascript" src="./js/script.js"></script>
</head>
<body>
<body>
<table>
<tr>
<td colspan="6"><label>
<input class="screen" type="text" disabled/>
</label></td>
</tr>
<tr>
<td><input class="but but_val montage but_row" type="button" value="("></td>
<td><input class="but but_val but_row" type="button" value=")"></td>
<td><input class="but but_val but_row" type="button" value="%"></td>
<td><input class="but_back but" type="button" value="DEL"></td>
<td><input class="but_ac but" type="button" value="AC"></td>
<td><input class="but but_val" type="button" value=","></td>
</tr>
<tr>
<td><input class="but but_val" type="button" value="7"></td>
<td><input class="but but_val" type="button" value="8"></td>
<td><input class="but but_val" type="button" value="9"></td>
<td><input class="but but_val but_column" type="button" value="÷"></td>
<td><input class="but but_val but_column" type="button" value="sin"></td>
<td><input class="but but_val but_column" type="button" value="pow"></td>
</tr>
<tr>
<td><input class="but but_val" type="button" value="4"></td>
<td><input class="but but_val" type="button" value="5"></td>
<td><input class="but but_val" type="button" value="6"></td>
<td><input class="but but_val but_column" type="button" value="×"></td>
<td><input class="but but_val but_column" type="button" value="cos"></td>
<td><input class="but but_val but_column" type="button" value="ln"></td>
</tr>
<tr>
<td><input class="but but_val" type="button" value="1"></td>
<td><input class="but but_val" type="button" value="2"></td>
<td><input class="but but_val" type="button" value="3"></td>
<td><input class="but but_val but_column" type="button" value="-"></td>
<td><input class="but but_val but_column" type="button" value="tan"></td>
<td><input class="but but_val but_column" type="button" value="√"></td>
</tr>
<tr>
<td><input class="but but_val" type="button" value="0"></td>
<td><input class="but but_val" type="button" value="."></td>
<td><input class="but but_submit but_eq" type="button" value="="></td>
<td><input class="but but_val but_column" type="button" value="+"></td>
<td><input class="but but_val" type="button" value="π"></td>
<td><input class="but but_val" type="button" value="e"></td>
</tr>
</table>
</body>
<p style="margin: 20px 20px"> <a style="font-size: 30px;font-weight:bold;color:orange " href="./list.html">查询历史结果→</a></p>
<p style="margin: 20px 20px"> <a style="font-size: 30px;font-weight:bold;color:orange " href="./savemoney.html">利息计算器→</a></p>
</body>
</html>
<!DOCTYPE html>
<head>
<meta charset="utf-8" />
<title>个人存款计算器</title>
<script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
<link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<div class="text-center center-block">
<table class="table table-bordered">
<tr class="bg-primary text-center">
<td colspan="2">存款利息计算器</td>
</tr>
<tr>
<td>存款金额</td>
<td>
<input placeholder="请输入数据" class="form-control" type="text" id="money">
</td>
</tr>
<tr>
<td>存款方式</td>
<td style ="text-align:left;">
<input id="活期" type="radio" checked="checked" name="method"/>活期
<input type="radio" id="定期" name="method"/>定期
</td>
</tr>
<tr>
<td>存款期限</td>
<td>
<select class="form-control" id="year">
<option value="0.0285">三个月</option>
<option value="0.0305">半年</option>
<option value="0.0325">一年</option>
<option value="0.0415">二年</option>
<option value="0.0475">三年</option>
<option value="0.0525">五年</option>
</select>
</td>
</tr>
<tr>
<td>所得利息</td>
<td>
<input readonly="readonly" class="form-control" type="text" id="balance">
</td>
</tr>
<tr>
<td>本息合计</td>
<td>
<input class="form-control" readonly="readonly" type="text" id="total">
</td>
</tr>
<tr>
<td colspan="2">
<button onclick="calc()" class="btn btn-danger btn-block">开始计算</button>
</td>
</tr>
</table>
<p style="margin: 20px 20px"> <a style="font-size: 30px;font-weight:bold;color:orange " href="./index.html">←科学计算器</a></p>
<p style="margin: 20px 20px"> <a style="font-size: 30px;font-weight:bold;color:orange " href="./borrowmoney.html">贷款利息计算器→</a></p>
</div>
这两段代码主要定义了计算器页面布局的信息,包括相关页面的跳转。
后端
(由于代码过于冗长,这里以科学计算器为例)
data.json
data.json文件用于将前端输入的计算结果记录到后端本地文件中,在每次查询历史记录时进行后端文件查询,再将查询到的数据返回到前端页面。该文件中信息包含计算结果、输入表达式以及请求计算时间。
public class CalculatorServiceImpl implements CalculatorService {
private CalculatorDao calculatorDao;
@Override
public String calculator(String expression) {
//前端传入的表达式需要替换
expression = expression
.replaceAll("×", "*")
.replaceAll("÷", "/")
.replaceAll("π","Math.PI")
.replaceAll("sin","Math.sin")
.replaceAll("cos","Math.cos")
.replaceAll("tan","Math.tan")
.replaceAll("√","Math.sqrt")
.replaceAll("e","Math.E")
.replaceAll("ln","Math.log")//JAVA里log表示ln
.replaceAll("pow","Math.pow");
String result = "";
//这里捕获异常 如果表达式出错
try {
//这是将计算表达式 传给CalculateUtil 计算出表达式
result = CalculateUtil.execute(expression);
} catch (ScriptException e) {
result = "ERROR";
}
//将结果写入data.json 文件中
calculatorDao.saveRecord(expression, result, DateUtil.getNowTime());
return result;
}
/*
* 这里是查询历史结果
*/
@Override
public String getRecord() {
return calculatorDao.readRecord();
}
}
这段代码对传入的算式进行初步判断,替换掉一些字符,方便后续计算。同时将结果写入后端data.json文件中 ,以便于查询历史记录。
@WebServlet("/calculate")
public class CalculatorServlet extends HttpServlet {
private CalculatorService calculatorService;
@Override
public void init() {
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
calculatorService = webApplicationContext.getBean(CalculatorService.class);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
String expression = req.getParameter("expression");
//计算结果
String result = calculatorService.calculator(expression);
PrintWriter out = resp.getWriter();
out.println(result);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
这段代码是对计算机接口的描述与定义, doGet() 方法有两个参数,第一个参数是 HttpServletRequest 类的对象,第二个参数是 HttpServletResponse 类的对象。我们这里使用它来获取计算结果。
public class CalculateUtil {
public static String execute(String str) throws ScriptException {
ScriptEngine jse = new ScriptEngineManager().getEngineByName("JavaScript");
return jse.eval(str).toString();//用toString()
}
}
这段代码是计算器计算的核心, 使用了 JDK 底层调用 javaScript 的运算公式,JDK 自带的类可以实现调用 JS 的功能,可以实现执行字符串中的运算公式的功能,同时也直接避免精度丢失。
@Repository
public class CalculatorDaoImpl implements CalculatorDao {
/**写入的文件绝对路径*/
private static String jsonPath = "E:\\ideaproject\\java-web-calculator-master\\src\\main\\resources\\data.json";
/**是否追加或覆盖*/
private static Boolean isAppend = true;
@Override
public String readRecord() {
/** 用到JsonUtil 工具类 */
return JsonUtil.readJson(jsonPath);
}
@Override
public void saveRecord(String expression, String result, String submitTime) {
Map<String, String> map = new HashMap<>();
map.put("expression", expression);
map.put("result", result);
map.put("submitTime", submitTime);
/** 利用JsonUtil 将 表达式 结果 提交的时间 以json 格式写入 文件中 **/
Boolean isSuccess = JsonUtil.writeJson(jsonPath, map, isAppend);
if (!isSuccess) {
System.out.println("保存失败!");
}
}
}
这一部分定义了怎么写入json文件以及写入的内容,包括文件的绝对路径和是否覆盖等信息。
public class JsonUtil {
/**
* 往json文件中写入数据
*
* @param jsonPath json文件路径
* @param inMap Map类型数据
* @param flag 写入状态,true表示在文件中追加数据,false表示覆盖文件数据
* @return 写入文件状态 成功或失败
*/
public static Boolean writeJson(String jsonPath, Map<String, String> inMap, boolean flag) {
// Map数据转化为Json,再转换为String
String data = new JSONObject(inMap).toString();
File jsonFile = new File(jsonPath);
try {
// 文件不存在就创建文件
if (!jsonFile.exists()) {
jsonFile.createNewFile();
}
FileWriter fileWriter = new FileWriter(jsonFile.getAbsoluteFile(), flag);
BufferedWriter bw = new BufferedWriter(fileWriter);
bw.write(data + ",");
bw.close();
return true;
} catch (IOException e) {
return false;
}
}
/**
* 读取json文件数据
*
* @param jsonPath json文件路径
* @return 字符串
*/
public static String readJson(String jsonPath) {
File jsonFile = new File(jsonPath);
String result = "";
try {
FileReader fileReader = new FileReader(jsonFile);
BufferedReader reader = new BufferedReader(fileReader);
StringBuilder sb = new StringBuilder();
while (true) {
int ch = reader.read();
if (ch != -1) {
sb.append((char) ch);
} else {
break;
}
}
fileReader.close();
reader.close();
result = sb.toString();
return "[" + result.substring(0, result.length()-1) + "]";
} catch (IOException e) {
return "[]";
}
}
}
这部分是对json文件进行操作的主要接口,写接口使用Map将数据转化为json写入文件。都利用了FileWriter和FileReader的Web API接口,极大提高了存取速度和效率。它们允许 Web 应用程序异步写入或读取存储在用户计算机上的文件(或原始数据缓冲区)的内容。
-
心路历程和收获
本次作业其实没有做到最好,但是不完美也是一种对自己的激励吧。虽然这次没用数据库也实现了后端数据存储,但是之后希望自己可以去深入学习一下MYSQL,拓展一下自己的技术。这次也有点想挑战一下自己,尝试接触了自己不熟悉的Java。学习的过程就花了很长时间,还有就是对于环境的配置,Tomcat等等,都让我体会到开发真的不是一件容易的事。往往那种找不出语法错误却又跑不通的bug是最耗时间的,这次就碰到了修改了HTML页面但是运行后没有改变的情况,但是好在看开了一点,多刷新几次,问题就解决了。这次的作业是一次挺好的经历,希望未来自己能好好珍惜现有的学习时间。