面向企业费用管理场景的智能报销与费控系统。覆盖“票据上传与 OCR、预算控制、分级审批、会计凭证、付款与消息通知”的完整业务闭环。
Smart Expense 是一个前后端分离的微服务练习项目,聚焦企业报销系统中最核心、最容易产生数据一致性问题的链路:预算不能超扣、审批不能越级、票据必须可追溯、异步任务不能轻易丢失。
项目将员工、经理、财务三类角色串入同一条报销流程,并在关键节点通过 Redis、数据库唯一约束、条件更新、消息重试和死信队列控制风险。
员工上传票据
-> 腾讯云 COS 保存原图
-> 阿里云 OCR 识别发票信息
-> 提交报销并预扣部门预算
-> RabbitMQ 异步验真
-> 经理审批
-> 财务审批并实扣预算
-> 自动生成会计凭证
-> 财务登记付款
-> 站内信通知相关人员
状态流转:
PENDING_VERIFY -> PENDING_MANAGER -> PENDING_FINANCE -> APPROVED -> PAID
\-> REJECTED / VERIFY_FAIL
| 场景 | 实现方式 | 解决的问题 |
|---|---|---|
| 预算预扣 | Redis Lua 原子扣减 + 预扣日志 | 防止并发提交导致预算超扣 |
| 预算实扣 | 幂等 token + MyBatis-Plus 乐观锁 | 将冻结额度安全转为已用预算 |
| 报销审批 | 状态机 + 条件更新 SQL | 防止越级审批和重复审批 |
| 票据处理 | COS 原图存储 + 阿里云 OCR | 减少手工录入并保留审计影像 |
| 重复报销 | 发票代码与号码联合唯一索引 | 以数据库约束作为最终兜底 |
| 异步验真 | RabbitMQ 主队列、重试队列、死信队列 | 解耦主流程并保留失败消息 |
| 财务闭环 | 费用科目映射 + 会计凭证 + 付款登记 | 连接报销业务与财务核算 |
| 消息提醒 | 站内信、未读数、已读状态 | 告知申请人和审批人流程变化 |
Vue 3 + Vite
|
v
Gateway & Auth (8080) ---- JWT 鉴权、路由转发
|
+------------------------+
| |
v v
Reimbursement Service Budget Service
| |
|-- MySQL: reimburse_db |-- MySQL: budget_db
|-- Redis |-- Redis Lua
|-- RabbitMQ |
|-- Tencent COS |
`-- Alibaba Cloud OCR |
- 员工报销:发票图片/PDF 上传、OCR 自动回填、报销单提交、查看原图和处理进度
- 预算控制:部门预算查询、提交预扣、审批通过实扣、驳回释放、token 幂等
- 审批工作台:经理按部门审批,财务复核、生成凭证并登记付款
- 发票验真:异步验真、失败重试、死信兜底、状态自动推进
- 会计凭证:按费用类型映射借方科目,自动生成期间与流水凭证号
- 消息中心:提交、验真、审批、付款等关键节点自动通知
后端:Java 17、Spring Boot 2.7、Spring Cloud Gateway、OpenFeign、MyBatis-Plus、MySQL 8、Redis、RabbitMQ、JWT、Nacos
前端:Vue 3、Vite、Vue Router、Axios
云服务:腾讯云 COS、阿里云 OCR
Smart-expense/
├── backend/
│ ├── common/ # 统一结果、异常、JWT、枚举和上下文
│ ├── gateway-auth/ # 登录认证、JWT 鉴权、网关路由
│ ├── reimbursement-service/ # 报销、发票、审批、验真、凭证、站内信
│ └── budget-service/ # 预算预扣、实扣、释放与并发控制
├── frontend/ # Vue 3 前端
└── docs/ # 数据库设计、分步骤设计与测试说明
- JDK 17
- Maven 3.8+
- Node.js 20+
- MySQL 8、Redis、RabbitMQ
- 可选:Nacos(当前配置支持本地直连下游服务)
- 可选:腾讯云 COS 和阿里云 OCR 凭证,用于真实票据上传与识别
数据库表结构见 docs/2-数据库设计.md。
仓库不包含真实密码、AccessKey 或票据文件。请通过环境变量或本地未提交的 application-local.yml 覆盖以下配置:
spring:
datasource:
username: your_mysql_username
password: your_mysql_password
redis:
host: localhost
password: your_redis_password
rabbitmq:
host: localhost
username: your_rabbitmq_username
password: your_rabbitmq_password
smart-expense:
aliyun:
ocr:
access-key-id: your_access_key_id
access-key-secret: your_access_key_secret
tencent:
cos:
region: your_cos_region
bucket-name: your_cos_bucket_name
base-url: https://your_cos_domain
secret-id: your_secret_id
secret-key: your_secret_key也可以使用 Spring 环境变量,例如 SMART_EXPENSE_REIMBURSE_DB_PASSWORD、SMART_EXPENSE_TENCENT_COS_SECRET_ID。
cd backend
mvn clean install
# 分别启动网关、预算服务和报销服务
mvn -pl gateway-auth spring-boot:run
mvn -pl budget-service spring-boot:run
mvn -pl reimbursement-service spring-boot:run默认端口:网关 8080、报销服务 8081、预算服务 8082。
cd frontend
npm install
npm run dev前端默认访问 http://localhost:5173,通过网关访问后端接口。
-
为什么预算要分预扣和实扣? 提交时先冻结额度,避免多人同时提交突破预算;最终审批通过后再把冻结额度转为已用额度,驳回则释放。
-
如何防止重复提交或重复扣减? 预算操作使用 token 幂等;发票使用联合唯一索引;审批与付款通过“期望状态”条件更新控制并发。
-
为什么验真使用 RabbitMQ? OCR/验真耗时且可能失败,异步化避免阻塞提交;重试队列处理瞬时故障,死信队列保留最终失败消息。
-
为什么原始票据放 COS 而不是 MySQL? 对象存储更适合大文件,数据库只存 URL,兼顾性能、成本和审计追溯能力。
-
为什么凭证在财务审批后生成? 只有业务被最终确认后才应该进入财务核算,避免给被驳回的单据生成无效凭证。
- 项目介绍
- 数据库设计
- 开发顺序
- Step 01 - 项目骨架
- Step 02 - 预算服务
- Step 03 - 网关与鉴权
- Step 04 - 报销提交与 OCR
- Step 05 - 审批流
- Step 06 - 预算实扣、凭证与付款
- Step 07 - RabbitMQ 异步验真
- Step 08 - 通知与联调
本仓库仅包含可公开展示的源代码和脱敏配置。请勿提交:数据库密码、Redis/RabbitMQ 凭证、云服务 AccessKey、JWT 生产密钥以及真实发票文件。