ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Spring Boot个人记账系统毕设实战:从需求拆解到统计报表

Spring Boot个人记账系统毕设实战:从需求拆解到统计报表 不用慌个人记账系统在毕业设计里是个非常成熟的方向但越是看似简单的题目越容易在细节上栽跟头。市面上能找的源码一大堆能讲清楚为什么这么设计、每个模块解决什么问题的少之又少。这篇内容就是把一个基于 Spring Boot 的个人记账系统从需求拆解到数据库设计再到核心代码实现和常见坑点完整过一遍计划拿它做毕设或者想练手 Spring Boot 的同学可以直接参考这套思路。1. 项目概述与需求拆解1.1 核心需求解析开始动手写代码之前先得想清楚个人记账系统到底要做什么。如果只是最基本的增删改查那这个项目做出来也没有太多说服力。真正扛得住答辩和个人使用的记账系统至少要覆盖这么几条核心链路用户注册登录、账目流水记录、收支分类管理、统计报表分析、预算提醒。我习惯把需求拆成三个层次。第一层是基础功能也就是用户能注册登录登录之后能新增一笔收入或支出记录能看到自己记录过的账单列表能编辑和删除。这一层是系统能跑起来的最低要求。第二层是增强功能比如记账的时候可以选择分类分类可以自己维护默认给一套生活常用分类用户还可以增删改账单列表支持按时间范围、分类、收支类型筛选。第三层是进阶功能统计模块按月给出支出构成饼图、收支趋势折线图预算模块让用户设置一个月的预算金额当支出超过某个比例或超支时给出提示。这样做的好处是每个人都可以根据自己的开发时间和技术水平决定做到哪一层。时间紧张就保证第一层完整实现有余力就把第二层第三层全部做好。答辩的时候老师问“你这个系统有什么亮点”答“支持多维度的统计分析并通过 ECharts 可视化展示”就比答“就是普通的增删改查”要有底气得多。另外一点很重要记账的核心数据是金额这个不能不做精度处理。数据库里金额字段要按十进制存Java代码里用BigDecimal接收和计算绝对不要用double。这块我在第4章会详细说但它属于需求阶段就应该定的技术口径。1.2 技术选型思路个人记账系统的技术栈我推荐 Spring Boot MyBatis Plus MySQL前端可以用 Vue 搭配 Element Plus也可以选择前后端不分离的方案直接用 Thymeleaf 模板引擎。两种方案都可以但如果问我实际带毕设的经验我更推荐的组合是后端 Spring Boot MyBatis Plus前端用 Vue3 Element Plus ECharts。为什么这么选先解释后端。Spring Boot 本身是当前 Java 后端开发的事实标准毕设选它技术上是完全稳妥的。MyBatis Plus 除了提供单表 CRUD 的现成能力还有分页插件、逻辑删除、自动填充这些小工具写记账这种以单表操作为主的系统非常合适能把大量样板代码省掉。相比之下Spring Data JPA 写复杂统计 SQL 反而别扭MyBatis Plus 在这个场景下的体验更好。前端单独拆出来用 Vue是因为统计报表这种模块如果用模板引擎渲染图表部分会做得非常痛苦。Vue 配合 ECharts把后端接口返回的 JSON 数据直接丢给图表组件几乎不费力气就能做出很漂亮的月度收支趋势图和分类占比饼图。对于毕设来说视觉冲击力很重要这种前端方案的演示效果远超服务端模板渲染。数据库用 MySQL 5.7 或 8.0 都行没有太挑剔的地方。如果你的电脑内存紧张同一个项目里用 Docker 把 MySQL 和 Redis 都拉起来跑也算是一个面对“系统架构”问题时的加分项。不过 Redis 在记账系统里不是必需品后面我详细说什么时候该用它。2. 系统架构与数据库设计2.1 后端分层结构与包管理个人记账系统的后端代码结构我习惯按功能模块分包而不按技术层分包。按技术层分包就是controller、service、mapper、entity每层一个包把所有类按层级堆在一起这个结构做小demo可以但项目一旦有多个业务模块后边找代码会很难受维护感觉一团糟。按功能模块分包会把结构组织成user、transaction、category、budget、statistics这种包每个包里面自己放 controller、service、mapper、entity。这样每加一个功能直接在对应模块包下加文件就行逻辑清晰各模块之间相互独立。实测下来这种结构对于展现代码组织能力在答辩时是很加分的一点。一个典型的后端包结构如下src/main/java/com/example/ledger/ ├── common/ // 通用模块统一返回结果、异常处理、校验封装 │ ├── Result.java │ ├── PageResult.java │ └── GlobalExceptionHandler.java ├── config/ // 配置类MyBatis Plus、跨域、拦截器等 │ ├── MybatisPlusConfig.java │ ├── CorsConfig.java │ └── WebMvcConfig.java ├── security/ // 认证相关JWT工具、拦截器、登录上下文 │ ├── JwtUtil.java │ ├── AuthInterceptor.java │ └── UserContext.java ├── module/ │ ├── user/ // 用户模块 │ ├── transaction/ // 账目流水模块 │ ├── category/ // 分类模块 │ ├── budget/ // 预算模块 │ └── statistics/ // 统计模块 └── LedgerApplication.java个人记账的核心业务是账单流水它依赖用户和分类这两个基础模块。统计模块又是一个典型的多表关联聚合场景放在单独模块里可以让 controller 层保持清爽也方便把统计相关的复杂 SQL 单独管理。2.2 数据库表设计与业务关系记账系统的表不需要设计得非常复杂但要认真考虑到业务关系。我通常会在一个完整项目中设计这些表用户表user、账户表account、分类表category、账目流水表transaction、预算表budget。用户表是所有数据的归属源头。设计时需要注意区分用户自身字段和统计字段避免把这些混在同一张表里。userId 在后续的所有表中都要出现作为业务数据归属的边界。账户表是用来管理用户的多个资金账户的。比如你可能有一个支付宝账号、一个微信钱包、一张银行卡如果只在账单表里存一个账户名的字符串后续要对某一笔钱做账户间的资金转移就会很麻烦。账户表把用户、初始余额、当前余额、账户类型都单独管理起来记账时会同步修改账户余额这样才更像一个真正可用的记账软件。分类表需要区分这是系统预置分类还是用户自定义分类。通过类型字段type区分收入和支出用户在新增账目时调用分类接口时前端直接按收支类型展示对应分类。这里有个实操经验预置分类要内置常见的生活分类比如餐饮住宿、交通出行、购物消费、休闲娱乐和人情社交。这样用户注册进来以后不用手动添加分类就能直接记账体验是顺畅的。账目流水表是系统的核心业务表。关键字段包括用户、账户、分类、类型收入或支出、金额、交易日期、备注。追加逻辑删除字段防止用户误删账单后数据彻底不可恢复。这里要注意容我用表格列出流水表的关键字段写清楚字段类型与逻辑含义你们在实际建表时可以直接参考字段名类型默认值/约束说明idbigint主键自增流水IDuser_idbigint索引所属用户account_idbigint索引所属账户category_idbigint索引所属分类typetinyint1收入/2支出收支类型冗余方便统计amountdecimal(10,2)非负金额BigDecimal映射occur_datedate当前日期消费发生日期remarkvarchar(255)可空备注描述deletedtinyint逻辑删除标记逻辑删除create_timedatetime自动填充创建时间预算表建议按月份维度设置限制例如对某个月份的总支出做上限。这里保存用户设定的预算金额、所属月份和用户ID。当一个月的支出累计已经超过预算金额时统计模块会在月末或者查询接口里给出预算警告信息。有的实现会加上 category_id 字段支持对某一类支出单独做预算但这就超出多数毕设的业务需求了反而会把预算逻辑复杂化。我遇到过不少同学在单独的数据库设计上不够重视直接用一个ledger表撑着整个项目。这样做不是说绝对不行但一旦答辩老师问这几个问题就很容易被挑战用户和账目怎么分开多个账户余额怎么处理预算数据存哪里因此还是不要偷懒把表结构一次设计到位。2.3 认证授权方案记账系统是有用户体系的所以安全认证是少不了的。个人记账系统的量级用 JWT 这种无状态令牌就非常合适不需要像 Session 那样依赖服务端保存状态也不需要在 Redis 里维护会话数据。用户登录成功后后端生成一个 JWT 令牌返回给前端前端后续请求在请求头上带上这个令牌后端通过拦截器解析令牌识别当前用户。如果想要更强的安全控制可以在服务端增加一个令牌失效时间或黑名单方案。但就毕设项目来说把这些做扎实已经足够。JWT 的核心原理是把用户ID、过期时间等数据通过签名算法打包成一个字符串服务端不需要存储这个令牌只要密钥不泄露就能通过验签确定请求来源可靠。具体依赖和代码我放在第4章这里先解释清楚设计意图。在拦截器里我将当前登录用户的ID解析后放进请求上下文的 ThreadLocal 中这样 controller 和 service 就能通过 UserContext 拿到当前用户避免每次从请求头里手动解析。为什么一定要这么设计记账系统的所有业务数据都按用户隔离假如一个用户看到的账单混入了另一个用户的数据这类数据越权问题在答辩中属于数据安全的硬伤。JWT加拦截器可以在网关层统一校验身份并要求查询条件中带上用户ID作为过滤条件数据读到哪个范围由后端统一控制防止传入任意ID就能看到别人的账单。3. 核心功能模块实现3.1 记账核心流程与代码实现记账是整个系统的核心交互。用户选择一个账户、选择收支类型和分类、输入金额、日期和备注提交后在后端要做的事情其实不少校验分类是否属于当前用户、构造流水记录并保存、同步更新账户余额、可能还要更新统计缓存。这里我展开说流水新增的实现思路。先看 Service 层的核心逻辑轮廓细节我尽量保留在一个比较规范的写法中Service RequiredArgsConstructor public class TransactionServiceImpl implements TransactionService { private final TransactionMapper transactionMapper; private final AccountMapper accountMapper; private final CategoryMapper categoryMapper; Override Transactional(rollbackFor Exception.class) public Long addTransaction(TransactionAddDTO dto) { Long userId UserContext.getUserId(); // 1. 校验分类归属必须存在且属于当前用户或预置分类 Category category categoryMapper.selectById(dto.getCategoryId()); if (category null || !category.getUserId().equals(userId) !category.getPresetFlag()) { throw new BizException(分类不存在); } // 2. 构建账单实体 Transaction transaction new Transaction(); transaction.setUserId(userId); BeanUtils.copyProperties(dto, transaction); // 3. 保存流水 transactionMapper.insert(transaction); // 4. 同步账户余额 updateAccountBalance(userId, dto.getAccountId(), dto.getType(), dto.getAmount()); return transaction.getId(); } }这段代码里有几个值得说明的细节。第一个细节是Transactional事务注解。新增流水、更新账户余额是两个写操作任何一个失败都应该让整个操作回滚否则就会出现账单记录了但余额没变、或者余额变了但账单没记录的数据不一致问题。这种一致性问题如果在上线后被用户发现那是对系统可靠性的巨大打击所以事务在这里是必需的。第二个细节是amount字段接收的类型。DTO 里金额字段对应的类型是BigDecimal前端传过来的 JSON 数据也是字符串数字Jackson 会自动转换成BigDecimal。如果有同学为了省事用了double接收财务数据在浮点运算中可能出现精度误差这一点务必定位清楚。第三个问题是实时更新账户余额。做一个记账系统账户当前余额是用户很关注的数据所以我建议在新增账单时同时维护 account 表的当前余额字段。这里针对性地说一个设计考量采用实时同步的方式代码复杂度小所以优先推荐如果后续数据量巨大想换成异步计算可以再引入消息队列、或定时任务重算但对于毕设和个人项目而言属于过度设计。3.2 分类管理与列表查询个人记账系统的分类功能并不复杂但它在代码组织上非常能体现分层的功底。分类模块的接口一般设计为提供一个查询分类列表的接口参数传入 type收入或支出返回当前用户自定义分类加上系统预置分类一个新增分类接口一个删除分类接口。删除分类有一个潜在问题当分类已被某笔账单使用时如果直接删除分类被动删除后账单详情就会变得不可读。我的做法是逻辑删除分类并在删除时查询 transaction 表里是否有引用记录若有引用就提示用户“该分类已被账单使用建议修改账单分类后再删除或改用停用功能”。这个产品细节拿出来在很多毕业设计里都是亮点说明你思考过真实场景而不是只管实现。账单列表查询则是一个典型的动态 SQL 场景支持按日期范围、类型、分类ID进行组合过滤。如果是用 XML 封装 SQL就可以比对一下自己手写 SQL 与使用 QueryWrapper 的差异。但无论用哪种方式关键过滤条件都需要加入 user_id作为数据越权的底线守卫。这里直接给一个 MyBatis Plus QueryWrapper 的写法public PageResultTransactionVO pageQuery(TransactionQueryDTO query) { LambdaQueryWrapperTransaction wrapper new LambdaQueryWrapper(); wrapper.eq(Transaction::getUserId, UserContext.getUserId()); wrapper.eq(query.getType() ! null, Transaction::getType, query.getType()); wrapper.eq(query.getCategoryId() ! null, Transaction::getCategoryId, query.getCategoryId()); wrapper.ge(query.getStartDate() ! null, Transaction::getOccurDate, query.getStartDate()); wrapper.le(query.getEndDate() ! null, Transaction::getOccurDate, query.getEndDate()); wrapper.orderByDesc(Transaction::getOccurDate); PageTransaction page transactionMapper.selectPage(new Page(query.getPageNum(), query.getPageSize()), wrapper); // 额外从分类表和账户表补全名称信息组装成VO返回 }很多新手会踩这么个坑需要在列表里展示分类名称和账户名但因为这些是独立表自己的交易表里只存了ID于是就开始担心 SQL 联表的问题。其实完全不必只局限于手写 LEFT JOIN SQL 一条路。用 MyBatis Plus 查询后在一个循环里根据分类ID去批量查出分类名称并填入结果集也能实现同样的效果只需要防止 N1 查询可以用一次性查全部ID列表的selectBatchIds处理。分页场景下查询压力很小这种方式可以让代码库比大段 XML SQL 整洁很多。3.3 统计数据如何算出来统计模块是记账系统里技术含量比较高的一个部分。常见的统计目标有三种按月统计每日支出画趋势折线图按分类统计支出金额算占比画饼图本月累计支出与预算对比。后端把各个统计的 SQL 写出来结果集直接映射成前端所需的 JSON 结构即可。按月按月统计每日支出在 MySQL 里用日期函数可以做成这样public ListDailyAmountVO getDailySummary(Long userId, String month) { // 例如这个实现用MyBatis注解SQL ListDailyAmountVO list transactionMapper.selectDailySummary(userId, month); return list; }select idselectDailySummary resultTypecom.example.ledger.module.statistics.vo.DailyAmountVO SELECT DATE_FORMAT(occur_date, %Y-%m-%d) AS date, SUM(CASE WHEN type 2 THEN amount ELSE 0 END) AS expenseAmount, SUM(CASE WHEN type 1 THEN amount ELSE 0 END) AS incomeAmount FROM transaction WHERE deleted 0 AND user_id #{userId} AND DATE_FORMAT(occur_date, %Y-%m) #{month} GROUP BY DATE_FORMAT(occur_date, %Y-%m-%d) ORDER BY date /select我还是要重点提醒你关于类型映射问题。type字段如果定义成tinyint在 Java 里用Integer接收加载到CASE WHEN type 2这种判断里没有问题。前提是你建立的实体类字段没有用Boolean type去接收在实际中确实有同学因为数据库里字段名称叫type就误映射为布尔类型导致收入支出全乱了套。写这个小建议是希望大家建立库表之前先确认字段语义和 Java 类型的配对没问题。按分类统计的逻辑和按日统计是同一套路只是分组字段变成category_id再关联分类表把名称取出来。重点关注应该放在如何让前端收到一个干净的统计接口返回值上。考虑到展示的需要我比较推荐接口直接返回[ { categoryName: 餐饮, amount: 1250.50, percentage: 34.2 }, { categoryName: 交通, amount: 560.00, percentage: 15.3 } ]percentage 由后端计算好还是前端计算这里我倾向于后端计算。因为后端可以直接从同一份数据里得到总额并计算出各项占比前端不需要做二次计算减少出错环节。如果你不嫌麻烦也可以后端只返回原始金额列表前端用 ECharts 的饼图功能自己计算占比。从答辩的稳定性来讲后端返回完整的统计结构会更稳。预算提醒的逻辑不复杂在统计模块加一个接口查询当前月份预算金额、当前支出总额、已用比例。当已用比例达到 80%、100% 时在前端给出不同颜色或提示语的预算条。这里的业务判断放前端更合适因为后端只需要如实返回数据和阈值配置就行。我自己的实现里会加一个budgetWarningThreshold配置项默认 80%放在application.yml中这样调整阈值不需要改代码也算一个小亮点。4. 项目搭建与实操过程4.1 IDEA快速初始化Spring Boot项目实际动手搭建项目时第一步是用 IDEA 创建一个 Spring Boot 工程。如果你是第一次上手建议通过 IDEA 自带的 Spring Initializr 向导创建而不是手动去官网下载压缩包。打开 IDEA选择 New Project左侧选择 Spring Initializr填好项目名和包名依赖那里勾选 Spring Web、MySQL Driver、Lombok。Spring Boot 的版本建议直接选择 2.7.x 或 3.x 里自己熟悉的一个稳定版本。具体踩到什么坑咱们放在第5章细说我先强调一点3.x 版本的 Servlet API 有些变化部分旧教程里的写法不兼容如果你是跟着教程步骤一步步走的尽量找一个对应版本的整套教程。网上找资料时留意一下发布时间的匹配。创建好项目以后需要手动往 pom 文件里加入 MyBatis Plus、JWT 和 Hutool 的依赖。MyBatis Plus 在 Spring Boot 3 需要使用适配的 starter 依赖这里示例给 Spring Boot 2.7 下的写法。dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency dependency groupIdcn.hutool/groupId artifactIdhutool-all/artifactId version5.8.16/version /dependencyHutool 是一个工具包里面包含了日期处理、ID生成、PBE加密等非常好用的能力。记账系统中的密码存储可以用 Hutool 的 SHA256 加盐方式来完成避免自己重复造轮子写加密工具类。4.2 核心配置与通用类编写集成好依赖之后就是抓配置环节我把这些配置项全部写在application.yml中server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ledger?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 jwt: secret: your-secret-key-please-change-in-production expire-hours: 168这段配置有几个意图明确值得记一下。map-underscore-to-camel-case让数据库的occur_date自动映射到实体类的occurDate字段省去大量字段映射配置。逻辑删除的配置很重要它使MyBatis Plus在删除时自动把deleted改为1而不是物理删除。建议所有带删除标记的业务表都启用它。serverTimezoneAsia/Shanghai用于处理 MySQL 连接时的时区问题这也是新手最容易碰到又看不出原因的坑点之一。再写一个通用返回结果类ResultT。这个类统一封装接口返回值的状态码、提示信息和数据让前端对接口返回结构有统一预期。看起来是小事但在前后端联调时能省很多沟通成本。Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(操作成功); result.setData(data); return result; } public static T ResultT error(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } }然后再配一个全局异常处理器把业务异常和系统异常统一转换为Result.error返回避免后台直接把堆栈信息抛给前端。答辩时上面这两个类虽然很基础却能看出你具备工程规范意识。4.3 前端基础搭建与接口对接前端我用 Vue3 Vite Element Plus 来搭。创建工程用 Vite 几步就完成然后在 main.js 里引入 Element Plus 和 ECharts。页面结构方面布局上左侧是侧边导航里面有总览、账目明细、分类管理、预算设置这几个菜单右侧是内容区。这里把路由结构、页面组件拆分说一下因为有序组织前端模块和后面的接口对接直接相关。路由组件规划大概如下src/ ├── api/ │ ├── auth.js │ ├── transaction.js │ ├── category.js │ └── statistics.js ├── views/ │ ├── Login.vue │ ├── Register.vue │ ├── Dashboard.vue │ ├── TransactionList.vue │ ├── TransactionEdit.vue │ └── BudgetSetting.vue ├── components/ │ └── EChartBox.vue └── router/index.js前后端联调的关键接口设计遵循 RESTful 风格。例如查询账单列表的接口定义是GET /api/transaction/page?pageNum1pageSize10type2startDate2024-11-01新增账单是POST /api/transaction统计接口是GET /api/statistics/daily?month2024-11和GET /api/statistics/category?type2month2024-11。每个请求都在 axios 拦截器里自动附带Authorization: Bearer token头返回状态码不是 200 时统一弹出错误消息。这样把通用逻辑收敛在请求封装里后续每个页面的代码会干净很多。实际联调建议先跑通一个清单注册新用户、登录、新增收入与支出、列表展示、统计图表展示这条链路上集成了用户、认证、核心流水、统计所有模块。只要这条闭环通过再补充分类管理和预算设置的联动就轻松多了。4.4 项目打包部署与演示准备本地完成开发后毕设最好准备一个可以随手演示的部署环境。后端打包用 Maven 执行package生成一个 jar 包然后在服务器或者本地用java -jar ledger-0.0.1.jar启动。如果部署在 Linux 服务器上配合nohup后台运行前后端分离的话前端打包后放在 Nginx 或者直接用serve托管都行。这一步在毕设的演示环节是一个小的加分项因为你能讲清楚生产环境下的部署流程而不仅是会启动 IDEA 点个运行按钮。演示环境搭好之后我强烈建议准备一套演示数据。比如在数据库里预置用户demo/123456给这个用户造过去几个月的账单流水数据这样打开总览页就能直接看到统计图表和预算条的效果而不是对着一个空页面尴尬地现场录入。这是一个很容易被忽视的小准备但能给答辩老师留下很好的印象。5. 常见问题与避坑实录5.1 高频启动与运行报错这类项目最容易遇到的坑集中在环境层面而不是业务代码本身。这里我把带过的学生里出现频率最高的几个问题列成一张速查表你们下次再遇到可以直接对照解决。现象原因解决方式启动报Failed to configure a DataSource没有配置数据源或 yml 文件名拼错确认application.yml在 resources 目录下且包含 spring.datasource 配置接口日志查询正常但列表为空逻辑删除未配置或数据中 deleted 字段不是 0检查 MyBatis Plus 逻辑删除配置确认未删除数据的 deleted 为 0中文乱码数据库连接未指定编码或表字符集不是 utf8mb4连接串加characterEncodingutf8库和表统一 utf8mb4前端请求接口 404后端接口没写前缀/api或路由不匹配统一在 controller 的 RequestMapping 里加/api前缀跨域报错前端地址与后端端口不一致配置 CORS 过滤器允许前端地址跨域访问启动时端口被占用上一个实例未关闭用lsof -i:8080查找占用进程并结束这些问题的排查思路基本都是先看控制台日志定位到明确报错再顺着堆栈找根因。很多人慌是因为看到一大堆异常就翻网上的零散答案其实大部分问题都是环境或配置层面对照表格解决即可。不要一上来就怀疑是自己代码写错了先排查这些项目基础环节反而更快。5.2 金额与日期处理经验资金项目最需要审慎对待的是精度和时区。数据库amount字段要使用decimal(10,2)Java 侧用BigDecimal接收和参与计算避免double。如果你用了double去做浮点累加当出现金额带着很长的小数尾巴的时候后端再传给前端就会出问题。一旦被问到精度问题而自己却没有处理意识的同学当场很难给出一个建设性的解释。所以无论接口入参还是数据库建表都建议坚持这条规范。日期处理方面记录日期occur_date用date类型设置日期格式在 Jackson 中统一序列化为字符串。前端传日期一般直接用YYYY-MM-DD字符串Spring 接收时的转换不需要额外配置。这里有一个比较隐蔽的问题是 MySQL 的时区如果你的数据库连接串没指定serverTimezone在用DATE_FORMAT做统计时可能出现小时偏移。建议在数据源连接串上直接写死serverTimezoneAsia/Shanghai就省去了这个问题。5.3 源码二次开发与答辩准备如果拿到了别人的毕设源码不要直接运行第一件事是修改配置文件里的数据库密码和 JWT 密钥第二件事是确认数据库版本与 SQL 脚本匹配。在二次开发前可以先花半小时把项目跑起来并把代码结构过一遍。理一下 controller 层的接口清单再到 service 层看核心逻辑理解数据流转的顺序。这样做的好处是后续你想加新功能时只在对应模块扩展就行。答辩的提问经常落在几个点上为什么用这个框架表结构为什么这么设计某个功能怎么实现的预算提醒逻辑是前端还是后端统计 SQL 怎么写你写过的每个模块都能拿代码和数据库解释清楚这比死记硬背八股文有含金量得多。还有一个答辩经验准备回答“你这个项目还有什么可以改进”这类问题时可以从这里挑一个合理的点展开。比如引入 Redis 做热点数据的缓存、引入 RabbitMQ 让统计模块做成异步处理、用 Docker Compose 一键编排部署环境、给“账户余额变动”加一条资金流水记录做对账。说一两条自己真正了解的技术点远比泛泛说“以后会更好”要站得住脚。写在最后的几个实操心得带过好几届毕设也被人问过无数次“网上源码这么多为什么还要自己写一遍”。我的实际感受是直接拿源码交给老师的学生答辩时往往连项目里用到的注解都解释不清。反倒是从零写过一遍的同学代码水平未必多高但对自己做的系统非常有底。个人记账系统这个题目最好的地方就在于麻雀虽小五脏俱全用户、流水、分类、统计、预算几个模块串下来Spring Boot 常见的东西都碰到了。如果时间只够做好一件事我的建议是先把统计模块打磨好。记账系统最直观的出彩点就是图表和报表后端把统计 SQL 写明白前端画两根漂亮的折线图整个项目的水准立刻不一样。如果后续想继续扩展这个项目可以尝试引入一个小程序端或移动端复用现在写的这套后端接口也能体会一套接口多端共用的设计思路。做项目这件事把基础链路走通做好就比单纯堆功能有价值得多。
返回列表