First Assignment — Front-End and Back-End Separation Calculator System Java 25 + Spring Boot 4.1.1
Course Information
| Item | Information |
|---|---|
| Course for This Assignment | Software Engineering |
| Assignment Requirements | First Assignment — Front-End and Back-End Separation Calculator System |
| Objectives of This Assignment | Develop 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 References | Assignment page; Google Java Style Guide; Google JavaScript Style Guide; Spring Boot documentation; SQLite documentation |
| Student ID | 832401128 |
| Student Name | Hongfu 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
TBDvalues with your real development records. The estimated values should also be adjusted if you already recorded your own estimates before implementation.
| PSP Stage | Estimated Time (min) | Actual Time (min) |
|---|---|---|
| Requirement analysis | 40 | TBD |
| System design | 50 | TBD |
| Frontend development | 120 | TBD |
| Backend project setup | 60 | TBD |
| Backend API development | 80 | TBD |
| Expression calculation module | 150 | TBD |
| Database design and persistence | 70 | TBD |
| Calculation-history module | 60 | TBD |
| Delete-history function | 40 | TBD |
| Front-end/back-end integration | 90 | TBD |
| Exception handling and validation | 60 | TBD |
| Testing and debugging | 90 | TBD |
| Deployment | 90 | TBD |
| README and code-standard documentation | 50 | TBD |
| Blog writing and screenshot preparation | 120 | TBD |
| Total | 1180 | TBD |
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
Enterto calculate; -
pressing the
Cbutton to clear the current expression and reset the displayed result to0.
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 Requirement | Project Implementation |
|---|---|
| Addition | Implemented |
| Subtraction | Implemented |
| Multiplication | Implemented |
| Division | Implemented |
| Compound expressions | Implemented |
| Operator precedence | Implemented by recursive-descent parser |
| Parentheses | Implemented |
Unary + / - | Implemented |
| Decimal numbers | Implemented with BigDecimal |
| Invalid-expression handling | Implemented |
| Division-by-zero handling | Implemented |
| Core calculation on backend | Implemented |
No eval / exec | No arbitrary-code evaluator is used |
| Backend database history | SQLite |
| Query history | GET /api/history |
| Delete one record | DELETE /api/history/{id} |
| Clear all history | Implemented as an additional function |
| Front-end/back-end separation | Two independent projects |
| README | Included in both projects |
codestyle.md | Included in both projects |
| Automated testing | JUnit parser tests |
6.2 Technology Stack
| Layer | Technology |
|---|---|
| IDE | IntelliJ IDEA 2026.2.3 |
| Java | JDK 25 |
| Backend Framework | Spring Boot 4.1.1 |
| Build Tool | Maven |
| Web Layer | Spring Web MVC |
| Database Access | Spring JDBC / JdbcTemplate |
| Database | SQLite |
| SQLite Driver | Xerial SQLite JDBC 3.53.4.0 |
| Frontend | HTML5 + CSS3 + JavaScript |
| Data Exchange | HTTP + JSON |
| Testing | JUnit 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.
| Method | Endpoint | Responsibility | Success Status |
|---|---|---|---|
POST | /api/calculate | Calculate expression and save history | 201 Created |
GET | /api/history | Get all calculation history | 200 OK |
DELETE | /api/history/{id} | Delete a specified record | 204 No Content |
DELETE | /api/history | Clear all history | 204 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
);
| Field | Purpose |
|---|---|
id | Automatically generated unique record ID |
expression | Original expression submitted to the backend |
result | Calculation result |
created_at | Calculation 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("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """)
.replaceAll("'", "'");
}
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 Item | Test Input / Action | Expected Result | Actual Result |
|---|---|---|---|---|
| 1 | Addition | 12+8 | 20 | [FILL IN] |
| 2 | Subtraction | 20-7 | 13 | [FILL IN] |
| 3 | Multiplication | 6*8 | 48 | [FILL IN] |
| 4 | Division | 20/4 | 5 | [FILL IN] |
| 5 | Decimal | 0.1+0.2 | 0.3 | [FILL IN] |
| 6 | Precedence | 1+2*3 | 7 | [FILL IN] |
| 7 | Parentheses | (1+2)*3 | 9 | [FILL IN] |
| 8 | Unary minus | 3*-2 | -6 | [FILL IN] |
| 9 | Invalid syntax | 1++*2 | HTTP 400 / error shown | [FILL IN] |
| 10 | Division by zero | 10/0 | HTTP 400 / error shown | [FILL IN] |
| 11 | History save | Complete valid calculation | New record appears | [FILL IN] |
| 12 | History persistence | Refresh/reopen frontend | Existing history remains | [FILL IN] |
| 13 | Delete one record | Click record delete button | Target record removed | [FILL IN] |
| 14 | Clear all | Confirm Clear All | All records removed | [FILL IN] |
| 15 | Backend separation | Stop Spring Boot and calculate | No new result; connection error | [FILL IN] |
| 16 | JUnit | Run ExpressionParserTest | All 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
基于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最新研究)



















