832402209黄浩罡— First Assignment — Front-End and Back-End Separation Calculator System

hhg832402209 hhg832402209 墨衍会员 · AI 创作全网分发 墨衍智能分发 通过墨衍一键同步至各平台 可同步平台 CSDN微信公众号微博知乎掘金百家号博客园抖音小红书 你的文章也可以这样分发 墨衍会员 ¥399起/年,写一次发全网 我也要 + 加入社区

Web Calculator System – Development Report

Content of Table

  1. Project Introduction
  2. Project Links
  3. PSP Table
  4. System Design
  5. Main Functions
  6. Key Code Implementation
  7. Testing
  8. GitHub and Version Control
  9. Deployment
  10. Problems and Solutions
  11. Project Results
  12. Personal Reflection
  13. Future Improvements

Course Information

ItemInformation
NameHaogang Huang
FZU ID832402209
MUID24127353
CourseEE308FZ — Software Engineering
ProjectWeb Calculator
Learning and Development ContentSoftware engineering principles, requirements analysis, PSP planning, frontend development, backend development, REST API design, database management, Git/GitHub version control, software testing, debugging, frontend-backend integration, and cloud deployment
Technologies UsedHTML, CSS, JavaScript, Node.js, SQLite / sql.js
Development ToolsVisual Studio Code, Git, GitHub
Deployment PlatformRender
Assignment Requirements1. Develop a web calculator with basic arithmetic, compound expressions and calculation history.
2. Use a database to store history.
3. Deploy the project online.
4. Submit a complete blog report. Assignment Requirements
Objectives of This Assignment1. Practice full-stack development (HTML/CSS/JavaScript frontend + Node.js backend).
2. Learn REST API design and SQLite database management.
3. Practice Git/GitHub version control.
4. Learn cloud deployment with Render.
Other References1. EE308FZ course materials.
2. Assignment Requirements

1. Project Introduction

This project is a web-based calculator developed using a frontend-backend separated architecture.
The frontend is implemented with HTML, CSS, and JavaScript. The backend is implemented with Node.js, and SQLite is used to store calculation history.
The main functions include:
• Basic arithmetic operations
• Compound expressions
• Parentheses and operator precedence
• Decimal calculations
• Unary operations
• Percentage calculations
• Calculation history
• Deleting and clearing history
• Error handling
The final system was deployed online using Render.


The following links provide access to the source code, coding standards, and deployed services of the project.

ItemLink
Frontend GitHub RepositoryCalculator-frontend-code
Backend GitHub RepositoryCalculator-backend-code
Online CalculatorOpen Online Calculator
Backend ServiceOpen Backend Service
Frontend CodeStyleFrontend codestyle.md
Backend CodeStyleBackend codestyle.md

3. PSP Table

PSP (Personal Software Process) was used to estimate and record the time spent on each stage of the project. The estimated total development time was 440 minutes, while the actual development time was 550 minutes.

Development StageEstimated Time (min)Actual Time (min)
Requirement Analysis1020
Project Planning1020
Interface Design6030
Frontend Development3050
Calculator Logic Development4060
Backend Development6080
Database Development3050
History API Development2030
Frontend-Backend Integration4050
Testing and Debugging3040
Git and GitHub Management3030
Deployment2030
Documentation and Blog Writing6060
Total440550

4. System Design

The project uses a frontend-backend separated architecture.

img

Figure: System architecture diagram (frontend-backend separated architecture)
The frontend is responsible for the user interface and user interaction.
The backend is responsible for calculation processing, API services, and database operations.
SQLite stores the calculation history.


5. Main Functions

The main functions of the calculator are:

img

Figure: Functional structure diagram of the calculator
The history module supports:
• Viewing calculation history
• Deleting one record
• Clearing all records


6. Key Code Implementation

6.1 Frontend API Request

The frontend communicates with the backend through HTTP requests.

img

The frontend communicates with the deployed backend through an HTTP POST request. The API base URL points to the online backend service deployed on Render.

const API_BASE = "https://calculator-backend-code-1.onrender.com";

async function callApi(path, body) {
    const res = await fetch(API_BASE + path, {
        method: "POST",
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify(body)
    });

    return res.json();
}

This function provides a unified way for the frontend to call backend APIs.


6.2 Backend API

img

The backend provides APIs for calculation and history management.
Main endpoints include:
| Method | Endpoint | Function |
|---|---|---|
| POST | /api/calculate | Calculate expression |
| POST | /api/unary | Unary operation |
| POST | /api/percent | Percentage calculation |
| GET | /api/history | Get history |
| DELETE | /api/history/{id} | Delete one record |
| DELETE | /api/history | Clear history |


6.2.1 Backend Calculation API (server.js)

The /api/calculate endpoint reads the JSON body, trims the expression, and delegates the calculation to calculateExpression() in the parser module. On success it returns a unified { success, expression, result } payload and automatically saves the record into SQLite; on a parsing error it responds with HTTP 400 and a user-readable message.

// POST /api/calculate - core: parse the expression, calculate, and save it to history
if (req.method === 'POST' && url === '/api/calculate') {
  const body = await readBody(req);
  const expression = String(body.expression || '').trim();

  if (!expression) {
 return sendJson(res, 400, {
   success: false,
   message: 'Invalid expression: empty input'
 });
  }

  const result = calculateExpression(expression);
  return okResult(res, expression, result, expression);
}

/** Success response for calculation APIs; automatically saves the record to the database */
async function okResult(res, expression, result, displayExpression) {
  const payload = {
 success: true,
 expression: displayExpression || expression,
 result: String(result)
  };

  try {
 await db.insertHistory(payload.expression, payload.result);
  } catch (e) {
 // A history write failure does not affect the calculation result
  }

  sendJson(res, 200, payload);
}

function sendCalcError(res, e) {
  if (e instanceof ExpressionError) {
 sendJson(res, 400, {
   success: false,
   message: e.message
 });
  } else {
 sendJson(res, 500, {
   success: false,
   message: 'Internal server error'
 });
  }
}

All responses share one JSON shape, so the frontend only needs a single handler. The backend never evaluates user input: calculation runs in a pure parser, and history is written only after the result is ready.

6.2.2 Expression Parsing (Recursive Descent)

The parser is the core of the backend. The expression is first normalized (× and ÷ become * and /), then tokenize() splits it into numbers and operators, and finally a recursive-descent parser builds and evaluates the syntax tree.

/** Normalize characters: × * -> multiply, ÷ / -> divide (spaces are skipped by the tokenizer) */
function normalize(expr) {
  let out = '';
  for (const ch of String(expr)) {
    if (ch === '×' || ch === '*') out += '*';
    else if (ch === '÷' || ch === '/') out += '/';
    else out += ch;
  }
  return out;
}

/**
 * Grammar:
 *   expression := term { ('+' | '-') term }
 *   term       := factor { ('*' | '/') factor }
 *   factor     := ('+' | '-') factor | primary
 *   primary    := NUMBER | '(' expression ')'
 */

function parseAddSub(tokens, pos) {
  let { node, pos: next } = parseMulDiv(tokens, pos);
  while (tokens[next].type === TOKEN.PLUS || tokens[next].type === TOKEN.MINUS) {
    const op = tokens[next].type === TOKEN.PLUS ? '+' : '-';
    next++;
    const rhs = parseMulDiv(tokens, next);
    node = { op, left: node, right: rhs.node };
    next = rhs.pos;
  }
  return { node, pos: next };
}

function parseMulDiv(tokens, pos) {
  let { node, pos: next } = parseUnary(tokens, pos);
  while (tokens[next].type === TOKEN.MUL || tokens[next].type === TOKEN.DIV) {
    const op = tokens[next].type === TOKEN.MUL ? '*' : '/';
    next++;
    const rhs = parseUnary(tokens, next);
    node = { op, left: node, right: rhs.node };
    next = rhs.pos;
  }
  return { node, pos: next };
}

function parseUnary(tokens, pos) {
  const t = tokens[pos];
  if (t.type === TOKEN.PLUS || t.type === TOKEN.MINUS) {
    const op = t.type === TOKEN.PLUS ? '+' : '-';
    const inner = parseUnary(tokens, pos + 1);
    return { node: { op, unary: true, child: inner.node }, pos: inner.pos };
  }
  return parsePrimary(tokens, pos);
}

/** Evaluate the syntax tree */
function evaluate(node) {
  if (node.value !== undefined) return node.value;

  if (node.unary) {
    const v = evaluate(node.child);
    return node.op === '-' ? -v : v;
  }

  const l = evaluate(node.left);
  const r = evaluate(node.right);

  switch (node.op) {
    case '+': return l + r;
    case '-': return l - r;
    case '*': return l * r;
    case '/':
      if (r === 0) throw new ExpressionError('Division by zero');
      return l / r;
    default:
      throw new ExpressionError('Invalid expression: unsupported operator');
  }
}

Each grammar level maps to one precedence tier: parseAddSub calls parseMulDiv, which calls parseUnary, so multiplication/division bind tighter than addition/subtraction, parentheses group a sub-expression, and a minus in front of a number is a unary sign. Division by zero and malformed input throw ExpressionError, which the API maps to a 400 response. Because the evaluator only walks the parsed tree, eval/exec are never used, which makes code injection impossible.

6.3 Database

SQLite is used to store calculation history.
Each record contains:
• ID
• Expression
• Result
• Creation time
Example:
{
"id": 49,
"expression": "(1+2)*3",
"result": "9",
"created_at": "2026-10-07 13:13:34"
}


6.3.1 Database Operations (database.js)

The project uses sql.js — the SQLite engine compiled to WebAssembly — so no database server needs to be installed, and the database file data/calculator.db persists across restarts.

db.run(
  'CREATE TABLE IF NOT EXISTS calculation_history (' +
    'id INTEGER PRIMARY KEY AUTOINCREMENT, ' +
    'expression TEXT NOT NULL, ' +
    'result TEXT NOT NULL, ' +
    'created_at TEXT NOT NULL' +
  ')'
);

/** Current time, format: year-month-day hour:minute:second */
function nowStr() {
  const d = new Date();
  const p = (n) => String(n).padStart(2, '0');
  return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
}

/** Insert a history record, returns { id, expression, result, created_at } */
async function insertHistory(expression, result) {
  await init();
  const time = nowStr();

  db.run(
    'INSERT INTO calculation_history (expression, result, created_at) VALUES (?, ?, ?)',
    [String(expression).slice(0, 300), String(result).slice(0, 100), time]
  );

  const rows = db.exec('SELECT last_insert_rowid() AS id');
  const id = rows[0].values[0][0];

  persist();

  return {
    id,
    expression: String(expression).slice(0, 300),
    result: String(result).slice(0, 100),
    created_at: time
  };
}

/** Get all history (ordered by time descending, newest first) */
async function getAllHistory() {
  await init();

  const rows = db.exec(
    'SELECT id, expression, result, created_at FROM calculation_history ORDER BY id DESC'
  );

  if (rows.length === 0) return [];

  const cols = rows[0].columns;

  return rows[0].values.map((v) => {
    const obj = {};
    cols.forEach((c, i) => {
      obj[c] = v[i];
    });
    return obj;
  });
}

/** Delete a history record, returns whether deletion succeeded */
async function deleteHistory(id) {
  await init();

  const n = Number(id);
  if (!Number.isInteger(n) || n <= 0) return false;

  db.run(
    'DELETE FROM calculation_history WHERE id = ?',
    [n]
  );

  const changes = db.getRowsModified();

  persist();

  return changes > 0;
}

All parameters are bound with ? placeholders, so SQL injection is impossible. Every record stores the full timestamp (year-month-day hour:minute:second) required by the assignment, and getAllHistory() orders by id DESC so the newest calculation appears first.

7. Testing

img

Several functions were tested during development.

Figure 1: 1+2

img

Figure 2: 1+2*3

img

Figure 3: (1+2)*3

img

Figure 4: (1*3)/4

img

Figure 5: Unary operation

img

Figure 6: Percentage

img

Figure 7: Calculation history

img

Figure 8: Delete history

img

Figure 9: Clear history

img

Figure 10: Division by zero

img

Figure 11: √(9)

img

Figure 12: 1.1+3

img


8. GitHub and Version Control

Git was used for version control during development.
The frontend and backend were stored in separate GitHub repositories.
The development process was:
Modify Code
↓
git add
↓
git commit
↓
git push
↓
GitHub
This allowed the source code to be managed and updated systematically.
Frontend GitHub Repository

img

Backend GitHub Repository

img


9. Deployment

The application was deployed using Render.
The backend uses the following configuration:
Runtime: Node
Branch: main
Build Command: npm install
Start Command: npm start
The frontend was deployed as a Static Site.
After deployment, the frontend communicates with the public backend API instead of the local localhost address.
Local Development:

Frontend → localhost:3000

After Deployment:

Frontend → Public Backend API

img

Backend Render Settings

img

Frontend Render Settings

img


10. Problems and Solutions

Problem 1: Frontend Could Not Connect to Backend
During local development, the frontend used the local backend address.
After deployment, localhost could no longer be used to access the public backend.
Solution
I changed the frontend API configuration to use the deployed backend URL.


Problem 2: Backend Root Page Error
After deploying the backend separately, opening the backend root page returned an error because the backend was no longer running together with the frontend files.
However, the API endpoints worked correctly.
Solution
The frontend and backend were treated as two independent services, and the frontend communicated with the backend through its API endpoints.


Problem 3: Slow First Request
The deployed application sometimes had a delay when making the first request.
This was related to the behavior of the free cloud hosting environment, where inactive services may need to start again.


11. Project Results

The final calculator can successfully perform calculations and manage calculation history.
The main workflow is:

flowchart TD
    A[User Input] --> B[Frontend]
    B --> C[Backend API]
    C --> D[Calculation]
    D --> E[SQLite]
    E --> F[Result]
    F --> G[Frontend Display]

The application was successfully deployed and tested online.


12. Personal Reflection

This project helped me understand the complete development process of a web application.
I learned how to build a frontend interface, develop backend APIs, use SQLite for data storage, and connect the frontend and backend through HTTP requests.
I also learned that local development and online deployment can involve different problems. For example, the frontend cannot use localhost to access a backend deployed on a public server.
Using Git and GitHub also helped me understand the importance of version control.
Overall, this project improved my practical programming and debugging skills and gave me a better understanding of frontend-backend architecture and cloud deployment.


13. Future Improvements

In the future, the project could be improved by adding:
• More mathematical functions
• A better mobile interface
• Dark mode
• User accounts
• Individual user calculation history
• A more powerful database
• Better deployment performance

...全文

相关推荐

从注意力到行动 pdf Agent

从注意力到行动 pdf

言之。

同步电机与构网型变流器的频率稳定特性及多时间尺度交互机理研究(Simulink仿真实现)

内容概要:本文系统解读了一套完整的多机器人路径规划(MAPF)MATLAB代码库,重点围绕优先级规划算法展开,涵盖从地图解析、拓扑构建、路径规划、冲突消解到可视化呈现的全流程。文章提出四层架构设计:几何层负责地图解析与栅格化;拓扑层构建四邻域图与Petri网模型;调度层实现基于动态边权膨胀的优先级路径规划与时间同步冲突消解;呈现层完成动画与视频输出。核心创新包括动态边权膨胀机制避免硬封锁导致的虚假不可解、节点预留历史实现高效冲突查询、几何惩罚驱动的局部同步提升效率,以及Petri网双阶段优化支持解的存在性与最优性分离判断。框架兼顾实用性与可扩展性,适用于大规模智能体场景。; 适合人群:具备MATLAB编程基础,从事机器人、自动化、智能交通或路径规划相关研究的科研人员与研究生。; 使用场景及目标:① 学习并复现多机器人无冲突路径规划的经典与先进算法;② 掌握动态边权膨胀、时间同步、冲突消解等关键技术的工程实现;③ 借助模块化框架进行算法改进与性能优化,支撑仓储物流、无人机编队等应用场景的仿真研究。; 阅读建议:建议按文中所述的数据流顺序逐步阅读,重点关注各模块接口设计与算法取舍逻辑,结合提供的完整资源进行代码调试与实验验证,深入理解“空间规划-时间消解”分离的设计思想。

程序猿鑫

huashu-motion 让编码 Agent 直出商业宣传片-一个专门给 Claude Code、Codex 等工具使用的 Skill

huashu-motion 一个专门给 Claude Code、Codex 等工具使用的 Skill。它不跑黑盒视频生成模型,而是让 Agent 直接用代码绘制动画,成片就是一个网页。 项目最大的特色是“先写谱再拍片”,Agent 能写纯文本乐谱配合乐器采样在本地配乐,并精准对齐每帧音效。在浏览器里按 C 键点击画面就能直接留批注,Agent 会定位源码只改动那一处,也可以在右侧表单手动调参。 它还内置了从顶级发布片提炼的 20 种运动语言、自动化质检与并行渲染机制,小模型同样能做出高级感。

一叶知秋yyds

YOLO26算法校园作业场景锚目标检测+训练好的模型+103张数据集+pyqt可视化界面.zip

附训练好的 YOLO 权重与 PyQt 可视化检测界面,页面底部含可视化效果预览,毕设/课设开箱即用。 【数据集概况】 · 检测类别(中文):[锚(anchor)] · 训练集:90 张 · 验证集:9 张 · 测试集:4 张 · 总计:103 张 该数据集聚焦校园作业场景,通过精准标注手写文字中的锚元素,为教育场景下的文本识别与作业分析提供高质量数据支撑,助力教学评估与学生书写质量研究。该数据集训练集占比90%,验证集与测试集分别占9%和4%,符合目标检测任务数据划分的常规比例,训练集规模充足以保障模型学习能力,验证集与测试集合理分配用于模型性能评估,确保数据分布科学合理。该数据集标注工作严格遵循规范,所有锚元素的边界框标注精准,标注内容与... 【训练曲线与评估图】 【模型训练配置】 参数 | 值 模型 | yolo26n 训练轮数 | 100 epochs 输入尺寸 | 640x640 批次大小 | 24 优化器 | auto 初始学习率 | 0.01 训练设备 【关键指标汇总】 训练了 100 个 epoch,最终轮指标: 指标 | 数值 mAP50 | **0.6351** mAP50-95 | 0.1958 Precision | 0.6436 Recall | 0.5714 train/box_loss | 1.6649 train/cls_loss | 1.3181 val/box_loss | 2.3156 val/cls_loss | 2.5145 【训练过程分析】 100 轮训练后 mAP50 仅 0.6351,模型收敛不充分。Loss 曲线前期下降后进入平台期,mAP 提升缓慢。mAP50-95 为 0.1958,定位精度同样偏低。 【模型性能评估】 Precision 0.643... 使用场景:毕业设计、课程设计、深度学习入门、工业场景落地验证。

guo_xiao_xiao_

基于扩展卡尔曼滤波(EKF)的激光雷达与雷达异构传感器融合算法研究(Matlab代码实现)

内容概要:本文系统解读了一套完整的多机器人路径规划(MAPF)MATLAB代码库,聚焦于优先级规划算法的工程实现。该框架将求解过程划分为几何、拓扑、调度与呈现四个解耦层次,实现了从地图解析、四邻域拓扑与Petri网建模,到基于优先级启发式的路径规划、时间同步冲突消解,再到动画可视化的全流程闭环。其核心创新在于“空间规划-时间消解”分离的设计思想:通过动态边权膨胀策略取代传统硬封锁,允许后续智能体在必要时穿越先前路径,提升狭窄环境下的规划成功率;利用节点预留历史机制实现高效的冲突查询,并结合几何惩罚机制在规划阶段预测潜在冲突,显著提高时间同步效率。框架还支持Petri网整数规划求解路径,增强了算法的完备性与优化潜力,整体具备良好的可复现性、可扩展性与教学价值。; 适合人群:具备一定编程基础和机器人路径规划基础知识,从事相关科研或工程应用的研发人员,尤其是高校研究生及自动化、人工智能方向的技术人员。; 使用场景及目标:①应用于仓储物流、无人机编队、自动驾驶等多智能体协同场景中的路径规划问题;②为MAPF算法的教学、实验与性能评估提供可复现的MATLAB实现参考;③支持对优先级规划、冲突消解机制、Petri网建模等关键技术进行二次开发与优化验证。; 阅读建议:建议读者按照文中所述的数据流顺序逐步学习,重点关注各模块之间的接口设计与信息传递机制,结合提供的可视化工具调试与观察算法行为,深入理解“空间规划-时间消解”分离的设计思想,并可在实际应用中尝试调整惩罚权重、同步策略等参数以优化性能。

李锦科研
评论
收藏 已收藏
分享
返回顶部