832401218_calculator_blog
Front-End / Back-End Separated Calculator System — First AssignmentCourse: Software Engineering, Fuzhou University (2601_FZU-MU_SE)Student ID: 832401218Assignment: First Assignment — Front-End and Back-End Separation Calculator System1. Project OverviewThis assignment requires afront-end / back-end separatedcalculator system: the front end handles only the user interface and interaction, the back end handles expression parsing, evaluation, and history persistence, and the two sides communicate over HTTP/JSON APIs. The system must be deployed to a cloud server and publicly accessible.Features I implemented:Arithmetic with operator precedence, parentheses, decimals, and unary /- (e.g.-5,3 * -2)Invalid-expression detection with user-friendly error messages (division by zero, missing operands, illegal characters, etc.)Calculation history: display, per-record deletion, and clear-allHistory persisted in an SQLite database —records survive a service restartTwo fully separated GitHub repositories (front end / back end), deployed to the cloud and publicly accessible2. Code RepositoriesItemLinkFront-end repositoryhttps://github.com/WJ4F5DA2/832401218_calculator_frontendBack-end repositoryhttps://github.com/WJ4F5DA2/832401218_calculator_backendFront-end code stylehttps://github.com/WJ4F5DA2/832401218_calculator_frontend/blob/main/codestyle.mdBack-end code stylehttps://github.com/WJ4F5DA2/832401218_calculator_backend/blob/main/codestyle.mdAll code, README files, comments, and UI text in both repositories are written in English, and so is this blog post.3. PSP TableModuleEstimated (min)Actual (min)Requirements analysis1015System design1520Front-end development3035Back-end development3045Expression evaluation module2025Database design1015History module1520Front-end / back-end integration2030Testing1525Deployment3090Blog writing3045Total225365Deployment took far longer than estimated: the platform I chose first (Render) now requires every user to add a credit or debit card (Visa/MasterCard only, UnionPay not accepted) before the first deployment, so I switched to PythonAnywhere, which needs no payment method at all, and went through the deployment flow again.4. Tech Stack and Functional StructureTech stackLayerTechnologyFront endPlain HTML5 CSS3 JavaScript (no framework, no build step), Fetch APIBack endPython 3.10 Flask 3.xDatabaseSQLite (Python standard librarysqlite3, zero extra dependencies)Expression parsingHand-written tokenizer recursive-descent parser (no eval/exec)DeploymentPythonAnywhere free tier (WSGI static files mapping)Functional structure diagramFront-End / Back-End Separated Calculator System ├── Front end (832401218_calculator_frontend, pure static pages) │ ├── Calculator keypad │ │ ├── Digits 0-9, decimal point │ │ ├── Operators - * /, parentheses ( ) │ │ ├── Backspace and Clear (C) │ │ └── Equals (): calls POST /api/calculate │ ├── Expression display / result display / error message area │ └── History panel │ ├── Display history (GET /api/history, newest first) │ ├── Delete one record (DELETE /api/history/{id}) │ ├── Clear all (DELETE /api/history) │ └── Manual refresh └── Back end (832401218_calculator_backend, Flask SQLite) ├── Controller layer: HTTP routes /api/*, JSON in/out ├── Service layer │ ├── Expression tokenizing recursive-descent parsing evaluation │ └── Calculation and history business logic, error wrapping └── Model layer: SQLite connection, table creation, CRUDArchitecture notes:the two sides communicate only through JSON APIs. The front end sends the raw expression string to the back end as-is; the back end parses and evaluates it, stores the record in the database, and returns the result as JSON; the front end only renders what it receives. The front end containsno calculation logic at all, which keeps the separation boundary sharp and also means validation cannot be bypassed by tampering with the front end — all validation happens on the back end.5. Key Code Walkthrough5.1 Expression evaluation: hand-written tokenizer recursive-descent parser (calculator_service.py)The assignment explicitly forbidseval(), so I implemented the classic two-phase approach from compiler fundamentals.Tokenizing:the expression string is split into a token stream; illegal characters are rejected right here:deftokenize(expression):Split an expression string into a list of tokens.tokens[]i0lengthlen(expression)whileilength:chexpression[i]ifch.isspace():i1continueifch.isdigit()orch.:startiwhileilengthand(expression[i].isdigit()orexpression[i].):i1numberexpression[start:i]ifnumber.count(.)1:raiseExpressionError(Invalid number: %s%number)tokens.append(Token(num,number))elifchin-*/:tokens.append(Token(op,ch))i1elifch(:tokens.append(Token(lparen,ch))i1elifch):tokens.append(Token(rparen,ch))i1else:raiseExpressionError(Invalid character: %s%ch)ifnottokens:raiseExpressionError(Empty expression)returntokensRecursive-descent parsing:four grammar rules map one-to-one onto four methods; operator precedence and associativity fall out of the grammar structure itself (exprhandles /-,termhandles * and /,factorhandles unary signs,primaryhandles numbers and parentheses):expr : term (( | -) term)* term : factor ((* | /) factor)* factor : ( | -) factor | primary primary : NUMBER | ( expr )classParser:Recursive-descent parser for arithmetic expressions.defparse(self):valueself._parse_expr()ifself.pos!len(self.tokens):raiseExpressionError(Unexpected token: %s%self._peek().value)returnvaluedef_parse_term(self):valueself._parse_factor()whileself._peek()isnotNoneandself._peek().kindopand(self._peek().valuein*/):opself.tokens[self.pos].value self.pos1rightself._parse_factor()ifop*:valuevalue*rightelse:ifright0:raiseExpressionError(Division by zero)valuevalue/rightreturnvalueDivision by zero raisesExpressionError(Division by zero)during evaluation, which the route layer converts into a 400 response for the front end. Results are formatted so integers drop the trailing.0(9.0→9) to avoid redundant output.5.2 API design (controller/routes.py)Flask Blueprint mounted under the/apiprefix; all requests and responses are JSON, and errors follow a uniform shape{success: false, message: ...}:bpBlueprint(api,__name__,url_prefix/api)bp.route(/calculate,methods[POST])defcalculate_route():Evaluate an expression sent by the front end and store the record.bodyrequest.get_json(silentTrue)or{}expressionbody.get(expression)try:recordcalculate(expression)exceptCalculationErrorasexc:returnjsonify({success:False,message:exc.message}),400returnjsonify({success:True,expression:record[expression],result:record[result],id:record[id],created_at:record[created_at],}),200API overview:MethodPathDescriptionGET/api/healthHealth checkPOST/api/calculateEvaluate an expression and store the recordGET/api/historyReturn all history records (newest first)DELETE/api/history/{id}Delete one record (404 if it does not exist)DELETE/api/historyClear all history records (optional feature)For cross-origin access I did not pull in an extra dependency; instead, Flask’safter_requesthook sets CORS headers manually so the front end can be hosted on any domain:app.after_requestdefadd_cors_headers(response):response.headers[Access-Control-Allow-Origin]*response.headers[Access-Control-Allow-Headers]Content-Typeresponse.headers[Access-Control-Allow-Methods]GET, POST, DELETE, OPTIONSreturnresponse5.3 Database operations (model/database.py)The SQLite database file lives in the project root; the table is created automatically on first start, so no manual setup is needed:definit_db():Create the calculation history table if it does not exist.connget_connection()try:conn.execute( 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 ) )conn.commit()finally:conn.close()All SQL uses parameterized binding (?placeholders), which eliminates SQL injection; the query orders byid DESC, which directly satisfies the newest-first display requirement:deffetch_all_records():Return all history records, newest first.connget_connection()try:rowsconn.execute(SELECT id, expression, result, created_at FROM calculation_history ORDER BY id DESC).fetchall()return[dict(row)forrowinrows]finally:conn.close()6. Deployment and AccessLive application: https://wj4f5da2.pythonanywhere.com/Both ends are deployed on the PythonAnywhere free tier:Back end:a PythonAnywhere web app served through WSGI. After uploading the code to the server, the WSGI configuration file only needs a few lines to mount the Flask application:importsys path/home/wj4f5da2/832401218_calculator_backendifpathnotinsys.path:sys.path.insert(0,path)fromsrc.appimportcreate_app applicationcreate_app()Front end:a PythonAnywhere static files mapping points the site root/at the front end’ssrcdirectory, so the server serves the static assets directly; requests to/api/*match no static file and fall through to WSGI, where Flask handles them. The whole application is therefore exposed under a single domain, while the front end and back end remain fully separated in code, repositories, and responsibilities.The front end’sAPI_BASEpoints tohttps://wj4f5da2.pythonanywhere.com/api.Why PythonAnywhere: completely free, requires no credit/debit card at all, natively supports Flask SQLite, and web apps run permanently without sleeping — a reliable option for a student assignment.Testing notes:a free-tier web app is valid for 3 months; PythonAnywhere emails a reminder before expiry, and renewal takes a few clicks in the console. If the application is unreachable during grading, it has usually been stopped manually — logging into the console and clicking “Reload” brings it back. Instructions for running locally are in each repository’s README (python run.pyfor the back end, any static file server for the front end).7. ScreenshotsAll screenshots were taken from the running system. The UI is in English, consistent with the project’s language.【Figure 1: 01-basic-add.png】Basic addition: enter12 8, press, the result 20is shown and a history record is added automatically.【Figure 2: 02-compound.png】Mixed operators and precedence:(12)*3 9— parentheses first, then multiplication.【Figure 3: 03-decimal.png】Decimal arithmetic:10 / 4 2.5.【Figure 4: 04-unary.png】Unary minus:-5 8 3; expressions like3 * -2are also supported.【Figure 5: 05-div-zero.png】Division by zero:1/0reports “Division by zero” and no history record is created.【Figure 6: 06-invalid.png】Invalid expression:1(a trailing unary plus with no operand) reports “Unexpected end of expression”.【Figure 7: 07-history.png】History panel: every successful calculation is appended automatically, displayed newest first, and each record has a delete button.【Figure 8: 08-delete.png】Per-record deletion: clicking×on a record removes only that record; the rest are kept.【Figure 9: 09-persistence.png】Persistence check: after calculating5 × 8 40andrefreshing the page, all 4 records (including 5×840) are still there — the data lives in the back-end SQLite database.【Figure 10: 10-backend-down.png】Separation proof: with theback end stopped, pressingresets the result to zero and reports “Failed to fetch”, and the history panel shows “Cannot connect to the back end” — calculation really happens on the back end, and the front end cannot produce a result on its own.【Figure 11: 11-clear-all.png】Clear all: after clicking “Clear”, the history is empty and shows “No history records”.8. TestingThe back end ships withtest_api.py, an API smoke-test suite (Flask test client) covering addition/subtraction/multiplication/division, precedence and parentheses, unary signs, decimals, invalid expressions, division by zero, and history insert/query/delete/clear — 30 assertions in total, all passing. Front-end / back-end integration was verified in both directions (UI operations plus back-end logs), including the failure scenario “no result when the back end is down” (Figure 10).9. Personal SummarySeparation is about boundaries.The key is resisting the urge to “just calculate it in the front end”: all computation, validation, and persistence belong on the back end, while the front end only sends requests and renders responses. This lets both sides be developed, tested, and deployed independently.Writing a parser by hand (no eval) was very rewarding.Tokenizing recursive descent was the first time classroom theory really clicked for me: four grammar rules handle precedence, associativity, unary operators, and parentheses — more elegant than I expected.Deployment was the biggest lesson.Free-tier policies of cloud platforms change without notice (Render went from no-card-required to mandatory card binding), so always confirm payment requirements before choosing a platform; PythonAnywhere turned out to be a solid, dependable option for a student assignment.Engineering hygiene matters too.Writing README files, a codestyle document, English comments, and keeping the whole project in English took real time — but it makes the repositories presentable and trained my engineering communication skills.

相关新闻

AI技术栈分层扫盲:从GPU计算到模型推理的工程认知地图

AI技术栈分层扫盲:从GPU计算到模型推理的工程认知地图

1. 项目概述:这不是一份“节日速成课”,而是一张AI技术世界的实景地图“AI概念大全:技术人的国庆7天扫盲指南”——光看标题,你可能以为这是份带点营销味的节前知识清单。但作为连续三年在AI基础设施层做模型服务编排、参与过5个行…

2026/10/4 1:21:14 阅读更多 →
AI Agent工程化:七要素与七个决策点全解析

AI Agent工程化:七要素与七个决策点全解析

这两年聊 AI Agent 的人不少,但真正把它当工程系统来搭的人很少。GitHub 上一搜一大把 demo,跑起来能聊天、能调几个工具,可一聊到并发、可观测、降级、评估和人工复核,很多人就沉默了。AI Agent 的工程实现不是“写个 prompt 再调…

2026/10/4 1:21:14 阅读更多 →
Android应用安装失败根因解析:PackageManagerService深度指南

Android应用安装失败根因解析:PackageManagerService深度指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 1:21:14 阅读更多 →

最新新闻

OpenCode IDE扩展接入Ace Data Cloud:突破免费限制与多端配置

OpenCode IDE扩展接入Ace Data Cloud:突破免费限制与多端配置

最近在团队里推 AI 编程时遇到一个很典型的场景:大家早就在终端里把 OpenCode 用得挺顺,但真正写业务代码的时候,人的眼睛还是离不开编辑器。于是所有人都希望——AI 能直接住在 VS Code / Cursor / Windsurf 里。OpenCode IDE Extension 就是…

2026/10/4 6:41:33 阅读更多 →
python的先进制造技术工业场景模拟第五十六篇:加载3D打印翘边试验数据集,建立分类模型,识别容易产生翘边的参数组合。

python的先进制造技术工业场景模拟第五十六篇:加载3D打印翘边试验数据集,建立分类模型,识别容易产生翘边的参数组合。

周三早上,增材制造实验室,FDM 桌面级 3D 打印机刚跑完一轮 ABS 材料翘边对比试验。"这批测试件,12 组参数组合,每组打 5 件,翘边率从 0% 到 80% 不等,"实验室助教小林指着打印平台,&q…

2026/10/4 6:41:33 阅读更多 →
PDHG原始对偶混合梯度法:原理、调参与工程实践指南

PDHG原始对偶混合梯度法:原理、调参与工程实践指南

先直接说结论:PDHG(Primal-Dual Hybrid Gradient,原始-对偶混合梯度法)是目前处理“大规模、非光滑、带线性算子复合结构”优化问题最实用的算法之一。它不像梯度下降那样要求目标函数处处光滑,也不像ADMM那样需要频繁…

2026/10/4 6:41:33 阅读更多 →
卡尔曼滤波五大公式详解:从预测更新到状态估计的工程实践

卡尔曼滤波五大公式详解:从预测更新到状态估计的工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 6:41:33 阅读更多 →
JavaWeb图书管理系统:Tomcat8.5+MySQL5.7毕设级可部署源码

JavaWeb图书管理系统:Tomcat8.5+MySQL5.7毕设级可部署源码

简介:这是一套完整可用的JavaWeb图书管理系统毕业设计项目,面向计算机专业本科生及Java初学者,解决课程设计、期末大作业与毕业设计选题难、调试难、部署难等实际问题。资源包含前端(HTML/CSS/JS/JSP)、后端&#xff0…

2026/10/4 6:41:33 阅读更多 →
Trae实战:AI原生IDE如何重塑开发工作流与效率

Trae实战:AI原生IDE如何重塑开发工作流与效率

最近一个月,我把主力编辑器从 VS Code 切到了 Trae。一开始是抱着尝鲜的心态,毕竟“AI 原生 IDE”这个概念听得太多了,真正好用的没几个。但用下来之后,我发现它确实改变了我每天写代码的节奏:不是简单地在编辑器里塞一…

2026/10/4 6:40:32 阅读更多 →

日新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 1:00:58 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 1:00:58 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 1:00:58 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 1:00:58 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 1:00:58 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 1:00:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/2 10:36:31 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/3 9:42:35 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/3 9:42:36 阅读更多 →