资讯动态

SpringBoot电子发票管理系统实战:从环境搭建到核心模块与避坑指南

发布时间:2026/10/17 22:39:47 来源:尧图企业网站定制
简介这是一套基于Java Spring Boot的电子发票管理系统完整源码面向学习企业级Java开发的学生、初级开发者及需要课程设计或毕业设计参考的技术人员帮助理解发票录入、自动开具、存储备份、查询审核、报表统计与税务合规检查等业务闭环。压缩包共963个文件约2.97MB以321个html、260个css、187个png、98个js构成前端页面与静态资源39个java文件承载Spring Boot后端核心逻辑另有json配置、db数据库文件及md说明整体结构清晰便于按模块阅读与二次开发。已有87人学习关注。资源覆盖Spring MVC、Spring Data JPA、Spring Security、MySQL存储、RESTful接口与安全防护等知识点适合作为全栈项目实战模板帮助读者快速掌握前后端分离架构、数据库设计与发票业务建模思路。1. 从一张报销单说起这套 SpringBoot 电子发票管理系统到底能跑通什么上个月帮一个做企业服务的朋友看他们的报销流程财务那边还在用共享表格登记发票员工贴票、拍照、发邮件月底对账全靠人肉比对。问题很典型发票号重复录入没人发现抬头写错了要退回重开跨月的票混在一起根本查不清。他们想上一套轻量的电子发票管理系统但预算有限买 SaaS 按人头收费不划算于是我把这套基于 Java SpringBoot 的电子发票管理系统源码包拆了一遍跑起来验证了核心链路。这套东西解决的不是「开票」——开票是税务侧的事它解决的是发票的录入、查验登记、归档、查询和统计这一整条内部管理链路。技术栈是标准的 SpringBoot MyBatis-Plus MySQL Thymeleaf或前后端分离的 Vue 版本看具体分支Maven 构建JDK 8 起步。适合谁中小团队的后端开发拿来当毕设或练手项目或者企业内部想快速搭一个发票台账系统的场景。它不是一个能直接上生产的成品但骨架完整、表结构清晰改起来比从零写省至少两周。下面我按「怎么跑起来 → 核心模块怎么实现的 → 哪里会翻车」的顺序拆。2. 环境搭建与启动从 JDK 到第一个登录页2.1 依赖版本选型为什么我不建议无脑上最新拿到源码包第一件事不是急着mvn spring-boot:run而是先看pom.xml里的版本组合。这套项目大概率是 SpringBoot 2.x 系列2.3 到 2.7 之间因为 3.x 要求 JDK 17 且把javax.*换成了jakarta.*很多老代码直接编译不过。热搜里「springboot版本太高」这个坑我见得太多了——新手装了 JDK 17 又拿了个 2.x 的项目或者反过来启动就报Unsupported class file major version。我的建议是先看项目里的 JDK 编译级别再决定装哪个 JDK。打开pom.xml找这几行properties java.version1.8/java.version spring-boot.version2.7.6/spring-boot.version /properties如果java.version是 1.8那就老老实实装 JDK 8 或 JDK 11别折腾 17。SpringBoot 2.7 是 2.x 的最后一个版本兼容性和生态都稳MyBatis-Plus、Druid 这些常用依赖都能对上。选 2.7 而不是 2.3是因为 2.7 对 JDK 11 支持更好而且自动配置的写法更规范后面你想升级到 3.x 迁移成本也低一些。数据库这块MySQL 5.7 和 8.0 都行但要注意驱动类名和连接串参数的差异。5.7 用com.mysql.jdbc.Driver8.0 用com.mysql.cj.jdbc.Driver而且 8.0 必须带时区参数否则启动报时区错误。这个后面避坑章节细说。2.2 数据库初始化建库、导表、改连接源码包里一般有个sql目录里面是.sql建表脚本。常见做法是先建一个空库字符集选utf8mb4然后导入脚本。别用utf8发票备注里可能有 emoji 或者特殊符号utf8存不进去会报错。# 登录 MySQL 后执行 CREATE DATABASE invoice_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 退出后用命令行导入注意路径换成你本地的 mysql -u root -p invoice_db /path/to/invoice_db.sql导完之后检查一下表数量一般会有用户表、发票主表、发票明细表、报销单表、部门表、操作日志表这几张。如果脚本里带了初始管理员账号通常在sys_user表里密码是 MD5 或 BCrypt 加密的默认密码看 README 或者注释。接着改application.yml或application.properties里的数据源配置spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/invoice_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 你的密码serverTimezoneAsia/Shanghai这个参数在 MySQL 8.0 下不加会直接启动失败报The server time zone value xxx is unrecognized。useSSLfalse是本地开发省事生产环境要开。2.3 启动与验证看到登录页只是第一步配置改完在项目根目录执行mvn clean package -DskipTests java -jar target/invoice-system-0.0.1-SNAPSHOT.jar或者直接在 IDE 里跑主启动类。看到控制台输出Started XxxApplication in x.xxx seconds就算起来了。默认端口 8080浏览器访问http://localhost:8080应该能看到登录页。登录进去之后重点验证三个功能发票录入能不能新增一条发票记录、发票列表查询分页和条件筛选是否正常、统计看板如果有的话图表数据能不能加载。这三个通了说明数据库连接、MyBatis 映射、前端模板渲染这条链路是完整的。如果登录页都出不来先看控制台有没有Whitelabel Error Page那多半是静态资源路径或者模板引擎配置的问题。3. 核心模块拆解发票录入、查验登记与权限控制怎么实现的3.1 发票实体与 MyBatis-Plus 映射这套系统的核心表是发票主表字段一般包括发票代码、发票号码、开票日期、金额、税额、购买方名称、销售方名称、发票类型增值税专票/普票/电子普票、状态待查验/已查验/已归档等。对应的实体类用 MyBatis-Plus 的注解映射Data TableName(t_invoice) public class Invoice { TableId(type IdType.AUTO) private Long id; private String invoiceCode; // 发票代码 private String invoiceNo; // 发票号码 private LocalDate invoiceDate; // 开票日期 private BigDecimal amount; // 不含税金额 private BigDecimal taxAmount; // 税额 private String buyerName; // 购买方名称 private String sellerName; // 销售方名称 private Integer invoiceType; // 1-专票 2-普票 3-电子普票 private Integer status; // 0-待查验 1-已查验 2-已归档 TableField(fill FieldFill.INSERT) private LocalDateTime createTime; }TableId(type IdType.AUTO)对应数据库自增主键如果你用的是雪花算法就改成IdType.ASSIGN_ID。TableField(fill FieldFill.INSERT)配合 MetaObjectHandler 自动填充创建时间省得每次手动 set。这里有个细节金额字段一定用BigDecimal别用Double发票金额涉及财务计算浮点误差是致命的。热搜里有人问「mybatisplus根据java实体类生成创建表的sql语句」MyBatis-Plus 本身不直接生成 DDL但可以用mybatis-plus-generator反向生成实体和 Mapper或者用 JPA 的ddl-auto思路。这套项目是手写 SQL 脚本更可控。3.2 发票录入的校验逻辑与重复拦截发票录入最容易出的问题是重复录入。同一张发票被两个员工先后登记财务对账时才发现。系统里一般会在 Service 层做唯一性校验public Result addInvoice(Invoice invoice) { // 1. 校验发票代码号码是否已存在 LambdaQueryWrapperInvoice wrapper new LambdaQueryWrapper(); wrapper.eq(Invoice::getInvoiceCode, invoice.getInvoiceCode()) .eq(Invoice::getInvoiceNo, invoice.getInvoiceNo()); Long count invoiceMapper.selectCount(wrapper); if (count 0) { return Result.fail(该发票已录入请勿重复提交); } // 2. 校验金额是否为正数 if (invoice.getAmount().compareTo(BigDecimal.ZERO) 0) { return Result.fail(发票金额必须大于零); } // 3. 校验开票日期不能晚于当前日期 if (invoice.getInvoiceDate().isAfter(LocalDate.now())) { return Result.fail(开票日期不能晚于今天); } invoiceMapper.insert(invoice); return Result.success(录入成功); }这段逻辑看着简单但实际跑的时候要注意并发场景。两个请求同时进来都先selectCount查到 0然后都插入还是会产生重复。严格的做法是在数据库层给invoice_code invoice_no加唯一索引靠数据库兜底ALTER TABLE t_invoice ADD UNIQUE KEY uk_code_no (invoice_code, invoice_no);这样即使应用层校验漏了数据库也会抛DuplicateKeyException在全局异常处理器里捕获一下返回友好提示就行。3.3 权限控制不同角色看到不同数据发票管理系统一般分三种角色普通员工只能看自己的发票、部门主管看本部门的、财务看全部。这套项目大概率用 Spring Security 或者简单的拦截器 Session 实现。如果是 Spring Security配置类里会定义 URL 级别的权限Configuration EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers(/login, /css/**, /js/**).permitAll() .antMatchers(/admin/**).hasRole(ADMIN) .antMatchers(/finance/**).hasAnyRole(FINANCE, ADMIN) .anyRequest().authenticated() .and() .formLogin().loginPage(/login).defaultSuccessUrl(/index) .and() .logout().logoutSuccessUrl(/login); } }数据级别的权限行级权限通常是在 Mapper 查询时拼条件。比如普通员工查询时强制加create_by 当前用户ID财务角色不加这个条件。热搜里「行级权限java」说的就是这个场景。实现方式可以是在 Service 层根据当前登录用户的角色动态构造 QueryWrapper也可以用 MyBatis 拦截器统一处理后者更优雅但调试麻烦。4. 避坑与排查启动失败、乱码、分页失效的五个血泪现场4.1 启动报时区错误MySQL 8.0 的经典翻车现象项目启动直接抛异常控制台一堆红字核心信息是The server time zone value ?D1ú±ê×?ê±?? is unrecognized。原因MySQL 8.0 的驱动在建立连接时会读取服务器时区如果服务器时区是CST这种模糊标识驱动解析不了就报错。Windows 下的中文系统尤其容易触发。解决在 JDBC URL 里显式指定serverTimezoneAsia/Shanghai或者去 MySQL 命令行执行SET GLOBAL time_zone 8:00;永久生效。我一般两个都做URL 里带上最保险。4.2 中文乱码从数据库到页面的全链路排查现象录入的发票抬头是中文存进数据库变成???或者页面上显示成乱码。原因乱码可能出现在三个环节——数据库字符集、连接串编码、页面编码。任何一环不是utf8mb4都会出问题。解决按顺序查。先确认建库时用了utf8mb4再确认连接串有characterEncodingutf8最后看 Thymeleaf 模板的meta charsetUTF-8有没有。如果是前后端分离还要看application/json的响应头编码。三个都对了基本不会乱。4.3 分页查询返回全部数据MyBatis-Plus 分页插件没配现象前端传了pageNum1pageSize10但接口返回了全表数据分页完全没生效。原因MyBatis-Plus 的分页功能依赖PaginationInnerInterceptor插件如果配置类里没注册这个 BeanPage对象就是个摆设。解决在配置类里加上Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }注意DbType.MYSQL要和你实际用的数据库对上用错了生成的分页 SQL 方言不对照样查不出来。4.4 静态资源 404SpringBoot 拦截器把 css/js 也拦了现象登录页样式全丢F12 看控制台一堆 404css 和 js 文件加载失败。原因自定义的登录拦截器如果只排除了/login没排除/static/**或/css/**、/js/**静态资源请求也会被拦截去检查登录状态未登录就重定向了。解决在拦截器的addPathPatterns和excludePathPatterns里把静态资源路径放行。SpringBoot 默认的静态资源目录是classpath:/static/对应 URL 是/**所以排除规则要写全。4.5 打包后运行报找不到主类Maven 插件没配对现象mvn clean package成功但java -jar报no main manifest attribute。原因pom.xml里缺少spring-boot-maven-plugin或者配置了但没执行repackage目标。普通 Maven 打包生成的 jar 不包含依赖和启动清单。解决确认pom.xml的build节点下有build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build然后重新mvn clean package生成的 jar 会比普通 jar 大很多因为把依赖打进去了那才是可执行的。5. 进阶技巧用代码生成器省掉一半的 CRUD 工作量这套系统跑通之后如果你想加新模块——比如「发票查验记录」或者「报销单关联」——手写 Controller、Service、Mapper、XML 四件套很烦。MyBatis-Plus 的代码生成器能根据表结构自动生成这套代码省掉大量重复劳动。我一般会在项目里单独放一个CodeGenerator类只在开发阶段跑不打包进生产。public class CodeGenerator { public static void main(String[] args) { // 1. 配置数据源 DataSourceConfig dsc new DataSourceConfig(); dsc.setUrl(jdbc:mysql://localhost:3306/invoice_db?serverTimezoneAsia/Shanghai); dsc.setDriverName(com.mysql.cj.jdbc.Driver); dsc.setUsername(root); dsc.setPassword(你的密码); // 2. 全局配置 GlobalConfig gc new GlobalConfig(); gc.setOutputDir(System.getProperty(user.dir) /src/main/java); gc.setAuthor(dev); gc.setOpen(false); gc.setServiceImplName(%sService); // 3. 包配置 PackageConfig pc new PackageConfig(); pc.setParent(com.example.invoice); pc.setEntity(entity); pc.setMapper(mapper); pc.setService(service); pc.setController(controller); // 4. 策略配置指定要生成的表名 StrategyConfig strategy new StrategyConfig(); strategy.setInclude(t_invoice_check); // 换成你的新表名 strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setColumnNaming(NamingStrategy.underline_to_camel); strategy.setEntityLombokModel(true); strategy.setRestControllerStyle(true); // 5. 执行生成 new AutoGenerator() .setDataSource(dsc) .setGlobalConfig(gc) .setPackageInfo(pc) .setStrategy(strategy) .execute(); } }跑完这个 main 方法entity、mapper、service、controller四个包下会多出对应的文件基础的增删改查接口直接可用。你只需要在生成的 Service 里补业务逻辑比如查验状态的流转、金额的汇总计算。注意生成的 Mapper XML 默认放在resources/mapper下如果项目配置了mapper-locations确认路径能对上。还有一个实用技巧发票数据的批量导入。财务那边经常拿到一堆 Excel 格式的发票清单一条条录入不现实。可以用 EasyExcel 或者 POI 写个导入接口读 Excel 转成ListInvoice然后走 MyBatis-Plus 的saveBatch批量插入。注意批量插入时唯一索引冲突会整批回滚建议先查重再插或者用INSERT IGNORE的写法。从那以后我每次拿到新的 SpringBoot 项目都强制先跑一遍「改配置 → 导数据 → 启动 → 登录 → 核心功能点一遍」这个流程确认基线通了再动代码。这套电子发票管理系统的骨架不算复杂但把发票录入、查验、权限、统计这几块串起来对理解企业级 CRUD 项目的完整链路很有帮助。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑