832401306陈子洋 Front-End and Back-End Separation Calculator System

832401306 陈子洋 2026-10-03 00:01:17

Front-End and Back-End Separation Calculator System

ItemCurrent Information
Student ID832401306(FZU) 24125245(MU)
Student NameChen Ziyang 陈子洋
CourseEE308FZ Software Engineering
AssignmentFirst Assignment-Front-End and Backend Separation Calculator System
Blog PlatformCSDN

Table of Contents

  1. Course Information
  2. Project Links
  3. PSP Table
  4. Assignment Description
  5. Requirement Analysis
  6. Overall Architecture
  7. Frontend Design
  8. Backend Design
  9. API Design
  10. Database Design
  11. Expression Parser Design
  12. Calculation History Design
  13. Exception Handling
  14. Frontend and Backend Communication
  15. Deployment
  16. Functional Structure Diagram
  17. Presentation of Finished Product
  18. Code Explanation
  19. Personal Journey and Learnings

1. Course Information

FieldContent
Assignment RequirementsDevelop a separated frontend and backend calculator, then provide code, deployment, a blog and presention evidence
ObjectivesImplement basic and compound calculations, exceptions, exception handling, persistent SQLite history, deletion by ID and public deployment
Other ReferencesTeacher provided assignment blog and class announcements

2. Project Links
Frontend Repository: https://github.com/chenzy3034-ux/832401306_calculator_frontend
Backend Repository: https://github.com/chenzy3034-ux/832401306_calculator_backend
Frontend Code Standard: https://github.com/chenzy3034ux/832401306_calculator_frontend/blob/main/codestyle.md
Backend Code Standard: https://github.com/chenzy3034ux/832401306_calculator_backend/blob/main/codestyle.md

3. PSP Table
The table below lists the typical development activities planned for this project, along with the estimated time for each activity.

ActivityEstimated minActual min
Requirement analysis9075
System design120125
Frontend development240255
Backend development240260
Expression calculation module180200
Database design120120
Calculation history module120140
Frontend and backend integration120135
Testing180180
Deployment180180
Blog writing240215
Other work9090

4. Assignment Description
This assignment requires a calculator system with fully separated frontend and backend applications. The frontend handles expression input, button interaction, result and error display, and history presentation. The backend validates and parses expressions, performs calculations, handles exceptions, and accesses the database.
The project supports addition, subtraction, multiplication, division, decimals, parentheses, operator precedence, unary plus and minus, invalid expression handling, and division by zero handling. Successful calculations are stored in SQLite, and records can be queried and deleted. The frontend neither stores history in localStorage nor calculates final results independently.

5. Requirement Analysis
In this assignment, the calculator system should cover the following requirements:
· Basic calculation. Backend supports +,-,* and /.
· Compound expressions. Precedence, parentheses, decimals, unary signs.
· Invalid input. Restricted tokenizer and parser return controlled errors.
· Division by zero. Evaluator raises a dedicated domain error.
· Persistence history. SQLite file stores every successful calculation.
· History retrieval from the backend.
· Delete the specified records by ID.

6. Overall Architecture
The system is divided into the browser client, HTTP API, business services, and database layers. The browser handles interaction and presentation. FastAPI receives JSON requests and invokes calculation or history services. The SQLite repository provides persistence.

User
  ↓
React + Vite Frontend
  ↓ HTTPS / JSON
FastAPI API Routes
  ↓
Service Layer
  ├── Expression Calculation Service
  │     ↓
  │   Tokenizer → Parser → Evaluator
  │
  └── Calculation History Service
        ↓
SQLite Database

Module boundaries keep routes, calculation logic, and database access separate. This supports focused testing and keeps parsing and SQL out of API route functions.

7. Frontend Design
The frontend uses React19, Vite8, Javascript and CSS. The calculator component provides the expression field, number button, operators parentheses, decimal point, clear button, and equal button. The HistoryList component presents stored records and handles refresh and delete actions.
· When the expression is empty, the field displays 0.
· Multiplication and division buttons display familiar symbols and are converted to * and / before sending a request.
· Pressing equals calls the backend; React does not calculate the final result.
· The page calls GET/api/history on load and DELETE/api/history/{id} for deletion.
· Failed requests display the safe message returned by the backend.
· When the backend is unavailable, users can still edit the input but cannnot obtain a new valid result.

8. Backend Design
The backend uses Python, FastAPI, Pydantic, Uvicorn and the standard sqlite 3 library. app/api contains HTTP routes, app/services coordinates use cases, app/calculator safely precesses expressions and app/database contains the connection, schema and repository layers.
At application startup, the lifespan handler initializes the database table. Global exception handlers convert request validation errors and unexpected exceptions into a consistent JSON response without exposing a Python stack trace. Allowed CORS origins are configured through an environment variable.

9. API Design

MethodEndpointPurpose
POSTapi/calculateValidate, calculate, save and return one record
GETapi/historyReturn stored calculations, newest first
DELETEapi/history/{id}Delete one record by database ID
GEThealthDeployment health check

Example calculation request:

{
  "expression": "(1+2)*3"
}

Successful responses place the payload in data. Failed responses place a stable error code and safe message in error.

{
  "success": false,
  "data": null,
  "error": {"code": "INVALID_EXPRESSION", "message": "The expression is invalid"}
}

10. Database Design
The project uses a file based SQLite database rather than browser storage or an in process list.

ColumnTypeConstraintMeaning
idINTEGERPRIMARY KEY AUTOINCREMENTHistory record identifier
expressionTEXTNOT NULLOriginal API expression
resultTEXTNOT NULLDecimal calculation result
created_atTEXTNOT NULLUTC ISO timestamp

11. Expression Parser Design
Expression processing follows three explicit stages: tokenizer, parser and evaluator. The input is never executed as Python code.

11.1 Tokenizer
The tokenizer accepts only digits, decimal points, whitespace, +, -, *, /, and parentheses. It constructs numbers with Decimal. Multiple decimal points, an isolated decimal point, or an unknown character raises Invalid expression error.

11.2 Recursive Descent Parser
The parser converts tokens into NumberNode, UnaryNode, and BinaryNode objects. Its grammar is:

expression -> term ((+ | -) term)*
term       -> unary ((* | /) unary)*
unary      -> (+ | -) unary | primary
primary    -> NUMBER | (expression)

11.3 Precedence and Evaluation
The expression level handles addition and subtraction while the term level handles multiplication and division, giving multiplication and division higher precedence. The primary level recursively parses parentheses, and the unary level handles unary plus and minus. The evaluator traverses the AST recursively and checks the right operand before division.

12. Calculation History Design
A successful calculation follows this order: parse the expression, evaluate the result, insert the record into SQLite, and return the stored record. Insertion happens only after evaluation succeeds, so invalid expressions and division by zero do not create history records.
1.POST /api/calculate returns and stores a successful record.
2.GET /api/history returns records in descending created_at and id order.
3.The frontend reloads history on page load and after successful calculations or deletions.
4.DELETE /api/history/{id} removes the row with the specified database ID.
5.Deleting a missing ID returns 404 rather than reporting false success.

13. Exception Handling

SituationHTTPCodeFrontend Message
Invalid expression400INVALID_EXPRESSIONThe expression is invalid
Division by zero422DIVISION_BY_ZERODivision by is not allowed
Missing history ID404HISTORY_NOT_FOUNDThe calculation history record was not found
Invalid request body422INVALID_REQUESTThe request data is invalid
Unexpected backend error500INTERNAL_ERRORAn internal server error occurred

14. Frontend and Backend Communication
A successful calculation uses the following communication sequence:

User presses =
  -> React converts × and ÷ to * and /
  -> POST /api/calculate
  -> FastAPI validates the request
  -> Tokenizer -> Parser -> Evaluator
  -> SQLite INSERT
  -> JSON response
  -> React displays the result
  -> React reloads GET /api/history

In development, VITE_API_BASE_URL points to the local backend. The production build sets it to the PythonAnywhere HTTPS address. The backend uses CALCULATOR_CORS_ORIGINS to allow only configured frontend origins.

15. Deployment

15.1 Frontend
The frontend is deployed on GitHub Pages. When the main branch changes, GitHub Actions runs npm ci and npm run build, then publishes dist as the Pages artifact. The Vite base is /832401306_calculator_frontend/.
Public URL: https://chenzy3034-ux.github.io/832401306_calculator_frontend/

15.2 Backend
The backend is deployed on PythonAnywhere as a Uvicorn ASGI web application. Its start command uses environment variables to specify the persistent SQLite file and CORS origin. The platform provides a public HTTPS subdomain.
Backend URL: https://chenzy.pythonanywhere.com/
The public environment has been checked for health, CORS, calculation, history retrieval, deletion, and persistence after a backend reload. The production frontend build and GitHub Pages workflow succeeded, and all 41 backend tests passed.

16. Function Structure Diagram

Calculator System
|-- Frontend
|   |-- Expression input and keypad
|   |-- Result and error display
|   |-- History list and delete action
|   `-- Backend API client
|
|-- Backend
|   |-- Calculate API
|   |-- History APIs
|   |-- Calculation service
|   |-- History service
|   `-- Unified responses and exception handling
|
|-- Expression Processing
|   |-- Tokenizer
|   |-- Recursive descent parser
|   `-- Decimal evaluator
|
`-- Persistence
    |-- SQLite connection and initialization
    `-- Insert, query, and delete repository

This structure diagram matches the modules in the current repositories. It excludes unimplemented features such as scientific calculation, login, and bulk history deletion.

17. Presentation of Finished Product
17.1 Main calculator page

在这里插入图片描述


Figure1. Main calculator page
The initial expression shows 0, the result shows a dash, and the history section is visible.

17.2 Addition

在这里插入图片描述


Figure2. Addition: 1+2=3
The backend returns 3 and the successful record appears in history.

17.3 Subtraction

在这里插入图片描述


Figure3. Subtractiion: 10-3=7
The backend returns 7.

17.4 Multiplication

在这里插入图片描述


Figure4. Multiplication: 4x5=20
The interface shows the multiplication symbol while the API receives an asterisk.

17.5 Division

在这里插入图片描述


Figure5. Division: 10/2=5
The interface shows the division symbol while the API receives a slash.

17.6 Decimal

在这里插入图片描述


Figure6. Decimal: 3.5+1.2=4.7
This demonstrates decimal parsing and calculation.

17.7 Compound expression

在这里插入图片描述


Figure7. Compound expression: 1+2x3=7
This demonstrates multiplication taking precedence over addition.

17.8 Parentheses

在这里插入图片描述


Figure8. Parentheses: (1+2)x3=9
This demonstrates parentheses changing the evaluation order.

17.9 Unary minus

在这里插入图片描述


Figure9. Unary minus: -5+8=3
This demonstrates unary minus.

17.10 Invalid expression

在这里插入图片描述


Figure10. Invalid expression: 1+x2
The page shows "The expression is invalid" and does not add a history record.

17.11 Division by zero

在这里插入图片描述


Figure11. Division by zero: 10/0
The page shows "Division by zero is not allowed" and does not add a history record.

17.12 Calculation history

在这里插入图片描述


Figure12. Calculation history
The list shows each expression, result, time and Delete button.

17.13 History persistence

在这里插入图片描述


Figure13. History persistence
Database records remain after refreshing or reopening the page.

17.14 Delete history

在这里插入图片描述


Figure14. Delete history
The selected record -5+8=3 disappears, and the deleted record does not return after refreshing.

18. Code Explanation

18.1 Frontend API Request
The interface uses x and ÷ to make input more intuitive. After the user clicks the equals button, the frontend first converts these symbols into * and /, which are supported by the backend, and then sends the expression.

function toApiExpression(value) {
  return value.replaceAll('×', '*').replaceAll('÷', '/')
}

const record = await calculateExpression(toApiExpression(expression))
setResult(record.result)
await loadHistory()
}

calculateExpression sends JSON through an HTTP POST request. The frontend only displays the result returned by the backend and does not calculate the final result by itself.

export function calculateExpression(expression) {
  return request('/api/calculate', {
    method: 'POST',
    body: JSON.stringify({ expression }),
  })
}

18.2 Backend Calculate API
POST /api/calculate receives the expression, calls the service layer, and returns the saved history record after a successful calculation. A successful response uses HTTP 201.

history = calculate_and_store(request.expression, repository)
return CalculateSuccessResponse(data=history)

The service layer performs the evaluation before writing the result to the database. Therefore, a failed calculation does not create a history record.

def calculate_and_store(
    expression: str,
    repository: HistoryRepository,
) -> CalculationHistory:
    result = evaluate_expression(expression)
    return repository.insert(expression=expression, result=result)

18.3 Expression Parsing
The expression passes through the tokenizer, parser, and evaluator in sequence. The tokenizer converts the input into restricted mathematical tokens, the parser constructs an expression tree, and the evaluator calculates the value of that tree.

def evaluate_expression(expression: str) -> Decimal:
    return evaluate(Parser(tokenize(expression)).parse())

This process only handles the numbers and operators defined by the project. It does not execute the user input as Python code.

18.4 Operator Precedence
The parser uses four levels: expression, term, unary, and primary. expression combines only addition and subtraction, while term combines only multiplication and division. Therefore 1+23 becomes 1+(23). Parentheses start a nested expression parse and can override the default order.

18.5 Input Validation
The tokenizer rejects empty expressions and only recognizes numbers, decimal points, whitespace, the four arithmetic operators, and parentheses. Invalid numbers and unknown characters trigger InvalidExpressionError.

if not expression or expression.isspace():
    raise InvalidExpressionError("Expression cannot be empty")

The parser also checks whether the expression is complete. For example, if extra tokens remain after parsing, the expression is treated as invalid.

def parse(self) -> ExpressionNode:
    expression = self._parse_expression()
    if self._current.type is not TokenType.END:
        raise InvalidExpressionError("Unexpected token after expression")
    return expression

18.6 Error Handling
The calculation API converts invalid expressions and division by zero into clear HTTP status codes and consistent JSON error messages.

except InvalidExpressionError:
    return error_response(
        status.HTTP_400_BAD_REQUEST,
        "INVALID_EXPRESSION",
        "The expression is invalid",
    )
except DivisionByZeroExpressionError:
    return error_response(
        status.HTTP_422_UNPROCESSABLE_CONTENT,
        "DIVISION_BY_ZERO",
        "Division by zero is not allowed",
    )

In addition, the global exception handler returns a general message for unexpected backend errors, preventing Python exception stack traces from being exposed to the browser. The shared frontend request function checks both the HTTP status and the success field in the response before displaying the corresponding error message.

18.7 Database Insert
After each successful calculation, the repository uses parameter binding to insert the expression, result, and creation time. The result is stored as text to preserve the representation of the Decimal result.

INSERT INTO calculation_history (expression, result, created_at)
VALUES (?, ?, ?)

After the insertion, the code reads cursor.lastrowid and returns the new record’s id, expression, result, and created_at to the calculation API.

18.8 Database Query
GET /api/history reads records from SQLite. The query sorts the records by creation time and ID in descending order so that newer records appear first.

SELECT id, expression, result, created_at
FROM calculation_history
ORDER BY created_at DESC, id DESC

The frontend requests the history again when the page loads and after a successful calculation or deletion. Therefore, the history is still obtained from the database after the page is refreshed.

18.9 Delete a Specific History Record
The delete operation uses the database record ID and parameter binding to locate the specified row.

cursor = connection.execute(
    "DELETE FROM calculation_history WHERE id = ?",
    (history_id,),
)
return cursor.rowcount > 0

If rowcount is zero, the service layer raises HistoryNotFoundError, and the API returns HTTP 404. After a successful deletion, the frontend reloads the history list so that the interface remains synchronized with the database.

19. Personal Journey and Learnings
I began this assignment by reviewing the Blog on society and converting its requirements into a checklist. This helped me understand that the task included frontend and backend separation, testing, database persistence, documentation, and deployment.
The most important technical challenge was implementing expression calculation without using eval or exec. I created a tokenizer, recursive descent parser, and evaluator to support operator precedence, parentheses, unary operators, decimals, invalid expressions, and division by zero. This improved my understanding of safe input processing and expression parsing.
I also learned how to separate responsibilities between FastAPI routes, services, calculation modules, and the SQLite repository. Successful calculations are stored in SQLite, while failed calculations do not create history records. The frontend only handles interaction and displays results returned by the backend.
Finally, deploying the frontend to GitHub Pages and the backend to PythonAnywhere helped me understand CORS, environment variables, HTTPS communication, and persistent database storage. Through this project,although there are so many tricky issues, I gained practical experience in developing, testing, integrating, and deploying a complete frontend and backend application.

...全文
48 回复 打赏 收藏 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

173

社区成员

发帖
与我相关
我的任务
社区描述
梅努斯-软件工程
软件工程 高校 福建省·福州市
社区管理员
  • FZU_SE_LQF
  • zjcTander
  • 木村修
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧