最近在辅导学弟学妹做毕业设计发现不少同学都选择了“网上记账系统”这个题目。想法很好但实际动手时问题就来了功能堆砌导致代码混乱、用户密码明文存储、多笔转账记录金额对不上……这些问题不仅影响项目质量更让答辩时的演示充满风险。今天我就结合自己踩过的坑分享一下如何从零开始构建一个结构清晰、安全可靠的Java网上记账系统希望能给正在为毕设发愁的你一些实实在在的帮助。1. 新手常踩的坑从“功能实现”到“工程思维”很多同学一开始就急着写代码忽略了系统设计导致后期问题频发。这里梳理几个最常见的误区SQL注入与字符串拼接这是最危险也最容易被忽略的点。很多新手为了图省事在DAO层直接用字符串拼接SQL比如SELECT * FROM bill WHERE user_id userId。如果userId来自前端输入且未经验证攻击者就能轻易注入恶意SQL窃取或破坏数据。务必使用预编译PreparedStatement或MyBatis的#{}语法这是底线。无状态会话管理记账系统肯定需要登录。有的同学把用户ID直接存在前端的Cookie或LocalStorage里或者用Servlet的HttpSession但不做任何安全处理。这会导致会话固定攻击、CSRF攻击等风险。我们需要一个无状态、可验证的令牌机制JWTJSON Web Token是目前最主流和简单的方案。事务缺失与数据不一致这是业务逻辑的核心难点。举个例子用户“转账”功能涉及从一个账户扣款和向另一个账户加款两个操作。如果只成功了一个数据就“脏”了。很多同学会忘记给这两个数据库操作加上事务Transaction控制或者错误地设置了事务边界比如在Service层每个DAO方法单独开事务。正确的做法是在Service层的业务方法上使用Transactional注解确保这两个操作要么全成功要么全回滚。密码明文存储我见过有同学直接把用户密码用MD5加密一下就存数据库了还觉得挺安全。实际上MD5早已被破解彩虹表一查就出来。存储用户敏感信息必须使用加盐Salt的强哈希算法比如BCrypt它是专门为密码存储设计的每次加密结果都不同且计算缓慢能有效抵御暴力破解。2. 技术选型为什么是Spring Boot MyBatis MySQL面对众多的Java框架新手容易眼花缭乱。我的选择是Spring Boot vs 传统SSMSpringSpringMVCMyBatis传统SSM需要大量XML配置整合过程繁琐容易在依赖和配置上浪费大量时间。Spring Boot的核心优势是“约定大于配置”和“自动装配”。它内嵌了Tomcat服务器一个main方法就能启动项目极大地简化了搭建和部署流程让我们能更专注于业务逻辑开发。对于毕设这种追求快速成型和演示的项目Spring Boot是不二之选。MyBatis vs JPA (Hibernate)两者都是优秀的持久层框架。JPA更“面向对象”通过操作实体类就能间接操作数据库但学习曲线稍陡且复杂查询的优化需要更多经验。MyBatis更“面向SQL”它把SQL语句写在XML或注解里开发者对SQL有完全的控制力对于需要复杂联表查询、动态SQL的记账系统来说MyBatis更加直观和灵活也更容易调试和优化。对于新手能清晰看到自己写的SQL理解更深也更容易排查问题。基础依赖pom.xml关键部分dependencies !-- Web支持 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatis整合 -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.2.2/version /dependency !-- MySQL驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency !-- 密码加密 -- dependency groupIdorg.springframework.security/groupId artifactIdspring-security-crypto/artifactId /dependency !-- JWT支持 -- dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.9.1/version /dependency /dependencies3. 核心实现拆解模块步步为营一个清晰的记账系统可以划分为几个核心模块用户认证、账单管理、分类管理、数据统计。我们重点看前两个。3.1 用户登录与鉴权JWT实践核心思路用户登录成功后服务器生成一个JWT令牌返回给前端。前端后续请求都在HTTP Header中携带此令牌通常放在Authorization: Bearer token服务器通过过滤器Filter或拦截器Interceptor验证令牌的有效性。实体类设计User包含id,username,password(加密后),email等字段。密码加密工具类使用Spring Security提供的BCryptPasswordEncoder。JWT工具类负责生成和解析Token。登录接口AuthControllerRestController RequestMapping(/api/auth) public class AuthController { Autowired private UserService userService; Autowired private JwtTokenUtil jwtTokenUtil; PostMapping(/login) public Result login(RequestBody LoginRequest request) { // 1. 根据用户名查询用户 User user userService.findByUsername(request.getUsername()); if (user null) { return Result.error(用户名或密码错误); } // 2. 校验密码 (BCrypt匹配) if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) { return Result.error(用户名或密码错误); } // 3. 生成JWT Token String token jwtTokenUtil.generateToken(user.getUsername()); // 4. 返回Token及用户基本信息避免返回密码 LoginResponse response new LoginResponse(); response.setToken(token); response.setUserInfo(...); // 用户脱敏信息 return Result.success(登录成功, response); } }认证过滤器JwtAuthenticationFilter继承OncePerRequestFilter在doFilterInternal方法中从Header取出Token用JWT工具类验证如果有效则将用户信息存入SecurityContext或Request属性供后续业务使用。3.2 账单的CRUD与事务管理账单Bill是核心实体包含id,userId,amount(金额),type(收入/支出),categoryId(分类),remark(备注),createTime等字段。Service层的事务控制这是保证数据一致性的关键。以“新增账单”为例它可能涉及更新用户的账户总额。这两个操作必须在同一个事务中。Service public class BillServiceImpl implements BillService { Autowired private BillMapper billMapper; Autowired private UserAccountService accountService; Override Transactional(rollbackFor Exception.class) // 声明事务任何异常都回滚 public boolean addBill(Bill bill) { // 1. 插入账单记录 int insertCount billMapper.insert(bill); if (insertCount 0) { return false; } // 2. 更新用户账户总额增加或减少 boolean updateSuccess accountService.updateBalance(bill.getUserId(), bill.getAmount(), bill.getType()); if (!updateSuccess) { // 如果更新失败会抛出异常触发上面Transactional的回滚 throw new RuntimeException(更新账户余额失败); } return true; } }注意Transactional默认只对运行时异常RuntimeException回滚检查型异常Exception不回滚。加上rollbackFor Exception.class确保所有异常都触发回滚。金额汇总逻辑统计每日/每月收支。在MyBatis的Mapper XML中使用SUM和CASE WHEN可以高效完成。!-- 查询某用户某月的收支统计 -- select idselectMonthlySummary resultTypemap SELECT DATE_FORMAT(create_time, %Y-%m) as month, SUM(CASE WHEN type INCOME THEN amount ELSE 0 END) as totalIncome, SUM(CASE WHEN type EXPENSE THEN amount ELSE 0 END) as totalExpense FROM bill WHERE user_id #{userId} AND create_time BETWEEN #{startDate} AND #{endDate} GROUP BY DATE_FORMAT(create_time, %Y-%m) /select4. 安全与性能让系统更健壮密码加密BCrypt如前所述在用户注册或修改密码时必须用BCryptPasswordEncoder加密后再存储。Service public class UserServiceImpl implements UserService { Autowired private BCryptPasswordEncoder passwordEncoder; public void register(User user) { String encodedPwd passwordEncoder.encode(user.getPassword()); user.setPassword(encodedPwd); userMapper.insert(user); } }防重复提交接口幂等性对于“创建账单”这类非幂等操作前端防抖Debounce是基础后端可以加一层简单的令牌机制。例如登录后下发一个“提交令牌submitToken”创建请求时必须携带此令牌服务器校验并使用后即失效防止短时间内同一数据重复提交。简单的性能考量数据库索引为bill表的user_id和create_time字段加上复合索引能极大提升按用户和时间范围查询账单的速度。SQL优化避免SELECT *只查询需要的字段。复杂统计查询可以考虑定期跑Job将结果存入汇总表用空间换时间。基础压测使用JMeter或Postman对“查询账单列表”接口做个简单并发测试比如50个线程循环100次观察响应时间和错误率确保在毕设演示的常规压力下表现稳定。5. 生产环境避坑指南毕设也能用上即使只是毕设养成好习惯也能让项目更规范答辩更出彩。数据库字段精度陷阱金额字段务必使用DECIMAL(19, 4)这种类型19表示总位数4表示小数点后4位。千万不要用FLOAT或DOUBLE它们存在精度丢失问题在财务计算中是致命的。时间与时区处理在Java实体类中使用java.time.LocalDateTimeJava 8替代旧的Date。在数据库连接字符串中明确指定时区jdbc:mysql://localhost:3306/accounting?serverTimezoneAsia/ShanghaicharacterEncodingutf8。存储时统一用UTC时间或服务器本地时间并在返回给前端时做好转换。日志脱敏在打印日志时敏感信息如用户手机号、身份证号、密码等必须脱敏。可以借助Logback或Log4j2的PatternLayout自定义转换器将匹配到的敏感字段替换为***。统一的响应封装所有Controller接口返回的数据都应该包裹在一个统一的Result对象里包含code(状态码),message(消息),data(数据)。这有利于前端统一处理也方便在全局异常处理器ControllerAdvice中捕获异常并返回友好的错误信息。API文档使用Swagger或Knife4j自动生成API文档。这不仅是给前端同学看的更是你答辩时展示项目规范性的利器老师看了也会觉得你很专业。写在最后按照上面的思路和关键点去实现一个具备基本安全性和健壮性的网上记账系统就初具雏形了。这个过程最重要的是理解为什么要这么做而不仅仅是复制代码。比如为什么用JWT为什么加Transactional理解了背后的原理你才能举一反三。这个单用户系统完成后你可以思考一个更有挑战性的扩展方向如何将它改造成一个多用户共享的账本例如家庭账本或团队活动AA记账这涉及到更复杂的权限模型RBAC、账本成员管理、实时通知WebSocket等技术点。尝试去设计一下数据库表结构和接口相信会让你对分布式系统有更深的认识。希望这篇笔记能为你扫清一些障碍。编程最好的学习方式就是动手赶紧打开IDE开始构建你自己的记账系统吧遇到问题多查文档多调试每一次解决问题的过程都是宝贵的经验。祝你毕设顺利