news 2026/10/22 4:52:54

Spring Boot财务管理系统实战:从需求拆解到部署优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot财务管理系统实战:从需求拆解到部署优化

做财务管理系统,Spring Boot 这套技术栈几乎是目前的主流选择,不管是拿来练手、做毕业设计,还是给中小型公司内部用,都能撑得起业务。我之前用 Spring Boot 完整做过一个财务管理系统,从需求拆解、表结构设计到权限控制和报表展示,一路踩了不少坑,也沉淀了不少经验。这篇文章就把整个项目的完整思路、核心模块的搭建过程,以及我在实践中遇到的典型问题一次讲清楚,希望给正在做同类系统的朋友一个可以直接“抄作业”的参考。

1. 项目全景与需求拆解

1.1 这个财务系统到底在做什么

先明确一点:财务管理系统这个名字听起来很宽泛,但在实际项目里,它的核心永远围绕“钱从哪里来、钱花到哪里去、账上还剩多少”这三件事展开。市面上成熟的产品如用友、金蝶,功能动辄几百个模块,但一个自研系统如果从一开始就把范围铺太大,十有八九会烂尾。

我做的这个基于 Spring Boot 的财务管理系统,定位非常明确:面向中小型企业的日常收支记账、费用审批、统计报表和基础用户管理。它要解决的核心痛点有三个:

  • 手工记账效率低,数据散落在 Excel 和纸质单据里,月底对账全靠翻聊天记录。
  • 缺乏权限控制,财务数据和普通业务数据混在一起,谁都能打开看,存在安全隐患。
  • 报表输出困难,老板要看月度收支情况,财务人员要花一两天整理数据才能出图。

针对这些痛点,我把系统拆成四大模块:用户与权限模块、收支管理模块、预算管理模块、统计报表模块。单看模块数量不多,但每个模块内部都有足够的业务深度,既保证了系统能真正在业务中落地使用,也避免了功能点过于单薄的问题。

1.2 拆解需求时容易忽略的细节

做这类业务系统,最忌讳的就是拿到标题直接开写代码。我第一版就吃了这个亏,上来先建表,结果做到预算模块时发现收支表里没设计“关联单据号”字段,后面所有对账逻辑都得返工。所以这里必须把需求拆解落到纸面上。

我把核心需求整理成了一张覆盖完整业务闭环的表格:

模块核心业务关键字段面向角色
用户与权限登录认证、角色控制、菜单管理用户名、密码、角色ID系统管理员
收支管理收入登记、支出报销、转账记录金额、类型、分类、经手人财务、员工
预算管理预算编制、执行预警、超支控制预算额度、已用额度、周期财务主管
统计报表月度收支、分类汇总、利润分析时间范围、统计维度老板、财务

做这张表的时候,有几个细节值得特别注意:一是“金额”字段,业务上永远用分作为单位存整数,避免浮点误差;二是“操作时间”和“业务时间”要分开,业务时间才是账务归属期;三是“创建人”和“审核人”不能混为一个字段,财务系统天然需要复核流程。这些细节如果需求阶段不确认清楚,后期改动成本很高。

1.3 项目编号 project50026 的管理意义

这个项目编号本身没什么技术含量,但我建议自研系统从一开始就建立清晰的项目管理意识。我当时把所有文档、数据库脚本、接口文档都统一加了版本号,用编号关联需求变更记录。曾经有一次接口参数调整,就是因为文档编号模糊,导致前端和后端各改各的,联调时才发现对不上,白白浪费了两天时间。你把项目编号、模块编号、文档编号管理清楚,后面排查问题和迭代维护会省非常多的心力。

2. 技术选型与系统架构

2.1 为什么是 Spring Boot 而不是更老的技术栈

有人会问,现在微服务那么火,怎么不用 Spring Cloud?我的判断是:系统的复杂度决定技术选型,而不是反过来。对于一个财务管理系统,单体应用完全能够承载业务需求,强行上微服务只会增加部署和运维成本。Spring Boot 在这个场景下有几个天然优势:

  • 开箱即用:内置 Tomcat,一个 JAR 包就能跑起来,配合 Maven 或 Gradle 管理依赖非常省心。
  • 生态成熟:Spring Security、Spring Data JPA、MyBatis-Plus 这些开源组件都是生产级验证过的,拿来即用。
  • 社区资料多:项目中出现任何报错,几乎都能搜到现成的解决方案,这对单人开发或小团队开发至关重要。
  • 快速迭代:Spring Boot 的自动配置机制让开发环境搭建从小时级缩短到分钟级,配合 DevTools 还能实现热部署,改完代码不用频繁重启。

我当时也纠结过是不是用更轻量的 Flask 或 Node.js,但考虑到涉及复杂的权限模型、动态报表查询以及团队后续的技术维护习惯,还是选择了 Spring Boot。事实证明这个选择是对的,尤其是后期接入 Quartz 定时任务做月度自动结算时,Spring 体系的支持非常顺手。

2.2 核心依赖选型与理由

技术选型不只是选一个框架,整个技术栈里的每一个组件都要能说出选它的理由。我用的是 JDK 8 + Spring Boot 2.7.x 组合,既避开了 JDK 太高版本可能带来的兼容问题,又能完整使用 Spring Security 5.7 的特性。具体依赖如下:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>

数据库我选了 MySQL 8.0,原因很直接:InnoDB 引擎对事务支持好,财务数据最怕的就是“写了一半失败”导致账目不平;MySQL 的物化视图和 JSON 函数虽然不如 PostgreSQL 丰富,但对这个项目的查询需求绰绰有余。ORM 层面选择了 MyBatis-Plus,因为它提供了条件构造器,写动态 SQL 很方便,尤其适合财务管理里大量的多条件组合查询场景。

2.3 分层架构设计

系统的代码结构我采用了经典的三层架构,但没有盲目包一层 Controller 就万事大吉。下面是我的分层思路:

com.example.finance ├── controller # 接口层,只做参数接收和结果封装 ├── service # 业务层,核心逻辑全部在这里 ├── mapper # 数据访问层,MyBatis-Plus 的 Mapper 接口 ├── entity # 对应数据库表的实体类 ├── dto # 前后端交互的数据传输对象 ├── vo # 视图对象,专门给前端展示用 ├── config # 配置类(Security、Cors、MyBatisPlus 等) ├── handler # 全局异常处理器 └── utils # 工具类(JWT 工具、Excel 导出工具等)

这个分层里最值得说的是DTO 和 VO 的分离。很多项目图省事,直接用 Entity 返回给前端,结果把数据库字段暴露得干干净净。比如用户表里的密码哈希值、内部备注字段,全都传给了浏览器。我做财务系统时严格区分了 DTO 和 VO:前端需要的字段一个不少,不该暴露的字段一个不漏。用 BeanUtils.copyProperties 做属性拷贝,代码量也不会增加多少,但安全性提升非常明显。

3. 数据库设计:表结构与关键约束

3.1 核心表的字段设计

数据库设计是财务系统的地基,地基歪了后面全白搭。我先花了整整两天时间来设计表结构,核心表包括:用户表(sys_user)、角色表(sys_role)、收支流水表(finance_record)、分类表(finance_category)、预算表(finance_budget)和审批单据表(finance_audit)。这里拿最核心的收支流水表来具体说明:

CREATE TABLE `finance_record` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', `record_no` VARCHAR(32) NOT NULL COMMENT '流水单号,业务唯一', `type` TINYINT NOT NULL COMMENT '类型:1-收入 2-支出', `category_id` BIGINT NOT NULL COMMENT '分类ID,关联分类表', `amount` BIGINT NOT NULL COMMENT '金额,单位:分', `occur_date` DATE NOT NULL COMMENT '业务发生日期', `created_at` DATETIME NOT NULL COMMENT '创建时间', `creator_id` BIGINT NOT NULL COMMENT '创建人ID', `audit_status` TINYINT NOT NULL DEFAULT 0 COMMENT '审核状态:0待审 1通过 2驳回', `auditor_id` BIGINT DEFAULT NULL COMMENT '审核人ID', `remark` VARCHAR(255) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`id`), UNIQUE KEY `uk_record_no` (`record_no`), KEY `idx_occur_date` (`occur_date`), KEY `idx_category_id` (`category_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='收支流水表';

这个表的设计有几个关键点:流水单号用业务规则生成,格式如“20241227-0001”,保证唯一性,目的是方便线下对账时快速检索;金额用 BIGINT 存分,是财务系统必须遵守的铁律,用 DOUBLE 存金额迟早会出精度问题,到时候差的几分钱够你对一整天;业务发生日期和创建时间拆分,这样月底结账时按业务日期归集,不会把跨月数据搞混。

3.2 权限相关表的设计套路

权限设计我用了经典的 RBAC(基于角色的访问控制)模型,三张表:用户表、角色表、用户角色关联表。用户表和角色表多对多,触达菜单和操作权限时再加一张权限表,总共五张表。一开始我想简化成用户表里直接存一个“角色”字段,后来想想财务系统里“出纳”和“会计”的权限边界差异很大,硬编码角色会导致后续每次需求变更都要改代码,所以还是老老实实做了标准 RBAC。

在实际建表时,有一个坑必须提醒:角色表一定要加数据范围字段。财务主管能看到全公司数据,普通会计只能看自己录入的数据,这是财务系统常见的需求。如果只在角色表里加一个权限标识,而不做数据行级隔离,就会出现“明明有权限但数据串了”的事故。我在角色表里加了data_scope字段(1-全部 2-本部门 3-仅本人),配合 MyBatis-Plus 的拦截器做数据权限过滤,效果非常理想。

3.3 预算表与流水表的关系处理

预算表的设计也有讲究。预算不是简单的“期初额度 - 支出金额”,因为一个预算周期内可能有多笔支出,而且跨部门的费用归属比较复杂。我的做法是:

CREATE TABLE `finance_budget` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `department_id` BIGINT NOT NULL COMMENT '部门ID', `budget_type` VARCHAR(16) NOT NULL COMMENT '预算类别:办公费/差旅费/招待费', `total_amount` BIGINT NOT NULL COMMENT '预算总额', `used_amount` BIGINT NOT NULL DEFAULT 0 COMMENT '已使用金额', `period_month` VARCHAR(7) NOT NULL COMMENT '预算月份,如2024-12', PRIMARY KEY (`id`), UNIQUE KEY `uk_dept_type_month` (`department_id`, `budget_type`, `period_month`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

预算和流水的关系不要用外键强关联,而是通过“部门 + 分类 + 月份”这三个维度动态计算已用金额。流水表里记录了每条支出的分类和所属部门,月末生成报表时用一条 SUM 查询就能得到各预算项的执行情况。这样的设计灵活得多,预算调整时不用改动任何流水数据。

4. 核心模块实操:从认证到权限控制

4.1 基于 JWT 的用户认证

财务系统对安全性的要求高于普通管理系统,第一道关卡就是登录认证。我采用了 JWT(JSON Web Token)方案,而不是传统的 Session。原因是前后端分离架构下,JWT 天然无状态,不需要在服务端保存会话信息,适合后续扩展小程序端或移动端。

JWT 的生成逻辑我放在了自定义工具类里,核心思路如下:

public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); claims.put("userId", userDetails.getUserId()); claims.put("username", userDetails.getUsername()); claims.put("roles", userDetails.getRoles()); Date now = new Date(); return Jwts.builder() .setClaims(claims) .setIssuedAt(now) .setExpiration(new Date(now.getTime() + 86400000L)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); }

关于 JWT 有几个实际问题值得单独讲讲。第一是过期时间,我设置成 24 小时,配合前端在请求拦截器里统一处理 401 状态,用户会话过期后自动跳转登录页。第二是密钥管理,SECRET_KEY 不能硬编码在代码里,明文写在配置文件里虽然省事,但如果代码仓库泄露后果不堪设想,至少也应该用 Jasypt 做配置加密。我当时用了一个简单但有效的方案:密钥放在环境变量里,部署时配置,开发环境用默认值兜底。第三是注销问题,JWT 无状态意味着服务端无法主动让 token 失效,我的解决方案是维护一个 Redis 黑名单,用户注销后把 token 的 jti 写入黑名单,过期时间与 token 剩余有效时间一致。

4.2 Spring Security 过滤链配置

Spring Security 的过滤链是新手最容易懵的地方。配置这玩意儿不是贴个代码就完事,而是得理解它的执行顺序。我的配置核心结构如下:

@Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .cors().and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/api/auth/login", "/api/auth/captcha").permitAll() .antMatchers("/api/reports/**").hasRole("ADMIN") .antMatchers("/api/records/**").hasAnyRole("ADMIN", "FINANCE") .anyRequest().authenticated() .and() .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); }

我踩过的一个印象深刻的坑是:.csrf().disable()写了两行报错发现是配置顺序问题;还有一次因为漏了cors().and(),前端请求跨域直接报 403,排查了好久才发现是 Spring Security 把跨域拦截了。所以说配置一定要完整,不能自认为“后端不需要跨域配置”。

另外,antMatchers的规则匹配顺序也很关键,越具体的规则要写在越前面。Spring Security 按照声明顺序依次匹配,一旦命中就停止继续匹配。如果把.anyRequest().authenticated()写在最前面,后面所有具体接口的权限配置都会被跳过,这是很典型的隐蔽 Bug。

4.3 数据权限的实现细节

RABC 只能解决“能不能访问这个接口”的问题,但财务系统里还有“能看哪些数据”的问题。同一个查询接口,财务主管要能看到全公司数据,部门经理只能看本部门的数据,普通会计只能看自己录的数据,这就需要在查询时动态拼 SQL 条件。

我用 MyBatis-Plus 实现数据权限的思路是:在实体类上做切面,通过自定义注解捕获当前登录用户的角色,然后根据角色往查询条件里注入数据范围限制。

@Aspect @Component public class DataScopeAspect { @Around("@annotation(dataScope)") public Object around(ProceedingJoinPoint joinPoint, DataScope dataScope) throws Throwable { // 获取当前登录用户角色和部门信息 LoginUser loginUser = SecurityUtils.getLoginUser(); // 如果是ADMIN角色,不做数据过滤;普通角色自动附加本部门条件 // 通过 MyBatis-Plus 的 wrapper 动态拼接部门条件 } }

这个方案的好处是业务代码里完全不用感知数据权限逻辑,只需要在对应的 Service 方法或 Mapper 方法上加一个@DataScope注解就行。但要注意的是,数据权限过滤不能只放在 Service 层,如果有的地方直接用了 Mapper 的 selectById 或自定义 SQL,过滤逻辑就会被绕过。所以我在所有核心查询入口都做了统一拦截,同时要求团队写 SQL 时严格走封装的 BaseMapper,不允许直接拼原生 SQL。

5. 核心业务模块实现:记账、预算和报表

5.1 收入支出记账的完整流程

记账模块是整个系统业务最密集的地方,因为每一笔流水都要经过“创建 → 核验 → 审核 → 入账”的完整状态流转。我把这个流程设计成了一个状态机:待审核(0)→ 已通过(1)/ 已驳回(2),驳回后允许修改内容重新提交。

在实现时,我用了一个专门的 Service 来处理记账流程,而不是让 Controller 直接操作 Mapper。每记一笔账,系统要做几件事:校验金额合法性、校验分类是否存在、生成流水号、写入流水表、如果关联了预算还要同步更新预算的已用金额。这些操作必须放在同一个事务里,任何一个环节出错,整个操作回滚。事务的实现很简单,加@Transactional注解,但要注意自调用失效的问题,同类内部方法调用this.addRecord()不会触发事务代理,必须通过注入自身的代理对象调用才能生效。

流水号生成也是细节。用 UUID 虽然简单,但财务人员拿到一串无规律的字符串完全没法做线下核对。我最终用了“yyyyMMdd + 当日自增序号”的格式,比如20241227-0001。实现的时候要注意并发问题,两个请求同一毫秒内同时拿到相同的计数就会冲突,所以我在 Redis 里用 INCR 命令维护每天的序号,天然线程安全,且当天序号从 0 开始,第二天自动重新计数。

public String generateRecordNo() { String datePrefix = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")); Long sequence = redisTemplate.opsForValue().increment("record:seq:" + datePrefix, 1); return datePrefix + "-" + String.format("%04d", sequence); }

5.2 预算管理模块的预警逻辑

预算管理不只是建一张表、填一个额度,重点是执行过程中的预警。我实现的预算预警逻辑分为两个级别:黄色预警(使用率达到 80%)和红色预警(使用率达到 100%)。触发条件不是在用户查询时被动计算,而是在每次新增支出记录成功后,主动去检查关联预算的执行情况。

预警推送我用的是 Spring 的事件机制。支出写入成功后,发布一个BudgetCheckEvent,监听器收到事件后查询对应维度下的预算使用率,达到阈值就给财务主管发送站内信通知。用事件机制的好处是解耦:记账逻辑和预警逻辑各自独立,即使预警服务出了问题,记账流程本身不受影响,不会因为一个通知失败导致一笔支出无法录入。这在实际生产环境中非常重要,财务数据录入永远应该是最高优先级的主链路。

预算超支的控制策略我设计成“软控制 + 硬控制”两种模式:默认是软控制,即超支后仍允许录入,但会提示;财务主管可以针对特定分类开启硬控制,超支时直接拒绝新增支出,防止预算失控。这两种模式在配置表里用一个字段控制,实现起来并不复杂,但业务价值非常大。

5.3 统计报表模块的 SQL 优化

统计报表是财务系统最容易出现性能瓶颈的地方,因为老板喜欢看“本月收入”“本月支出”这类聚合数据,如果每次查询都实时扫描全表,数据量一上来就会卡。我的方案是:日汇总表 + 月度汇总表两层预聚合。

每天晚上通过定时任务把当天的收入支出按类型、分类、部门聚合一次,写入日汇总表。查询月度报表时,直接查日汇总表再聚合,最多扫 31 行数据,比直接扫流水表快了几个数量级。定时任务用 Quartz 实现,配置好 cron 表达式后,每天凌晨 1 点执行前一天的数据汇总:

0 0 1 * * ?

这里有一个细节值得注意:定时任务跑完后,要做一次对账校验,把日汇总表的总金额和流水表的 SUM 金额做比对。如果对不上,立刻发送告警,而不是默默完成任务。我实际遇到过定时任务因为一条异常数据导致事务回滚的情况,如果没有对账校验,这个月的报表数据就会悄悄出错,直到月底结账时才发现,那就很被动了。

报表的图表展示我用的是 ECharts,后端把聚合后的数据按照图表需要的结构返回,前端直接渲染。月度收支趋势图、分类占比饼图、部门支出排名柱状图,这三个图基本覆盖了老板日常要看的核心指标。给接口返回数据时,我单独设计了报表 VO,把百分比、金额单位转换这些计算逻辑放在后端完成,前端只负责展示,避免前端因为金额单位不统一而显示错误。

6. 部署、性能与安全加固

6.1 打包部署的正确姿势

开发完成后的打包部署,很多人都以为就是一个mvn package然后java -jar启动,实际上这里面的门道不少。Spring Boot 的 Maven 插件里有两种打包方式:普通 JAR 包和可执行 JAR 包。如果用了 spring-boot-maven-plugin,默认打出来的就是可执行 JAR,嵌入 Tomcat 就能直接跑:

mvn clean package -DskipTests nohup java -jar finance-system.jar --spring.profiles.active=prod > app.log 2>&1 &

部署时我用多环境配置文件来管理不同环境下的参数。application-dev.yml是本地开发,application-prod.yml是线上环境,数据库连接、Redis 地址、日志级别都分开配置。启动时通过--spring.profiles.active=prod指定环境,这样就不会出现把本地数据库地址带到线上导致连错库的情况。数据库密码我强烈建议用环境变量注入,不要明文写在配置文件里,具体的方式在 Spring Boot 里可以直接用${DB_PASSWORD}占位符引用环境变量。

6.2 性能优化实战:慢查询与索引

财务系统在运行一段时间后,流水表的数据量可能会涨到几十万甚至上百万行,这时候查询性能就会明显下降。我遇到过最典型的问题是一个报表接口在数据量达到 80 万行时,响应时间从最初的 300ms 飙到了 8 秒以上。排查后发现是关联查询里日期范围没走索引。

排查 SQL 问题我用的是两个动作:一是开启 MySQL 慢查询日志,设置超过 2 秒的 SQL 记录到日志文件;二是用 EXPLAIN 查看执行计划。定位到问题 SQL 后,我给finance_record表的occur_date和category_id建了联合索引idx_date_category (occur_date, category_id),单次查询时间直接降回 80ms 以内。另外还调整了一个隐式类型转换的问题:record_no表里是 VARCHAR,但代码里传了 Long 类型参数,MySQL 会自动把字段转成数字导致索引失效。这种问题用 EXPLAIN 看type字段,会发现原来是ref,变成ALL全表扫描,非常明显。

6.3 安全加固的几个关键操作

财务系统的安全加固,不能只停留在 Spring Security 的配置上。我做了几件事:

  • 接口限流:登录接口用 AOP 切面实现同 IP 每分钟最多尝试 5 次,超过则锁定 15 分钟,防止暴力破解。
  • 敏感字段脱敏:日志打印时统一使用脱敏工具类,手机号、银行卡号只显示前后几位,中间用星号替代,避免日志文件泄露敏感信息。
  • 操作审计日志:用一个切面对所有写操作的 Controller 方法进行拦截,记录操作人、操作时间、修改前后的数据摘要。一旦发生数据异常,可以回查是谁在哪一步改了什么内容,这是财务系统内控合规的必要手段,也是后期出问题时唯一的排查依据。

7. 常见问题与排查技巧实录

7.1 金额精度丢失问题

这是财务系统里最容易踩的坑。某次我把流水表里的金额字段设计成了 DECIMAL(10,2),后端实体类里对应的是 BigDecimal,本来以为没问题。结果某天财务人员反映,有一笔 999.99 元的支出在列表页显示成了 1000.00 元。排查后发现原因在 ORM 映射:MyBatis-Plus 默认把 DECIMAL 映射成 BigDecimal 没错,但我在某个统计接口里为了图方便,用sum(amount)后直接转成了 Double,保留两位小数后发生了四舍五入。

这个问题的教训是:金额运算全程用 BigDecimal,禁止任何环节出现浮点类型。我把所有涉及金额的统计查询都改成了返回 BigDecimal,并且统一使用setScale(2, RoundingMode.HALF_UP),在业务代码层做舍入控制,数据库层只负责存储和求和。后来还定了条规范:凡是新增金额字段,一律用 BIGINT 类型存分,从根源杜绝精度问题。

7.2 事务不回滚的诡异现场

有一次往收支流水表和预算表同时写入数据,预算表写入故意抛了个异常测试回滚,结果发现流水表的数据竟然保留了。查了半天才发现,我在 Service 类里加了@Transactional注解,但方法被同类中的另一个方法通过this.xxx()调用了。Spring 的事务是基于 AOP 动态代理实现的,同类内部调用不会经过代理对象,事务注解自然就不生效。

解决办法是注入自身的代理对象,或者把需要事务的方法独立到另一个 Service 类中。这个坑非常隐蔽,而且代码层面不会报任何错误。我后来养成了一个习惯:凡是涉及多表写入的核心业务方法,都刻意放到独立的 Service 类中,同时用 CT(控制反转)的方式在整个调用链路里隐藏自调用,从架构上规避这个问题。

7.3 Excel 导出中文乱码与大数据量导出

系统里做了一个 Excel 导出功能,用阿里 EasyExcel 导出一个月的流水明细。某次用户反馈导出的文件中文全部变成了乱码,排查后确认是响应头编码设置问题。Spring Boot 中返回文件流时,必须设置:

response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); // 文件名做 URL 编码,兼容浏览器中文文件名 String fileName = URLEncoder.encode("月度流水报表", "UTF-8"); response.setHeader("Content-Disposition", "attachment; filename=" + fileName + ".xlsx");

这个已经算熟门熟路了。更麻烦的是大数据量导出,一次性导 10 万行数据,EasyExcel 直接内存溢出。我的解决策略是:分批查询 + 流式写入,每次只从数据库取 1 万行,写入 Excel 后立即释放,内存占用从几百 MB 降到了几十 MB。同时后台用线程池跑导出任务,前端轮询进度,导出完成后推送下载链接,避免长时间 HTTP 请求超时。

7.4 问题排查方法总结

财务系统的问题排查,我总结了一套固定流程:先看日志,再看 SQL,最后看数据。日志里重点看异常堆栈的 Caused by,很多时候真正的错误原因藏在内层。SQL 问题用慢查询日志定位,配合 EXPLAIN 分析执行计划,优先排查是否没走索引。数据问题先确认脏数据,再确认代码逻辑,最后才考虑代码 Bug。这套流程我用了很久,能覆盖绝大多数实际问题,也建议你在排查时保持这个顺序,不要一上来就猜代码问题,那样往往徒劳无功。

8. 写在最后的经验心得

把整个项目完整做下来之后,我最大的体会是:财务管理系统真正难的不是代码,而是对业务规则的理解和对细节的敬畏。金额精度、权限边界、数据一致性、操作审计,每一个问题单独看都不复杂,但组合在一起就对系统的稳定性提出了很高要求。

我认为有三件事值得每个做财务系统的人认真对待:第一,设计表结构时多花时间,把金额单位、时间字段、关联关系都敲定,后期的返工成本会大大降低;第二,认真做好数据权限,哪怕初期不需要,也要预留扩展能力,因为财务数据的安全边界只会越来越严格;第三,日志和审计机制一定在一开始就埋进去,不要等到出问题再补,出事之后的排查成本远大于提前设计的成本。

如果你正在做类似的项目,建议你先把这篇文章里提到的表结构和核心代码自己动手敲一遍,再尝试扩展一个你所在业务场景需要的功能点,比如发票管理、固定资产折旧、多币种汇率换算。顺着业务需求去驱动技术实现,比照着文档堆代码要有效得多。这套 Spring Boot 财务系统的架构思路,放到绝大多数的中小型管理系统里也都完全适用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 4:51:34

AI教材写作如何实现低查重?长篇教材结构化落地指南

写教材这件事&#xff0c;以前是真体力活。尤其是在高校和职业院校里&#xff0c;动辄几十万字的专业课教材&#xff0c;从大纲、初稿、配图、习题到参考文献&#xff0c;全流程走一遍&#xff0c;半年能磨出来都算快的。这几年AI教材写作工具火起来&#xff0c;确实能省不少力…

作者头像 李华
网站建设 2026/10/22 4:51:33

Dify开源大模型应用开发平台:从RAG知识库到LLMOps实践指南

1. Dify 是什么&#xff0c;它能帮你解决什么问题1.1 先别急着背概念&#xff1a;一句话讲清楚Dify 是一个开源的大语言模型应用开发平台&#xff0c;简单说就是让你不写一堆重复的胶水代码&#xff0c;也能把大模型接入到自己的业务里&#xff0c;做出可用的 AI 应用。它把模型…

作者头像 李华
网站建设 2026/10/22 4:50:30

Jedis操作Redis实战指南:从基础命令到生产级调优

1. 为什么Java项目里我一直在用Jedis操作RedisJedis这个词&#xff0c;只要你做过后端开发&#xff0c;大概率在依赖清单里见过。它是Java生态里最早被广泛使用的Redis客户端&#xff0c;说白了就是一个让我们在Java代码里操作Redis的中间层库。Redis本身是C语言写的服务端&…

作者头像 李华
网站建设 2026/10/22 4:50:26

Claude Haiku 5.5、GPT-6免费与Manus回归:AI行业三大信号拆解

1. 三条新闻背后的行业信号拆解2026年10月8日这一天&#xff0c;AI圈子里同时炸出了三条消息&#xff1a;Claude Haiku 5.5 发布、GPT-6 免费开放、Manus 重返北京。单看每一条都是独立事件&#xff0c;但把它们放在同一天的时间轴上&#xff0c;你会发现这不是巧合&#xff0c…

作者头像 李华
网站建设 2026/10/22 4:50:00

如何科学降低论文AIGC率?降AI工具实测、原理与使用边界解析

这两年研究生圈子里出现了一个高频词&#xff1a;降AIGC率。不管是在校生还是即将毕业的同学&#xff0c;写论文时基本都会遇到“论文查一遍AI率&#xff0c;发现一堆段落被标成疑似AI生成”的情况。市面上随之出现了不少降AIGC工具&#xff0c;我最近也花了两周时间专门测了几…

作者头像 李华
网站建设 2026/10/22 4:48:52

VS Code + TeX Live 配置指南:从安装到高效编写 LaTeX 文档

写 LaTeX 这事儿&#xff0c;我绕了不少弯路。最早用的是某在线协作平台&#xff0c;网络一波动就难受&#xff1b;后来切到某个一体化套装&#xff0c;编译按钮倒是方便&#xff0c;可代码补全和 Git 集成又跟不上。最后稳定在了 VS Code TeX Live 这条路上&#xff0c;一用就…

作者头像 李华