Author: Chunwei Chang
- Introduction
- Project Preview
- Tech Stack List
- Highlight
- Limitation
- How to run on your computer
- Development Timeline and Detail
Budget Tracker
- A web application for users to track their money privately or with others.
- A user have multiple books, they can invite friends to track money in assigned books together.
- A money record can share in many books.
Full-Stack Project
- Frontend:
React
+MobX
, Frontend Preview (test data, some features might cause error due to the lack of backend) - Backend:
SpringBoot
+MyBatis
, SSM (Spring-SpringMVC-MyBatis) structure
Type | Tool | Version | Comment |
---|---|---|---|
Frontend | React | 18.1.0 | functional component, hooks |
Mobx | 6.5.0 | manage data, similar to Redux |
|
scss | Sassy css, similar to Less |
||
axios | 0.27.2 | transfer data with backend | |
Ant Design | 4.20.3 | UI framework | |
Echarts | 5.3.2 | Data visualization library | |
Backend | SpringBoot | 2.6.7 | Spring + SpringMVC , easy to use |
MyBatis | 3.5.9 | operate data in database | |
MyBatis-Plus | 3.5.1 | some helper API for MyBatis |
|
MySQL | 5.7 | database | |
JWT | authorization (token, refresh token) |
- ✅ Developed by lots of
React Hooks
, such asuseState()
,useEffect()
,useRef()
. - ✅
MobX
+Hooks
to manage data, components can get data withuseStore()
. - ✅
Axios
interceptor, usetoken
to check if an user is logged-in, otherwise go to login page. - ✅
Auth
component to ensure a user is valid (with token).<Route path="/" element={ <Auth> <MainLayout /> </Auth> }>
function Auth({ children }) { const token = getLocalToken() if (token) { // token exists, show children component return <>{children}</> } else { // not exist, go to login page return <Navigate to='/login' replace></Navigate> } }
- ✅ Simple cache
- Since backend will not return all table data, we might suffer from querying every time when a user go to another page. Here we cache some table data in the frontend. (It is better to employ formal cache functionality such as
Redis
though)const updateTable = async (newPageParams) => { setTableLoading(true) const { oldFilter } = financeStore.pageParams const newFilter = newPageParams.filter const { curCacheIndex, refreshMinIdx, refreshMaxIdx, cacheNum, pageSize, } = financeStore.pageParams financeStore.setPageParams({ ...financeStore.pageParams, ...newPageParams }) /** * query database condition: * Cache some data on the frontend, only when condition meet will query the database. * 在前端 cache 一部分数据, 只有当查看的数据不在 cache 时, 才会发起请求 */ if (curCacheIndex < refreshMinIdx || curCacheIndex + pageSize > refreshMaxIdx || JSON.stringify(oldFilter) !== JSON.stringify(newFilter)) { await financeStore.getItemList() financeStore.setPageParams({ ...financeStore.pageParams, refreshMinIdx: curCacheIndex, refreshMaxIdx: curCacheIndex + cacheNum }) } setTableLoading(false) }
- Since backend will not return all table data, we might suffer from querying every time when a user go to another page. Here we cache some table data in the frontend. (It is better to employ formal cache functionality such as
- ✅
JWT + refresh token
to ensure a request is authorized. - ✅
SpringBoot
ecosystemSpring
IOC container, easy to useBean
by calling@Autowired
.Model-View-Controller
(MVC) structure.- Model:
MyBatis
+MySQL
- View: Json Object
- Controller:
SpringMVC
controller (Servlet)
- Model:
- ✅ Unified response object,
Result
is an object to wrap return value to the frontend.@GetMapping("/stat") public ResponseEntity<Object> getUserStat(HttpServletRequest request) { try { Object data = userService.getUserStat(request); return Result.success(data); } catch (Exception e) { log.info(e.getMessage()); return Result.fail("Get user stat error"); } }
⚠️ No formal cache functionality.- Frontend will query database directly via
SpringBoot
, there is no cache in-between such asRedis
.
- Frontend will query database directly via
⚠️ Not using too many database functionality.- No
Foreign Key
to ensure cascade update, all controlled by developers in code. - No
Stored Procedure
to wrap a series operation.
- No
⚠️ No password encryption in data transfer and storage.
- Clone the project to your computer.
- Install dependencies.
- React (Frontend)
cd react-app
npm install
npm start
- Visit http://localhost:3000
- MySQL (Database)
- Import data in
./MySQLDump
to your MySQL database - (Optional) Assign a username and password to operate this schema
- Import data in
- SpringBoot (Backend)
- Visit
./spring-boot
directory - Open
pom.xml
as a Maven project in your IDE - Refresh and install Maven dependencies
- Edit datasource settings
application.yaml
in./spring-boot/src/java/resources/
- especially the
username
andpassword
to link MySQL
- Maven compile and Run the project
- Base URL: http://localhost:8080
- Visit
- React (Frontend)
- You are ready to go.
Date | Title |
---|---|
May 9, 2022 | React + SpringBoot init |
May 10, 2022 | Database Design (ERD, MySQL) |
May 11, 2022 | MyBatis, React, MobX, Antd |
May 12, 2022 | JWT, Interceptor, CORS, Antd |
May 13, 2022 | Http code, Echarts, Antd table |
May 14, 2022 | database, Service, MyBatis-Plus, [NewItem] |
May 15, 2022 | [NewItem] done, both end |
May 16, 2022 | [Finance] filter, query cache optimization |
May 17, 2022 | MyBatis , [Book] (carousel, Echarts) |
May 18, 2022 | page draft, business logic |
May 19, 2022 | [Notification], partner logic |
May 20, 2022 | [Partner], tabs |
May 21, 2022 | ✌️ Break |
May 22, 2022 | [Dashboard], logo |
May 23, 2022 | Refresh Token, test data |
Frontend
- MobX: manage data, similar to Redux
- scss (sassy CSS), similar to Less
- Antd UI framework (look up, then copy & paste, that’s how UI framework works)
Backend
- DTO (Data Transfer Object)
- separate Controller, Service
Backend
- token (local storage), login, logout
- Antd, prepare Layout
Frontend
- JSON Web Token (JWT), how it works (header.payload.signature)
- combine with interceptor to make sure a user has logged in
- configure CORS in SpringBoot, make sure Frontend can get data
Frontend
- sign out feature
- filter (go to /login when GET response 401 status code)
- Echarts init
- Antd table, init, delete feature (only on frontend)
Backend
- customized http code, re-construct filter, JwtUtil, UserService
Frontend
- Antd, [NewItem] button and its form
Backend
- database re-construct, new table: book_invite
- SpringBoot Service re-construct, send map parameter, remove most DTO
- MyBatis-Plus, query data from database
Frontend
- [NewItem] form complete
- default data, data validation, extended dropdown
- data population. Get data from backend, then populate the drop menu
Backend
- ItemService: addItem
- database, new table: Category
- query for frontend data population. e.g. category, book
Frontend
- [Finance] filter done
- cache some data in frontend, reduce frequency to directly query database
- mobx stores optimization. Mostly, getter will query backend when dataList is empty
Backend
- SpringBoot controller parse data in different request.
- get: @RequestParam
- other: frontend wrap in “data”, which is @RequestBody
- query itemDetailList by frontend params
Frontend
- Antd Carousel with arrows (prev, next)
- Carousel Card
- Connect carousel data and description section
- (draft) implement echarts in description section
Backend
- MyBatis <if> inject HashMap parameter
- BookDetail SQL query with resultMap
Frontend
- [Book] page 90%, with double-axis Echarts, invite, new book, rename function
- [Partner] page draft, framework
- [Notification] page draft, framework
Backend
- Book Controller, REST API, with delete, put (update), add
Frontend
- mobx data
- [Notification]
- [Book], partner detail, delete book
Backend
@Controller
try-catch, pack data into ResponseEntity,@Service
return raw data- partner logic done: send invitation, respond, the right to see item detail, book stat
- [Finance] table, delete logic, as long as they have the right in the book:
- user can see partner’s item
- user can delete partner’s item
Frontend
- [Partner], tabs (overview, management). Originally using modals wrap a table to edit, but when table data update, all page will re-render, so using [tabs] in the end.
- support revoke partnership
Backend
- partnerList, partner nested with book stat
- revoke partnership logic
- queryWrapper code rewrite
Backend
- separate [Dashboard] query, 1 for static, 1 for dynamic chart
Frontend
- separate [Dashboard] query, 1 for static, 1 for dynamic chart
- [Dashboard] page done
- separate Personal and AllBook part
- combine echarts and antd component, so that user can filter data
- notification badge
- side menu logo
- [Book] trend chart optimization, same as [Dashboard] (user can filter)
- [Book] card actions. It shows only when user has the authority
Frontend
- refresh token with axios
- sample test data when server is down
Backend
- JWT refresh token