目录
仓库链接
| 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代码不断增删猜测效果/询问他人。感觉前端代码实在写的不堪入目,还好最后效果感觉还行。
 有些疑惑为什么要一个人完成前后端,可能全栈是学院派的宿命。










