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

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

Web Calculator System – Development Report

Catalogue:

1. Project Introduction

2. Project Links

3. PSP Table

4. System Design

5. Main Functions

6. Key Code Implementation

 6.1 Frontend API Request

 6.2 Backend API

   6.2.1 Backend Calculation API (server.js)

   6.2.2 Expression Parsing (Recursive Descent)

 6.3 Database 

   6.3.1 Database Operations (database.js)

7. Testing

8. GitHub and Version Control

9. Deployment

10. Problems and Solutions

11. Project Results

12. Personal Reflection

13. Future Improvements

 

Course Information:


Name    Haogang Huang
FZU ID    832402209
MUID    24127353
Course    EE308FZ — Software Engineering
Project    Web Calculator
Learning and Development Content    Software 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 Used    HTML , CSS, JavaScript, Node.js, SQLite / sql.js
Development Tools    Visual Studio Code, Git, GitHub
Deployment Platform    Render


Assignment Requirements    1) 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: https://bbs.csdn.net/topics/620530837)


Objectives of This Assignment    1) 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 References    1) EE308FZ course materials; 2) Assignment requirements: https://bbs.csdn.net/topics/620530837

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.
________________________________________


2. Project Links
Frontend GitHub Repository
https://github.com/140039hhh/Calculator-frontend-code


Backend GitHub Repository
https://github.com/140039hhh/Calculator-backend-code


Online Calculator
https://calculator-frontend-code1.onrender.com


Backend Service
https://calculator-backend-code-1.onrender.com


Frontend CodeStyle
https://github.com/140039hhh/Calculator-frontend-code/blob/main/codestyle.md


Backend CodeStyle
https://github.com/140039hhh/Calculator-backend-code/blob/main/codestyle.md


________________________________________


3. PSP Table
Development Stage                       Estimated Time (min)    Actual Time (min)
Requirement Analysis                                  10                           20
Project Planning                                           10                           20
Interface Design                                           60                           30
Frontend Development                                 30                           50
Calculator Logic Development                      40                           60
Backend Development                                  60                           80
Database Development                                 30                          50
History API Development                               20                          30
Frontend-Backend Integration                       40                           50
Testing and Debugging                                 30                           40
Git and GitHub Management                        30                            30
Deployment                                                   20                           30
Documentation and Blog Writing                   60                           60
Total                                                              440                         550
________________________________________


4. System Design


The project uses a frontend-backend separated architecture.
 
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:
 
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.

 

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

 

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


Several functions were tested during development.

 

Figure 1: 1+2


 
Figure 2: 1+2*3

 

 


 
Figure 3: (1+2)*3


 
Figure 4: (1*3)/4

 

 


 
Figure 5: Unary operation
 
Figure 6: Percentage

 

 


 
Figure 7: Calculation history
 
Figure 8: Delete history
 
Figure 9: Clear history
 
Figure 10: Division by zero
 
Figure 11: √(9)
 
Figure 12: 1.1+3
 
________________________________________


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
 
Backend GitHub Repository
 
________________________________________

 

 


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


Backend Render Settings
 
Frontend Render Settings
 
________________________________________

 

 

 


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:
User Input
    ↓
Frontend
    ↓
Backend API
    ↓
Calculation
    ↓
SQLite
    ↓
Result
    ↓
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
 

...全文

相关推荐

基于MATLAB的复合弹性模量分析,采用混合法则,结合纤维体积分数变化和图形结果。.zip

1.版本:matlab2014a/2019b/2024b 2.附赠案例数据可直接运行。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

海神之光

基于MATLAB的自适应纯追踪曲线跟踪机器人仿真,采用多路径、自适应速度控制和图形界面可视化。.zip

1.版本:matlab2014a/2019b/2024b 2.附赠案例数据可直接运行。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

海神之光

【数据库优化】通信行业B/O域查询性能提升:基于分区裁剪与覆盖索引的秒级溯源系统实现

内容概要:本文围绕通信行业中海量话单与信令数据的查询性能问题,系统阐述了从全表扫描到秒级响应的数据库优化路径。文章以运营商 cdr_detail 表为案例,揭示了因使用函数操作索引列(如DATE(call_start_time))和深分页(LIMIT offset, size)导致的全表扫描问题,并提出完整的优化方案:通过时间范围RANGE分区实现分区裁剪、构建msisdn与时间的联合覆盖索引、消除索引列函数运算、采用游标分页替代OFFSET等方式,使查询性能从P95 8.3秒降至290毫秒。同时介绍了执行计划分析(EXPLAIN)在调优中的关键作用,并展望了HTAP架构、向量化执行与AI自治优化器等未来方向。; 适合人群:具备一定数据库基础知识,从事通信行业数据开发、DBA或后端研发的技术人员,尤其是面对大规模时序数据查询性能瓶颈的工程师;; 使用场景及目标:①解决营业厅用户详单查询、网间结算对账、VoNR信令分析等高并发、低延迟查询场景下的性能问题;②掌握如何通过分区策略、索引设计和SQL改写实现从全表扫描到精准索引访问的优化落地; 阅读建议:建议结合实际生产环境中的大表查询案例,对照本文的反例与正例进行执行计划对比分析,并在测试环境中实践分区表创建、覆盖索引构建及游标分页改写,深入理解每项优化技巧的实际效果。

智能化咨询

石墨提纯全球市场结构与产业发展趋势分析报告(QYResearch最新研究).docx

石墨提纯全球市场结构与产业发展趋势分析报告(QYResearch最新研究)

行业报告QY

政府科技管理者如何提升科技成果转化政策的精准匹配与执行效率?.docx

政府科技管理者如何提升科技成果转化政策的精准匹配与执行效率?

BIZKEEN
评论
收藏 已收藏
分享
返回顶部