文章目录一、Git 仓库与代码规范链接二、课程信息三、PSP 表格四、成品展示1. 主界面2. 加法3. 减法4. 乘法5. 除法6. 复合表达式7. 小数计算8. 一元负号9. 除零错误处理10. 计算历史11. 删除历史记录12. 刷新后历史仍在13. 使用清空全部会询问五、设计与实现过程5.1 需求分析5.2 整体系统架构5.3 前端设计5.4 后端设计5.5 API 设计5.6 数据库设计5.7 功能结构图六、代码说明6.1 前端 API 请求封装6.2 后端表达式计算核心6.3 后端计算接口6.4 数据库操作6.5 前端光标处理七、部署7.1 后端部署Railway7.2 前端部署Vercel7.3 前后端联调八、个人历程与收获8.1 开发过程中遇到的问题8.2 新学到的知识8.3 未来可改进的方向九、注意事项一、Git 仓库与代码规范链接前端仓库https://github.com/longzhe356-max/832401118_calculator_frontend后端仓库https://github.com/longzhe356-max/832401118_calculator_backend前端代码规范https://github.com/longzhe356-max/832401118_calculator_frontend/blob/main/codestyle.md后端代码规范https://github.com/longzhe356-max/832401118_calculator_backend/blob/main/codestyle.md二、课程信息项目内容本次作业所属课程前后端分离开发作业要求开发一个前后端分离的计算器系统本次作业目标理解前后端分离架构、REST API 设计和数据库持久化其他参考无三、PSP 表格阶段预估耗时分钟实际耗时分钟需求分析3030系统设计6090前端开发180240后端开发240300表达式计算模块60120数据库设计6060前后端联调120180部署90150博客写作120120总计9601290四、成品展示以下截图展示了计算器系统的主要功能。1. 主界面主界面由输入框、4×5 按钮区和计算历史面板组成。2. 加法输入12结果为3。3. 减法输入5-3结果为2。4. 乘法输入3*4结果为12。5. 除法输入10/2结果为5。6. 复合表达式输入(12)*3结果为9展示了运算符优先级和括号处理。7. 小数计算输入1.52.5结果为4。8. 一元负号输入-58结果为3。9. 除零错误处理输入1/0系统返回错误提示除数不能为零。10. 计算历史每次成功的计算都会存储在后端数据库中并显示在历史面板中。11. 删除历史记录点击删除按钮对应记录会从数据库中移除。12. 刷新后历史仍在刷新页面后历史记录仍然存在证明数据持久化在后端数据库中。13. 使用清空全部会询问使用清空全部会询问点击确定后全部删除。五、设计与实现过程5.1 需求分析本系统是一个前后端分离的计算器需要满足以下核心需求基础计算支持加、减、乘、除四则运算计算必须由后端完成。复合表达式支持运算符优先级、括号、小数、一元正负号、除零处理。计算历史每次成功的计算都存储在后端数据库中前端可查询显示。删除历史支持按 ID 删除指定记录数据从数据库中真实删除。前端只负责界面展示和发送请求后端负责核心计算和数据持久化。5.2 整体系统架构系统采用前后端分离架构前后端通过 HTTP/JSON 通信前端Vue 3 Vite部署在 Vercel后端Spring Boot 3部署在 Railway数据库H2 文件数据库前端Vue 3→ HTTP/JSON → 后端Spring Boot→ H2 数据库5.3 前端设计前端采用 Vue 3 组合式 API分为两个核心组件Calculator.vue负责表达式输入、按钮交互、发送计算请求、显示结果。HistoryList.vue负责展示历史记录、删除单条、清空全部。API 请求统一封装在src/api/index.js中方便维护。5.4 后端设计后端采用经典分层架构Controller 层接收 HTTP 请求返回 JSON 响应。Service 层业务编排调用计算引擎和数据库操作。Repository 层基于 Spring Data JPA自动生成查询。Entity 层JPA 实体映射数据库表。DTO 层请求和响应对象。Config 层CORS 等配置。5.5 API 设计方法URL作用POST/api/calculate计算表达式GET/api/history查询所有历史DELETE/api/history/{id}删除一条历史DELETE/api/history清空所有历史请求示例POST /api/calculate{ “expression”: “(12)*3” }成功响应{ “success”: true, “expression”: “(12)*3”, “result”: 9.0 }错误响应{ “success”: false, “message”: “除数不能为零” }5.6 数据库设计计算历史表calculation_history字段类型说明idBIGINT主键自增expressionVARCHAR计算表达式resultVARCHAR计算结果created_atTIMESTAMP计算时间5.7 功能结构图计算器系统├── 计算器│ ├── 基础计算加减乘除│ ├── 复合表达式优先级、括号、小数、负号│ └── 错误处理除零、语法错误├── 计算历史│ ├── 保存历史│ ├── 查询历史│ └── 删除历史├── 前端│ ├── 用户输入│ ├── 结果展示│ └── 历史展示└── 后端├── API 接口├── 表达式处理├── 计算服务└── 数据库六、代码说明6.1 前端 API 请求封装src/api/index.js统一封装了所有与后端的通信importaxiosfromaxiosconstapiaxios.create({baseURL:https://832401118calculatorbackend-production.up.railway.app/api,timeout:5000})exportconstcalculate(expression)api.post(/calculate,{expression})exportconstgetHistory()api.get(/history)exportconstdeleteHistory(id)api.delete(/history/${id})exportconstclearHistory()api.delete(/history)所有请求共用一个baseURL部署后只需改这一处即可切换后端地址。6.2 后端表达式计算核心ExpressionEvaluator.java是后端最关键的代码负责安全地计算表达式ServicepublicclassExpressionEvaluator{publicdoubleevaluate(Stringexpression){Stringnormalizedexpression.replace(×,*).replace(÷,/);if(normalizednull||normalized.isBlank()){thrownewIllegalArgumentException(表达式不能为空);}try{ExpressionexpnewExpressionBuilder(normalized).build();doubleresultexp.evaluate();if(Double.isNaN(result)||Double.isInfinite(result)){thrownewArithmeticException(计算结果无效);}returnresult;}catch(ArithmeticExceptione){thrownewArithmeticException(除数不能为零);}catch(Exceptione){thrownewIllegalArgumentException(表达式语法错误);}}}这里使用了exp4j库来安全解析数学表达式而不是eval。eval会执行任意代码存在严重安全风险因此被作业禁止。exp4j 只识别数学运算符天然安全。6.3 后端计算接口CalcController.java中的计算接口PostMapping(/calculate)publicResponseEntity?calculate(RequestBodyCalcRequestrequest){try{CalcResponseresponsecalcService.calculate(request.getExpression());returnResponseEntity.ok(response);}catch(ArithmeticException|IllegalArgumentExceptione){returnResponseEntity.badRequest().body(Map.of(success,false,message,e.getMessage()));}}接口接收 POST 请求把 JSON 转成CalcRequest对象调用 Service 计算成功返回 200出错返回 400。6.4 数据库操作CalcService.java负责业务编排publicCalcResponsecalculate(Stringexpression){doubleresultevaluator.evaluate(expression);CalculationHistoryhistorynewCalculationHistory();history.setExpression(expression);history.setResult(String.valueOf(result));history.setCreatedAt(LocalDateTime.now());historyRepository.save(history);returnnewCalcResponse(true,expression,result);}先计算再构造历史记录并存入数据库最后返回结果。6.5 前端光标处理计算器支持在光标位置插入字符而不是永远追加到末尾。这通过insertAtCursor方法实现利用selectionStart和selectionEnd读取光标位置再精确插入。七、部署7.1 后端部署Railway后端部署到 Railway步骤用 GitHub 账号登录 Railway选择Deploy from GitHub repo连接后端仓库Railway 自动识别项目中的Dockerfile并构建镜像添加 Volume挂载路径为/app/data用于持久化 H2 数据库文件生成公网域名。后端公网地址https://832401118calculatorbackend-production.up.railway.app7.2 前端部署Vercel前端部署到 Vercel步骤用 GitHub 账号登录 Vercel选择Import Project连接前端仓库Framework Preset 自动识别为 Vite点击 DeployVercel 自动构建并部署。前端公网地址https://832401118-calculator-frontend.vercel.app7.3 前后端联调前端src/api/index.js中的baseURL指向后端公网地址constapiaxios.create({baseURL:https://832401118calculatorbackend-production.up.railway.app/api,timeout:5000})后端CorsConfig.java允许跨域访问registry.addMapping(/api/**).allowedOrigins(*).allowedMethods(GET,POST,DELETE).allowedHeaders(*);这样前端部署到 Vercel 后可以正常调用 Railway 上的后端接口。八、个人历程与收获8.1 开发过程中遇到的问题问题一前后端跨域前端在localhost:5173后端在localhost:8080端口不同导致浏览器拦截请求。解决方法是在后端添加CorsConfig配置类允许前端来源访问。问题二表达式解析的安全性一开始想用 Java 的ScriptEngine.eval()直接计算表达式但作业明确禁止eval因为它会执行任意代码存在严重安全风险。后来改用exp4j库只识别数学运算符既能支持运算符优先级、括号、小数、一元正负号又保证了安全性。问题三部署时的 JDK 版本不匹配最初 Dockerfile 中使用 JDK 17但pom.xml中指定了 Java 21导致 Railway 构建时报错Fatal error compiling: error: release version 21 not supported解决方法是把 Dockerfile 中的基础镜像统一升级到 JDK 21。问题四H2 数据库持久化部署到 Railway 后发现每次重新部署都会清空历史记录。原因是 H2 数据库文件写在容器内容器重建后会丢失。解决方法是在 Railway 中挂载 Volume 到/app/data目录让数据库文件持久化保存。问题五前端输入框光标位置最初在光标处插入字符时光标总是跳到末尾。原因是v-model会在每次更新后重置光标。解决方法是改用:valueinput并手动通过selectionStart/selectionEnd控制光标位置。8.2 新学到的知识前后端分离架构前端只负责展示和交互后端负责核心逻辑和数据持久化。REST API 设计使用合适的 HTTP 方法和状态码接口职责清晰。Spring Boot 分层架构Controller / Service / Repository / Entity / DTO 各司其职。JPA 自动建表通过注解定义实体Spring Boot 启动时自动创建数据库表。Docker 部署通过 Dockerfile 把应用打包成镜像方便部署到云平台。云平台部署Vercel 部署前端Railway 部署后端两者通过 HTTP 通信。安全的表达式解析禁止eval改用专门的数学表达式库。8.3 未来可改进的方向支持更多数学函数如三角函数、对数、幂运算。历史记录分页和搜索数据量大时前端分页显示。用户账号系统不同用户拥有独立的历史记录。界面美化使用 UI 框架如 Element Plus提升视觉效果。键盘快捷键支持支持直接通过键盘输入表达式。九、注意事项本项目的前端部署在 Vercel后端部署在 Railway两者均为境外云服务。在中国大陆的网络环境下部分运营商或网络环境可能无法直接访问 Vercel 和 Railway 的域名导致页面打不开或计算请求失败。如果需要稳定访问建议在能够正常访问境外服务的网络环境下打开本项目或使用相应的网络代理工具。在能够访问境外服务的网络环境下本项目的前后端功能均能正常工作前端地址https://832401118-calculator-frontend.vercel.app后端地址https://832401118calculatorbackend-production.up.railway.app如果助教在评测时遇到网络问题可以尝试使用网络代理工具后重试。