目录
- 仓库链接
- 0.PSP表格
- 1. 成品展示
- 1.基础运算
- 2. 清零回退
- 3.错误提示
- 4.历史记录
- 拓展功能
- 1.前端可修改的利率计算器
- 2.科学计算器
- 3. 按钮切换不同计算器模式
- 4.用户在一次运算后不清零继续输入操作符,替换表达式为上次答案
- 2.设计实现过程
- 3.代码说明
- 4.心路历程和收获
仓库链接
2301-计算机学院-软件工程 | https://bbs.csdn.net/forums/ssynkqtd-05 |
---|---|
这个作业要求在哪里 | https://bbs.csdn.net/topics/617377308 |
这个作业的目标 | 实现一个前后端分离计算器 |
其他参考文献 | … |
backend
frontend
Google 开源项目风格指南——中文版
Google Style Guides
0.PSP表格
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 40 | 20 |
• Estimate | 估计这个任务需要多少时间 | 10 | 10 |
Development | 开发 | 650 | 630 |
• Analysis | 需求分析 (包括学习新技术) | 40 | 30 |
• Design Spec | 生成设计文档 | 80 | 75 |
• Design Review | 设计复审 | 35 | 45 |
• Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 25 | 20 |
• Design | 具体设计 | 35 | 30 |
• Coding | 具体编码 | 325 | 320 |
• Code Review | 代码复审 | 65 | 60 |
• Test | 测试(自我测试,修改代码,提交修改) | 60 | 50 |
Reporting | 报告 | 95 | 90 |
• Test Report | 测试报告 | 25 | 20 |
• Size Measurement | 计算工作量 | 10 | 15 |
• Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 45 | 55 |
合计 | 670 | 630 |
1. 成品展示
1.基础运算
2. 清零回退
3.错误提示
4.历史记录
拓展功能
1.前端可修改的利率计算器
2.科学计算器
3. 按钮切换不同计算器模式
4.用户在一次运算后不清零继续输入操作符,替换表达式为上次答案
2.设计实现过程
- 前端:前端需要在上一轮作业的基础上加以完善。首先要扩充为两个输入框,一个用于显示用户输入的表达式,一个用于显示答案。同时在上一轮的form基础上再做一个利率计算器(利率计算器和存款计算器可以共用一个div)。同时使用fetch向后端传发数据。
- 后端:后端使用Gin做Web框架。通过路由组和hander接前端请求。Gin相比于Hertz是一个轻量级的框架适合做这种小项目。数据库交互用Gorm。
- 数据库:只用到了Mysql。存历史记录,存款、贷款三张表。暂且把这个计算器看作单机版本,所以在存历史记录的时候没有特意区分不同用户ID。可视化工具推荐Dbeaver。
3.代码说明
- 前端采用fetch通信。fetch的语法很简明,不再赘述。以查询历史记录为例:
function ans() {
fetch("http://localhost:8080/history/", {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
})
.then(response => response.json())
.then(data => {
// 处理后端的响应
if (data.code === 200) {
const expVal = data.result;
if (expVal.length > 0) {
const displayText = expVal.map((item, index) => `
<div class="expression-container" id="expression-${index}">
$ ${item.Exp} = ${item.Val} $
</div>
`).join('<br>');
Swal.fire({
title: '历史记录(近 10 条)',
html: displayText,
icon: 'success',
confirmButtonText: '关闭'
});
// 使用MathJax渲染数学表达式
for (let i = 0; i < expVal.length; i++) {
MathJax.Hub.Queue(["Typeset", MathJax.Hub, `expression-${i}`]);
}
} else {
// 如果没有之前的数据,显示提示消息
Swal.fire('没有可显示的数据', '', 'error');
}
} else {
// code 不为 200,给出提示
console.error(`响应状态不是 200,错误消息:${data.msg}`);
}
})
.catch(error => {
console.error("An error occurred while receiving the result from the backend: " + error);
});
}
页面的切换用一个变量来控制,相当于一个mod 3 的加法,控制div的可见性。
function mode() {
st = (st + 1) % 3;
const normalCalculator = document.querySelector('.normal');
const interestCalculator = document.querySelector('.interest');
let bt = document.getElementById("topMode");
var updateInter = document.getElementById("update_inter");
var updateInter2 = document.getElementById("update_inter2");
if (st === 0) {
normalCalculator.style.display = 'block';
interestCalculator.style.display = 'none';
bt.innerHTML = "科学计算器"
} else if (st === 1) {
normalCalculator.style.display = 'none';
interestCalculator.style.display = 'block';
updateInter.style.display = "block";
updateInter2.style.display = "none";
bt.innerHTML = "存款计算器"
} else if (st === 2) {
normalCalculator.style.display = 'none';
interestCalculator.style.display = 'block';
updateInter2.style.display = "block";
updateInter.style.display = "none";
bt.innerHTML = "贷款计算器"
}
}
- 后端启动服务:
package main
import (
"WebCalculator/dal/mysql"
"WebCalculator/router"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
func main() {
mysql.Init()
r := gin.Default()
// 解决跨域问题
r.Use(cors.Default())
router.SetUpRoutes(r)
err := r.Run(":8080")
if err != nil {
panic(err)
}
}
注意需要解决跨域问题。查阅资料得知最近的Gin官方已经给出了对于跨域的问题的默认配置无需再手动配置,一行代码就可以了。同时在main函数中初始化mysql相关链接信息和路由组。
func SetUpRoutes(engine *gin.Engine) {
home := engine.Group("/")
{
home.GET("/hello", hello.Hello)
}
his := engine.Group("/history")
{
his.POST("/", history.AddHistory)
his.GET("/", history.QueryHistory)
}
dep := engine.Group("/deposit")
{
dep.POST("/", deposit.UpdateDep)
dep.GET("/", deposit.QueryInterest)
}
l := engine.Group("/loans")
{
l.GET("/", loans.QueryInterest)
l.POST("/", loans.UpdateLoans)
}
}
hander包编写具体实现方法。
以查询利息为例:
func QueryInterest(c *gin.Context) {
// 获取查询参数
principalStr := c.DefaultQuery("principal", "")
durationStr := c.DefaultQuery("duration", "")
// 将字符串转换为浮点数
principal, err1 := strconv.ParseFloat(principalStr, 64)
duration, err2 := strconv.ParseFloat(durationStr, 64)
if err1 != nil || err2 != nil {
// 处理转换错误,例如返回错误响应
c.JSON(http.StatusOK, gin.H{
"code": 400,
"msg": "输入无效",
})
return
}
interest := service.CalInterest(entity.MoneyCal{
Money: principal,
Duration: duration,
}, 1)
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": "计算成功",
"result": interest,
})
}
注意存款和贷款的逻辑几乎相同。所以可以增加一个传入参数op,0/1表示不同类型就可以复用计算函数。
func ExistDuration(duration float64, op int) bool {
if op == 1 {
result := mysql.DB.Where("duration = ?", duration).First(&entity.Deposit{})
return result.Error != gorm.ErrRecordNotFound
}
result := mysql.DB.Where("duration = ?", duration).First(&entity.Loan{})
return result.Error != gorm.ErrRecordNotFound
}
func CalInterest(cal entity.MoneyCal, op int) float64 {
duration := cal.Duration
money := cal.Money
if op == 1 {
var deposit entity.Deposit
mysql.DB.Where("duration <= ?", duration).Order("duration desc").Limit(1).First(&deposit)
return money * deposit.Rate / 100
}
var loans entity.Loan
mysql.DB.Where("duration <= ?", duration).Order("duration desc").Limit(1).First(&loans)
return money * loans. Rate / 100
}
3.数据库
Gorm的数据库操作十分便利。提供了AutoMigrate来自动迁移表结构。有结构体的情况下可以自动建对应表.无需编写sql。
DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
SkipDefaultTransaction: true,
PrepareStmt: true,
Logger: logger.Default.LogMode(logger.Info),
})
if err != nil {
panic(err)
}
err = DB.AutoMigrate(&entity.History{}, &entity.Deposit{}, &entity.Loan{})
if err != nil {
return
}
且gorm.Model包含许多实用信息,ID,CT,UPT等等。
4.心路历程和收获
后端部分其实没啥技术含量,主要是CRUD…
感觉最难的部分还是在前端,因为之前不会,所以几乎是一直在对着已有的HTML和CSS代码不断增删猜测效果/询问他人。感觉前端代码实在写的不堪入目,还好最后效果感觉还行。
有些疑惑为什么要一个人完成前后端,可能全栈是学院派的宿命。