First Assignment — Front-End and Back-End Separation Calculator System Java 25 + Spring Boot 4.1.1

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

Course Information

ItemInformation
Course for This AssignmentSoftware Engineering
Assignment RequirementsFirst Assignment — Front-End and Back-End Separation Calculator System
Objectives of This AssignmentDevelop a calculator system with a true front-end/back-end separation architecture; complete expression calculation on the backend; persist calculation history in a backend database; implement history querying and deletion; expose clear HTTP APIs; deploy and demonstrate the finished project.
Other ReferencesAssignment page; Google Java Style Guide; Google JavaScript Style Guide; Spring Boot documentation; SQLite documentation
Student ID832401128
Student NameHongfu Zhao

1. Assignment Description

The goal of this assignment is to build a calculator system based on a front-end and back-end separation architecture.

The frontend is responsible for user interaction and presentation, including expression input, calculator-button interaction, result display, history display, delete requests, and error-message display.

The backend is responsible for the actual business logic, including input validation, mathematical-expression parsing, arithmetic calculation, exception handling, calculation-history persistence, history queries, and history deletion.

The most important architectural rule is:

Frontend
   |
   | HTTP / JSON
   v
Backend
   |
   v
Database

The frontend must not calculate the final result by itself and then send the result to the backend. In this project, the frontend only sends the expression:

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

The Java backend parses and evaluates the expression, saves the successful calculation to SQLite, and returns the result.

This project does not use eval, exec, a script engine, or any equivalent arbitrary-code execution method. Instead, it implements a recursive-descent mathematical expression parser in Java.


2. Git Repository Link and Code Standards Link

Important: Replace all placeholders below before publishing the final blog.

2.1 Frontend Repository

Frontend GitHub Repository:
[832401128zhf/832401128_calculator_frontend (github.com)]

Frontend codestyle.md:
[832401128_calculator_frontend/codestyle.md at main · 832401128zhf/832401128_calculator_frontend (github.com)]

Frontend README.md:
[832401128_calculator_frontend/README.md at main · 832401128zhf/832401128_calculator_frontend (github.com)]

2.2 Backend Repository

Backend GitHub Repository:
[832401128zhf/832401128_calculator_backend (github.com)]

Backend codestyle.md:
[832401128_calculator_backend/codestyle.md at main · 832401128zhf/832401128_calculator_backend (github.com)]

Backend README.md:
[832401128_calculator_backend/README.md at main · 832401128zhf/832401128_calculator_backend (github.com)]

2.3 Code-Style Standards

The backend code standard is based mainly on the Google Java Style Guide, together with consistent IntelliJ IDEA formatting rules.

The backend conventions include:

  • class names use UpperCamelCase;

  • methods and variables use lowerCamelCase;

  • constants use UPPER_SNAKE_CASE;

  • four-space indentation is used instead of tabs;

  • Controller, Service, Repository, Parser, DTO, Model, and Exception classes have clearly separated responsibilities;

  • user input is validated;

  • expected errors return explicit messages.

The frontend code standard is based mainly on the Google JavaScript Style Guide and consistent HTML/CSS readability conventions.

The frontend conventions include:

  • JavaScript variables and functions use lowerCamelCase;

  • constants use UPPER_SNAKE_CASE;

  • four-space indentation is used;

  • the frontend is responsible only for UI, events, and API communication;

  • user-controlled text is escaped before being inserted into generated HTML;

  • the backend API address is centrally configured in API_BASE_URL.


3. PSP Table

The PSP table records both estimated development time and actual development time.

Before final submission, replace the TBD values with your real development records. The estimated values should also be adjusted if you already recorded your own estimates before implementation.

PSP StageEstimated Time (min)Actual Time (min)
Requirement analysis40TBD
System design50TBD
Frontend development120TBD
Backend project setup60TBD
Backend API development80TBD
Expression calculation module150TBD
Database design and persistence70TBD
Calculation-history module60TBD
Delete-history function40TBD
Front-end/back-end integration90TBD
Exception handling and validation60TBD
Testing and debugging90TBD
Deployment90TBD
README and code-standard documentation50TBD
Blog writing and screenshot preparation120TBD
Total1180TBD

4. Deployment and Access Information

The assignment requires the project to remain accessible during the evaluation period.

4.1Local Development Addresses

During local development:

Frontend:
IntelliJ IDEA built-in web server, normally using localhost:63342
or another static server such as localhost:5500

Backend:
http://localhost:8080

Backend API base:
http://localhost:8080/api

The actual frontend source code currently defines:

const API_BASE_URL = "http://localhost:8080/api";

Before public deployment, this value must be changed to the real public backend address.

The backend CORS configuration must also allow the public frontend origin.

4.2 Backend Startup

The backend project uses:

Java 25
Spring Boot 4.1.1
Maven
SQLite JDBC 3.53.4.0

The main class is:

com.example.calculator.CalculatorApplication

In IntelliJ IDEA 2026.2.3, I run CalculatorApplication to start the Spring Boot backend.

The local backend listens on:

http://localhost:8080

The SQLite database URL is configured as:

spring.datasource.url=jdbc:sqlite:calculator.db

The database table is initialized automatically from:

src/main/resources/schema.sql

Because JDK 25 applies stricter native-access checks, SQLite JDBC may display a native-access warning. When necessary, the following option can be added to the IntelliJ IDEA Spring Boot run configuration under VM options:

--enable-native-access=ALL-UNNAMED

4.3 Maven Packaging

The backend can be packaged using:

mvn clean package

The generated Spring Boot JAR can then be started on a Java-capable server.

For a public deployment using SQLite, the deployment environment must provide persistent disk storage for calculator.db; otherwise, an ephemeral filesystem could cause the calculation history to disappear after a container restart.


5. Presentation of the Finished Product

This section demonstrates the actual implemented functions. The assignment requires at least 10 screenshots, GIFs, or videos. I reserve separate screenshot positions below so that each major function can be demonstrated clearly.


5.1 Main Calculator Interface

The frontend is implemented with native HTML, CSS, and JavaScript.

The main interface contains:

  • an expression input field;

  • a result display area;

  • parentheses buttons;

  • numeric buttons;

  • +, -, ×, and ÷ buttons;

  • decimal-point input;

  • a clear-input button;

  • an equals button;

  • a backend connection-status indicator;

  • a calculation-history panel;

  • a delete button for every history record;

  • a Clear All history button.

The page uses a responsive layout. When the screen width becomes smaller, the calculator and history panels change from a two-column layout to a single-column layout.

FUNCTION TEST SCREENSHOT 01 — Main Calculator Interface
[INSERT YOUR SCREENSHOT HERE]
Suggested description: The screenshot shows the complete calculator UI, result area, backend status, calculator keypad, and calculation-history panel.


5.2 Basic Addition

Test expression:

12+8

Expected result:

20

The frontend sends the expression to the backend through POST /api/calculate; the Java backend calculates the result.

 


5.3 Basic Subtraction

Test expression:

20-7

Expected result:

13

 


5.4 Basic Multiplication

Test expression:

6*8

Expected result:

48

The UI displays the multiplication button as ×, while the internal expression sent by the frontend uses *.

 


5.5 Basic Division

Test expression:

20/4

Expected result:

5

The UI displays the division button as ÷, while the internal expression uses /.

 


5.6 Decimal Calculation

Test expression:

0.1+0.2

Expected result:

0.3

The backend uses Java BigDecimal rather than double, which provides predictable decimal arithmetic for this calculator.

 


5.7 Operator Precedence

Test expression:

1+2*3

Expected result:

7

The recursive-descent parser evaluates multiplication before addition.

 


5.8 Parentheses

Test expression:

(1+2)*3

Expected result:

9

The parser evaluates the expression inside parentheses before the outer multiplication.

 


5.9 Unary Positive and Negative Operators

The parser contains a separate unary-expression stage and therefore supports expressions such as:

-5+8
3*-2

Expected results:

3
-6

 

 

 


5.10 Invalid Expression Handling

Test expression:

1++*2

The expression parser rejects invalid syntax and throws ExpressionException.

The global exception handler converts this exception into:

HTTP 400 Bad Request

with a JSON body similar to:

{
  "error": "..."
}

The frontend then displays the backend error message.

 


5.11 Division by Zero

Test expression:

10/0

The backend checks the divisor before division.

The actual parser code throws:

throw new ExpressionException("除数不能为 0");

The request is returned as HTTP 400 Bad Request, and the frontend displays the error message.

 


5.12 Calculation History

Every successful calculation is stored in the SQLite backend database.

Each record contains:

id
expression
result
created_at

The frontend retrieves the history through:

GET /api/history

Records are ordered by ID in descending order, so the newest records are displayed first.

 


5.13 History Persistence

The calculation history is stored in the backend SQLite file:

calculator.db

It is not stored only in JavaScript memory or browser LocalStorage.

After refreshing or reopening the frontend, the page calls:

GET /api/history

and loads the existing records from SQLite.

 


5.14 Delete a Specified History Record

Each history row has a delete button.

The frontend sends:

DELETE /api/history/{id}

The backend executes:

DELETE FROM calculation_history WHERE id = ?

After a successful deletion, the frontend calls loadHistory() again to display the latest database state.

 


5.15 Extended Feature: Clear All History

In addition to deleting a specified record, this project implements an extended Clear All History function.

The frontend first displays a confirmation dialog:

if (!window.confirm("确定要清空全部历史记录吗?")) {
    return;
}

After confirmation, it sends:

DELETE /api/history

The backend deletes all rows from calculation_history.


5.16 Backend Connection Status

The frontend contains a status indicator.

When the backend responds successfully:

后端已连接

When the backend cannot be reached:

后端未连接

If the Spring Boot backend is stopped, the frontend can still display the interface and accept button input, but pressing = cannot produce a new calculation result.

This is an important verification that the core calculation is really performed on the backend.

 

 


5.17 Enter-Key Calculation and Clear Input

In addition to the calculator buttons, the frontend supports:

  • pressing Enter to calculate;

  • pressing the C button to clear the current expression and reset the displayed result to 0.

These are usability improvements implemented in app.js.

 


 


6. Design and Implementation Process

6.1 Requirement Analysis

I converted the assignment requirements into the following implementation requirements.

Assignment RequirementProject Implementation
AdditionImplemented
SubtractionImplemented
MultiplicationImplemented
DivisionImplemented
Compound expressionsImplemented
Operator precedenceImplemented by recursive-descent parser
ParenthesesImplemented
Unary + / -Implemented
Decimal numbersImplemented with BigDecimal
Invalid-expression handlingImplemented
Division-by-zero handlingImplemented
Core calculation on backendImplemented
No eval / execNo arbitrary-code evaluator is used
Backend database historySQLite
Query historyGET /api/history
Delete one recordDELETE /api/history/{id}
Clear all historyImplemented as an additional function
Front-end/back-end separationTwo independent projects
READMEIncluded in both projects
codestyle.mdIncluded in both projects
Automated testingJUnit parser tests

6.2 Technology Stack

LayerTechnology
IDEIntelliJ IDEA 2026.2.3
JavaJDK 25
Backend FrameworkSpring Boot 4.1.1
Build ToolMaven
Web LayerSpring Web MVC
Database AccessSpring JDBC / JdbcTemplate
DatabaseSQLite
SQLite DriverXerial SQLite JDBC 3.53.4.0
FrontendHTML5 + CSS3 + JavaScript
Data ExchangeHTTP + JSON
TestingJUnit 5

6.3 Actual Project Directory Structure

The frontend and backend are separate projects.

Frontend

calculator_frontend/
├── index.html
├── style.css
├── app.js
├── README.md
├── codestyle.md
└── .gitignore

Backend

calculator_backend/
├── pom.xml
├── README.md
├── codestyle.md
├── .gitignore
└── src/
    ├── main/
    │   ├── java/com/example/calculator/
    │   │   ├── CalculatorApplication.java
    │   │   ├── config/
    │   │   │   └── WebConfig.java
    │   │   ├── controller/
    │   │   │   └── CalculatorController.java
    │   │   ├── dto/
    │   │   │   ├── CalculateRequest.java
    │   │   │   ├── CalculationResponse.java
    │   │   │   └── ErrorResponse.java
    │   │   ├── exception/
    │   │   │   ├── ExpressionException.java
    │   │   │   ├── ResourceNotFoundException.java
    │   │   │   └── GlobalExceptionHandler.java
    │   │   ├── model/
    │   │   │   └── CalculationHistory.java
    │   │   ├── parser/
    │   │   │   └── ExpressionParser.java
    │   │   ├── repository/
    │   │   │   └── CalculationHistoryRepository.java
    │   │   └── service/
    │   │       └── CalculatorService.java
    │   └── resources/
    │       ├── application.properties
    │       └── schema.sql
    └── test/
        └── java/com/example/calculator/parser/
            └── ExpressionParserTest.java

6.4 Overall System Architecture

The overall architecture is:

+--------------------------------------------------+
| Frontend                                         |
| HTML + CSS + JavaScript                          |
|                                                  |
| - User input                                     |
| - Calculator buttons                             |
| - Fetch API requests                             |
| - Result display                                 |
| - Error display                                  |
| - History display                                |
| - Delete requests                                |
+--------------------------+-----------------------+
                           |
                           | HTTP / JSON
                           v
+--------------------------------------------------+
| Spring Boot Backend                              |
|                                                  |
| Controller                                       |
|     |                                            |
|     v                                            |
| CalculatorService                                |
|     |                      |                     |
|     v                      v                     |
| ExpressionParser      HistoryRepository          |
+--------------------------+-----------------------+
                           |
                           | JDBC
                           v
+--------------------------------------------------+
| SQLite                                           |
| calculation_history                              |
+--------------------------------------------------+

The architecture is intentionally layered so that UI, business logic, expression parsing, persistence, and error handling are not mixed together.


6.5 Functional Structure Diagram

Calculator System
|
|-- Calculator
|   |
|   |-- Basic Calculation
|   |   |-- Addition
|   |   |-- Subtraction
|   |   |-- Multiplication
|   |   `-- Division
|   |
|   |-- Compound Expression
|   |   |-- Operator Precedence
|   |   |-- Parentheses
|   |   |-- Decimal Numbers
|   |   `-- Unary Plus / Minus
|   |
|   `-- Error Handling
|       |-- Empty Expression
|       |-- Invalid Expression
|       |-- Invalid Number
|       |-- Mismatched Parentheses
|       |-- Unknown Character
|       `-- Division by Zero
|
|-- Calculation History
|   |
|   |-- Save History
|   |-- Query History
|   |-- Delete One Record
|   `-- Clear All History
|
|-- Frontend
|   |
|   |-- Expression Input
|   |-- Calculator Buttons
|   |-- Enter-Key Calculation
|   |-- Result Display
|   |-- Error Display
|   |-- Backend Status
|   `-- History Display
|
|-- Backend
|   |
|   |-- REST API
|   |-- Validation
|   |-- Expression Parser
|   |-- Calculation Service
|   |-- Exception Handler
|   `-- Repository
|
`-- Database
    `-- SQLite calculation_history

6.6 Frontend Design

The frontend uses native web technologies and does not depend on a frontend framework.

index.html defines the calculator interface.

style.css implements:

  • calculator and history cards;

  • status styles;

  • calculator keypad;

  • result layout;

  • history-record cards;

  • delete buttons;

  • responsive layout using a media query.

app.js implements:

  • calculator-button events;

  • direct text input;

  • Enter-key calculation;

  • clear-input behavior;

  • calculation API request;

  • history API request;

  • delete API request;

  • clear-all API request;

  • backend connection-status display;

  • backend error display;

  • date formatting;

  • HTML escaping.

The frontend does not contain an arithmetic expression evaluator.


6.7 Backend Design

The backend follows a layered structure.

Controller Layer

CalculatorController defines the HTTP API endpoints.

Service Layer

CalculatorService coordinates:

  • expression validation;

  • parser invocation;

  • result formatting;

  • timestamp generation;

  • history insertion;

  • history querying;

  • deletion;

  • DTO conversion.

Parser Layer

ExpressionParser implements the mathematical grammar and calculation logic.

Repository Layer

CalculationHistoryRepository uses JdbcTemplate to communicate with SQLite.

Exception Layer

GlobalExceptionHandler converts Java exceptions into consistent JSON HTTP responses.

Configuration Layer

WebConfig configures CORS so that independently served frontend pages can access /api/**.


6.8 API Design

The actual project exposes the following API.

MethodEndpointResponsibilitySuccess Status
POST/api/calculateCalculate expression and save history201 Created
GET/api/historyGet all calculation history200 OK
DELETE/api/history/{id}Delete a specified record204 No Content
DELETE/api/historyClear all history204 No Content

Calculation Request

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

Actual Successful Response Structure

The project uses the CalculationResponse record:

public record CalculationResponse(
        long id,
        String expression,
        String result,
        String createdAt
) {
}

Therefore, a response has the following structure:

{
  "id": 1,
  "expression": "(1+2)*3",
  "result": "9",
  "createdAt": "2026-10-07T00:00:00Z"
}

The exact timestamp depends on the actual request time.

Actual Error Response Structure

The project uses:

public record ErrorResponse(String error) {
}

Therefore, an error response is structured as:

{
  "error": "error message"
}

6.9 HTTP Status-Code Design

The actual backend uses:

201 Created

for successful POST /api/calculate.

200 OK

for successful GET /api/history.

204 No Content

for successful delete operations.

400 Bad Request

for ExpressionException.

404 Not Found

when deleting a history record that does not exist.

500 Internal Server Error

for unexpected backend exceptions.

This behavior is centralized in GlobalExceptionHandler.


6.10 Database Design

The project uses SQLite.

The schema is:

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
);
FieldPurpose
idAutomatically generated unique record ID
expressionOriginal expression submitted to the backend
resultCalculation result
created_atCalculation time

The database table is created automatically during application startup because:

spring.sql.init.mode=always
spring.sql.init.schema-locations=classpath:schema.sql

The project also limits the Hikari connection pool to one connection:

spring.datasource.hikari.maximum-pool-size=1

which is suitable for this small SQLite-based coursework application.


6.11 Calculation Module Design

The expression evaluator is implemented manually rather than using arbitrary-code execution.

The grammar can be summarized as:

expression = term (("+" | "-") term)*
term       = unary (("*" | "/") unary)*
unary      = ("+" | "-") unary | primary
primary    = number | "(" expression ")"
number     = decimal number

This creates the following precedence:

Parentheses
    ↓
Unary + / -
    ↓
Multiplication / Division
    ↓
Addition / Subtraction

The parser accepts both:

*
/

and the visual symbols:

×
÷

because the input is normalized before parsing:

String normalized = expression.replace("×", "*").replace("÷", "/");

The parser also allows whitespace and limits an expression to 200 characters.


6.12 Calculation-History Module Design

When an expression is successfully evaluated, CalculatorService:

1. receives the expression;
2. validates that it is not null or blank;
3. asks ExpressionParser to calculate;
4. formats the BigDecimal result;
5. generates the current timestamp with Instant.now();
6. saves expression, result, and timestamp to SQLite;
7. returns CalculationResponse.

Only successful calculations are inserted into the history database.

The history query executes:

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

so newer entries appear first.


6.13 Exception Handling

The parser and service detect expected input problems instead of allowing the server to fail unpredictably.

Handled cases include:

  • blank expressions;

  • expressions longer than 200 characters;

  • incomplete expressions;

  • invalid number format;

  • invalid syntax;

  • unknown characters;

  • mismatched parentheses;

  • division by zero;

  • deletion of a nonexistent history record.

GlobalExceptionHandler translates the errors into REST responses.

This keeps exception-handling logic out of the controller methods and provides a consistent frontend API.


6.14 Front-End / Back-End Interaction Process

For the expression:

(1+2)*3

the interaction process is:

User enters "(1+2)*3"
        |
        v
Frontend app.js
        |
        | POST /api/calculate
        | {"expression":"(1+2)*3"}
        v
CalculatorController
        |
        v
CalculatorService
        |
        v
ExpressionParser
        |
        | calculates 9
        v
CalculationHistoryRepository
        |
        | INSERT
        v
SQLite
        |
        v
Backend returns CalculationResponse
        |
        v
Frontend displays 9
        |
        v
Frontend calls GET /api/history
        |
        v
Latest database history is rendered

This process directly demonstrates the required front-end/back-end separation.


6.15 CORS Design

During local development, the frontend and backend run from different origins.

The backend allows the following development origins:

http://localhost:5500
http://127.0.0.1:5500
http://localhost:63342
http://127.0.0.1:63342

The allowed methods are:

GET
POST
DELETE
OPTIONS

The CORS mapping applies only to:

/api/**

For deployment, the production frontend URL should be added to the allowed-origin configuration.


7. Code Explanation

The assignment requires explanation of important implementation logic rather than simply pasting large amounts of source code. The following sections therefore show only the key code.


7.1 Frontend API Request

The frontend sends only the expression:

const response = await fetch(`${API_BASE_URL}/calculate`, {
    method: "POST",
    headers: {
        "Content-Type": "application/json"
    },
    body: JSON.stringify({ expression })
});

This is important because the browser does not calculate the final arithmetic answer. It waits for the backend response.


7.2 Backend Calculation Interface

The controller delegates the calculation to CalculatorService:

@PostMapping("/calculate")
public ResponseEntity<CalculationResponse> calculate(
        @RequestBody CalculateRequest request) {
    CalculationResponse response =
            calculatorService.calculate(request.expression());

    return ResponseEntity
            .status(HttpStatus.CREATED)
            .body(response);
}

The controller is intentionally thin. Business logic is not implemented directly inside the HTTP layer.


7.3 Service-Layer Calculation Flow

The actual service method is:

public CalculationResponse calculate(String expression) {
    if (expression == null || expression.isBlank()) {
        throw new ExpressionException("表达式不能为空");
    }

    BigDecimal value = expressionParser.evaluate(expression);
    String result = format(value);
    String createdAt = Instant.now().toString();
    String normalizedExpression = expression.trim();
    long id = historyRepository.save(
            normalizedExpression,
            result,
            createdAt
    );

    return new CalculationResponse(
            id,
            normalizedExpression,
            result,
            createdAt
    );
}

The design separates responsibilities:

ExpressionParser -> mathematical calculation
Repository       -> database persistence
Service          -> workflow coordination
Controller       -> HTTP transport

7.4 Operator Precedence

parseExpression() handles addition and subtraction:

private BigDecimal parseExpression() {
    BigDecimal value = parseTerm();

    while (true) {
        skipWhitespace();

        if (match('+')) {
            value = value.add(parseTerm(), MATH_CONTEXT);
        } else if (match('-')) {
            value = value.subtract(parseTerm(), MATH_CONTEXT);
        } else {
            return value;
        }
    }
}

Because parseExpression() calls parseTerm() first, multiplication and division are evaluated at a deeper grammar level and therefore have higher precedence.


7.5 Multiplication, Division, and Division-by-Zero Handling

private BigDecimal parseTerm() {
    BigDecimal value = parseUnary();

    while (true) {
        skipWhitespace();

        if (match('*')) {
            value = value.multiply(parseUnary(), MATH_CONTEXT);
        } else if (match('/')) {
            BigDecimal divisor = parseUnary();

            if (divisor.compareTo(BigDecimal.ZERO) == 0) {
                throw new ExpressionException("除数不能为 0");
            }

            value = value.divide(divisor, MATH_CONTEXT);
        } else {
            return value;
        }
    }
}

The divisor is validated before division, so the program returns a controlled error instead of attempting an invalid operation.


7.6 Unary Plus and Minus

The parser supports both unary operators:

private BigDecimal parseUnary() {
    skipWhitespace();

    if (match('+')) {
        return parseUnary();
    }

    if (match('-')) {
        return parseUnary().negate(MATH_CONTEXT);
    }

    return parsePrimary();
}

This supports expressions such as:

-5+8
3*-2

without treating the negative sign as an invalid binary subtraction operator.


7.7 Parentheses

if (match('(')) {
    BigDecimal value = parseExpression();
    skipWhitespace();

    if (!match(')')) {
        throw new ExpressionException(
                "括号不匹配:缺少右括号 )"
        );
    }

    return value;
}

The parser recursively evaluates the expression inside parentheses and explicitly verifies that the closing parenthesis exists.


7.8 Decimal Arithmetic with BigDecimal

The parser uses:

private static final MathContext MATH_CONTEXT =
        new MathContext(16, RoundingMode.HALF_UP);

All arithmetic uses BigDecimal.

The service removes unnecessary trailing zeros before returning the result:

private String format(BigDecimal value) {
    BigDecimal normalized = value.stripTrailingZeros();

    if (normalized.compareTo(BigDecimal.ZERO) == 0) {
        return "0";
    }

    return normalized.toPlainString();
}

This produces clean values such as:

0.3
7
-6

7.9 Input Validation

The parser checks for blank and oversized expressions:

if (expression == null || expression.isBlank()) {
    throw new ExpressionException("表达式不能为空");
}

if (expression.length() > 200) {
    throw new ExpressionException(
            "表达式过长,最多允许 200 个字符"
    );
}

The frontend input also contains:

maxlength="200"

Therefore, validation exists on both the UI side and backend side, while the backend remains the authoritative validator.


7.10 Database Insertion

The repository uses a parameterized SQL statement:

String sql =
        "INSERT INTO calculation_history" +
        "(expression, result, created_at) VALUES (?, ?, ?)";

The values are set through PreparedStatement:

statement.setString(1, expression);
statement.setString(2, result);
statement.setString(3, createdAt);

The generated ID is obtained using GeneratedKeyHolder.


7.11 Database Query

public List<CalculationHistory> findAll() {
    return jdbcTemplate.query(
            "SELECT id, expression, result, created_at " +
            "FROM calculation_history ORDER BY id DESC",
            rowMapper
    );
}

This query is the source of the history shown by the frontend.


7.12 Database Deletion

Single-record deletion is:

public int deleteById(long id) {
    return jdbcTemplate.update(
            "DELETE FROM calculation_history WHERE id = ?",
            id
    );
}

The service checks the number of affected rows:

public void deleteHistory(long id) {
    int affectedRows = historyRepository.deleteById(id);

    if (affectedRows == 0) {
        throw new ResourceNotFoundException(
                "历史记录不存在,id=" + id
        );
    }
}

Therefore, deleting a nonexistent ID correctly produces HTTP 404 Not Found.


7.13 Clear-All Extension

The repository contains:

public int deleteAll() {
    return jdbcTemplate.update(
            "DELETE FROM calculation_history"
    );
}

The controller exposes it through:

@DeleteMapping("/history")
public ResponseEntity<Void> clearHistory() {
    calculatorService.clearHistory();
    return ResponseEntity.noContent().build();
}

This is an additional function beyond the required delete-one-history feature.


7.14 Global Exception Handling

The backend centralizes error responses:

@ExceptionHandler(ExpressionException.class)
public ResponseEntity<ErrorResponse>
handleExpressionException(ExpressionException exception) {
    return ResponseEntity.badRequest()
            .body(new ErrorResponse(exception.getMessage()));
}

A missing history record is mapped to 404, and unexpected exceptions are mapped to 500.

This provides a stable error contract for the frontend.


7.15 History Rendering and HTML Escaping

Calculation expressions come from user input. Before values are inserted into generated history HTML, the frontend escapes special characters:

function escapeHtml(value) {
    return String(value)
        .replaceAll("&", "&amp;")
        .replaceAll("<", "&lt;")
        .replaceAll(">", "&gt;")
        .replaceAll('"', "&quot;")
        .replaceAll("'", "&#039;");
}

This prevents raw user text from being directly interpreted as HTML in the history panel.


8. Functional Testing

The following table can be completed after inserting the real screenshots.

No.Test ItemTest Input / ActionExpected ResultActual Result
1Addition12+820[FILL IN]
2Subtraction20-713[FILL IN]
3Multiplication6*848[FILL IN]
4Division20/45[FILL IN]
5Decimal0.1+0.20.3[FILL IN]
6Precedence1+2*37[FILL IN]
7Parentheses(1+2)*39[FILL IN]
8Unary minus3*-2-6[FILL IN]
9Invalid syntax1++*2HTTP 400 / error shown[FILL IN]
10Division by zero10/0HTTP 400 / error shown[FILL IN]
11History saveComplete valid calculationNew record appears[FILL IN]
12History persistenceRefresh/reopen frontendExisting history remains[FILL IN]
13Delete one recordClick record delete buttonTarget record removed[FILL IN]
14Clear allConfirm Clear AllAll records removed[FILL IN]
15Backend separationStop Spring Boot and calculateNo new result; connection error[FILL IN]
16JUnitRun ExpressionParserTestAll tests pass[FILL IN]

9. Personal Journey and Learnings

This assignment helped me understand that front-end/back-end separation is not simply a matter of putting HTML and Java files into different directories. The responsibilities must also be separated.

The frontend in this project has no core arithmetic evaluator. It sends an expression to the Java backend and waits for a result. When the Spring Boot application is stopped, the frontend remains visible, but it cannot independently generate a new calculation result. This provided a clear practical understanding of client-server responsibility boundaries.

The most important technical challenge was expression parsing. A calculator must correctly handle operator precedence, parentheses, unary negative numbers, decimal input, incomplete expressions, invalid syntax, and division by zero. I implemented a recursive-descent parser instead of using eval or another code-execution mechanism. Dividing the grammar into parseExpression, parseTerm, parseUnary, and parsePrimary made the operator precedence explicit.

Another important decision was using BigDecimal instead of double. This allowed decimal expressions such as 0.1+0.2 to produce a clean 0.3 result. I also learned that result formatting matters, so stripTrailingZeros() and toPlainString() are used before the result is returned.

The history function improved my understanding of persistence. The frontend does not treat its local state as the source of truth. Every successful result is stored in SQLite, and the frontend retrieves the latest history using the backend API. After deletion, the frontend re-queries the API so that the displayed history matches the actual database state.

I also encountered a runtime issue related to the development environment. With JDK 25, SQLite JDBC may produce a native-access warning because the driver loads a native SQLite library. The application itself can still start normally, and adding:

--enable-native-access=ALL-UNNAMED

to the JVM options removes the warning. This debugging process helped me distinguish warnings from actual startup errors.

The front-end/back-end integration also introduced CORS. Since IntelliJ IDEA may serve the frontend from port 63342 while Spring Boot runs on port 8080, the browser considers them different origins. The backend therefore contains explicit CORS configuration for the local frontend addresses.

Finally, I learned that testing individual layers is useful. For example, GET /api/history can be tested directly before debugging the frontend. JUnit tests can verify the expression parser independently from the database and browser. This makes debugging more systematic.

...全文

相关推荐

MyEclipse_6_开发实战教程.doc

MyEclipse_6_开发实战教程.docMyEclipse_6_开发实战教程.docMyEclipse_6_开发实战教程.docMyEclipse_6_开发实战教程.doc

yugongkai123

基于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
评论
收藏 已收藏
分享
返回顶部