news 2026/10/16 9:55:52

微信小程序报销审批系统从零开发指南:技术选型与审批流设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
微信小程序报销审批系统从零开发指南:技术选型与审批流设计

小程序做内部工具,这几年确实是很多公司和高校毕设的首选。报销审批这种场景,业务逻辑清晰、角色分明、流程固定,用来练手或落地都非常合适。这个标题里信息量其实不少,除了免费源码这个卖点,真正有价值的是“怎么从零设计一个多角色审批流”,以及“Java、PHP、Python、C#这些后端语言怎么选”。基于我们长期做这类项目的经验,我把从需求拆解到技术选型,再到数据库设计、接口实现、权限控制、上线部署的完整链路写出来,争取让看到这篇内容的朋友少走弯路。

1. 项目概述与核心需求拆解

1.1 这个项目到底在解决什么问题

企业内部报销审批系统,本质上是一个流程管理工具。线下报销的痛苦相信很多人都经历过:填纸质单子、找领导签字、财务复核、出纳打款,中间任何一环人不在工位,整个流程就卡住了。尤其是几十人以上的公司或部门,报销周期动不动就拖一两周。

换成微信小程序来做,核心价值就四个字:移动、留痕。员工在手机上填单、拍照上传发票、提交审批,领导在微信里点开小程序就能批,财务在后台批量复核,所有操作自动记录时间、操作人、审批意见。整个过程不用装额外的App,微信扫一扫就能用,对非技术背景的同事非常友好。

从毕设或求职项目角度来说,这类系统的教学价值也很高。它里面涉及用户体系、角色权限、文件上传、流程状态机、数据统计这些通用模块,基本上把后端开发的常见知识点都串起来了。做完这一个项目,再去理解电商系统、工单系统、甚至简单的OA系统,逻辑都是相通的。

1.2 目标用户画像与使用场景分析

我把这个系统的主要使用者分成三类,对应三种不同的权限和操作界面:

角色核心诉求主要操作
普通员工(报销申请人)快速填单、随时查进度提交报销单、上传发票、查看审批状态
审批人(部门/财务)高效处理待办、信息充分查看明细、通过/驳回、填写意见
系统管理员配置规则、管控全局部门管理、用户管理、审批流配置、数据统计

实际使用场景通常是这样:员工出差回来,在手机上打开小程序,选择报销类型(差旅费、餐费、办公用品、交通费等),填写金额和事由,拍下发票照片上传,选择对应的审批流程,提交。部门经理收到微信通知(订阅消息),点开小程序看到申请单详情,有疑问就驳回并填写意见,没问题就通过。单据流转到财务人员那里,财务核对发票和金额,确认无误后通过,进入打款环节。全程无纸化,链条清晰,每一笔钱花在哪里都查得到。

1.3 系统边界与功能分层梳理

我给这类系统做功能规划时,习惯用前后端分离的思想来划分模块,哪怕是一个单体项目也要保持清晰的边界:

  • 基础支撑层(不可或缺):微信登录注册(获取微信头像昵称)、用户身份认证(Token鉴权)、图片上传服务(发票拍照)。
  • 业务核心层(这块是重点):报销单CRUD、审批流引擎(流转逻辑)、通知推送(订阅消息)、金额统计与筛选。
  • 管理配置层(隐藏难点):多级审批规则配置、部门/岗位用户管理、流程节点可视化配置、操作日志记录。
  • 数据报表层(加分项):按部门/人员/时间维度统计报销金额,月度趋势,预算预警提醒。

很多初学者做这类系统,容易把注意力全放在“报销单的增删改查”上,忽略了审批流和权限控制,结果做出来像个简易记账本。实际上审批流程引擎才是这个项目的灵魂,后面我会专门拆开讲。

2. 技术选型与架构设计思路

2.1 前端:为什么选择微信小程序原生框架

微信小程序目前有原生框架、第三方跨端框架(如Taro、uni-app)等选择。对于企业内部工具,我的建议是优先使用原生框架,原因很实际:

  • 体积小、启动快。原生小程序没有额外的框架层开销,体验更接近原生应用。
  • 上手门槛低。只要会HTML、CSS、JavaScript,基本上几个小时就能上手,不需要引入新的编译链。
  • 调试工具成熟。微信开发者工具的报错信息、性能面板、真机调试,对新手非常友好。
  • 第三方跨端框架在维护成本上更有优势:如果项目本身需要覆盖多端(小程序端、App端),可以考虑,但很多在微信小程序端会出现兼容性磨人的问题。

如果团队之后想扩展更多端,再引入跨端框架从原生迁移也是可行的路径,但最初设计时建议保持“原生优先”,这也是目前主流技术社区认可的做法。

项目在目录组织上,我一般会用这种结构:

pages/ login/ # 登录页 index/ # 首页(报销列表) submit/ # 提交报销单 detail/ # 报销单详情 approve/ # 审批操作页 mine/ # 个人中心 admin/ # 管理端(部分页面可复用) utils/ request.js # 封装 wx.request auth.js # Token 管理 components/ image-uploader/ # 图片上传组件 status-tag/ # 状态标签组件

注意一个小细节:登录页和首页要做分离。不要让用户每次都走微信授权登录,而是通过静默登录(wx.login获取code)换取后端Token,这样用户打开小程序就是已登录状态,几乎无感知,体验会好很多。

2.2 后端语言选型:Java、PHP、Python、C# 怎么选

标题里提到了Java、PHP、Python、C#四种后端语言,我分别说一下适用场景,大家可以根据自己的熟悉程度和学习目标来决定:

技术栈优点适用场景
Java(Spring Boot)生态最完整、市场招聘需求大、企业级标准最推荐作为毕设/求职项目,简历上写出来的价值最高
Python(Flask/Django)开发效率极高、代码量少、适合快速出效果适合非计算机背景跨专业、时间紧的同学
PHP(ThinkPHP/Laravel)部署简单、运维成本低、老项目兼容性强适合熟悉PHP、追求快速上线的小团队
C#(ASP.NET Core)性能好、与Windows生态集成度高适合目标企业用.NET技术栈的场景

这里面没有绝对的“最好”,只有“最匹配你目标”的选择。如果从求职角度考虑,Java(Spring Boot)可能是最稳妥的选项,因为它的知识体系(Spring IoC/AOP、Maven、MyBatis等)是很多公司技术面必考的内容;如果纯粹是想快速把一个东西跑起来,Python(Flask)写起来确实是最快的,整个后端所有接口加起来可能只需一两天就能搭完。我下面统一用Java(Spring Boot)+ MyBatis Plus + MySQL来做核心示例,原因是它最主流、最容易找到对应资料。

2.3 整体架构图与数据流转链路

系统整体架构可以这样看:

  • 接入层:微信小程序前端通过HTTPS请求调用后端API(域名需要在小程序后台配置白名单)。
  • 网关层:Spring Boot拦截器统一处理Token校验、跨域、参数校验,拦截非法请求。
  • 服务层:用户服务、单据服务、审批流服务、文件服务、统计服务。
  • 数据层:MySQL存储业务数据(用户、审批单、流程记录),对象存储(如阿里云OSS/MinIO)存放发票图片,Redis缓存用户Token信息(也可用内嵌内存缓存替代,小项目不必强依赖Redis)。

数据流转用日常例子来说就是:员工填报的报销单,在数据库里是一条带状态字段的记录;当它被提交后,系统通过审批流引擎找到当前节点对应的审批人,并把单据ID和状态推送到审批人的待办列表;审批人执行通过操作后,系统更新单据状态并记录一条审批历史,再推送到下一个节点。整个过程本质就是一个状态机的状态迁移,这个思想大家一定要抓住。

3. 数据库设计与核心表结构

3.1 数据库表整体设计思路

报销审批系统的数据量并不大,单表记录数通常在几千到几万级别,但表之间的关联关系比较典型,适合用来理解数据库范式和外键关联设计。建议从以下几张表入手:

  1. 用户表(sys_user):存用户基本信息、微信openid(唯一标识)、部门ID、角色类型。
  2. 部门表(sys_dept):公司组织架构的简化版,便于按部门统计报销金额。
  3. 角色表(sys_role):员工、部门主管、财务、管理员四种角色。
  4. 报销类型表(expense_type):差旅费、餐费、办公用品、交通费等基础配置数据。
  5. 报销单主表(expense_order):核心业务表,每条记录对应一笔报销申请。
  6. 报销单明细表(expense_item):一张报销单可能对应多笔费用明细(如一次出差含交通+住宿+餐费)。
  7. 审批流配置表(approve_flow_config):定义不同报销类型对应几级审批、各级审批角色是谁。
  8. 审批记录表(approve_record):记录每一步审批的操作人、时间、意见、结果。
  9. 图片附件表(expense_attachment):发票原始图片的存储路径,关联到报销单或明细。

这种表结构保证了一类相对清晰的边界:主表存状态公共信息,子表存明细数据,流程表存审计记录。即使项目规模不大,这种分层也方便后续扩展(比如增加付款记录表、预算表)。

3.2 报销单与审批记录的核心字段设计

报销单主表(expense_order)的字段设计,我调过不少版本,现在稳定下来基本就是这样:

字段名类型说明
idbigint主键
order_novarchar(32)报销单号(格式:BX + 日期 + 随机数,唯一索引)
user_idbigint申请人ID
dept_idbigint申请人所属部门ID
expense_type_idbigint报销类型ID
total_amountdecimal(10,2)报销总金额
statustinyint状态:0草稿、1审批中、2已通过、3已驳回、4已打款、5已撤销
current_approver_idbigint当前待审批人ID(关键字段,查待办用)
apply_timedatetime提交时间
update_timedatetime最后更新时间
descriptionvarchar(500)报销事由/说明

审批记录表(approve_record)相对简单,我更习惯这样设计:

字段名类型说明
idbigint主键
order_idbigint关联报销单ID
approver_idbigint审批人ID
approve_leveltinyint审批级数(第几级)
approve_actiontinyint操作:1通过,2驳回
approve_commentvarchar(500)审批意见
create_timedatetime操作时间

这里要特别提一下current_approver_id这个字段的设计。很多人会困惑:审批流一看就是用流程配置表动态计算出来的,为什么还要在报销单上存一个当前审批人?原因很简单,就是查询性能。当上级领导打开小程序“待办审批”列表时,本质上就是一条SQL:SELECT * FROM expense_order WHERE current_approver_id = #{userId} AND status = 1,这条查询走索引秒出结果。如果不冗余这个字段,那每次都要先把流程配置和审批记录拼一遍再筛选,SQL复杂不说,数据量上来之后性能会明显下降。

3.3 审批流程配置表:灵活是设计出来的

这一块是很多教程里讲得最少的,但实际开发中往往是最容易返工的部分。如果审批逻辑是硬编码的(比如代码里写死“金额大于1000需要二级审批”),那上线后业务方提需求说“我们部门想改成三级审批”,你就得改代码重新发布。

更好的做法是把流程规则配置化,用一张表(approve_flow_config)来存储规则:

字段名类型说明
idbigint主键
expense_type_idbigint报销类型ID(不同类型可以走不同流程)
min_amountdecimal(10,2)区间最小金额
max_amountdecimal(10,2)区间最大金额
approve_levelstinyint审批总级数
node_config_jsontext每个级次的审批角色配置(JSON格式)

node_config_json字段里存的东西长这样(MySQL 5.7+直接支持JSON类型):

[ {"level": 1, "role": "dept_manager", "description": "部门经理审批"}, {"level": 2, "role": "finance_staff", "description": "财务复核"} ]

那审批流引擎拿这个配置做事情,逻辑就很清晰了:提交报销单时,根据报销类型和金额,查配置表拿到审批链;将第一条节点记录的审批角色对应的实际用户写到报销单的current_approver_id字段;审批人通过后,按节点顺序往后推进;如果当前节点是最后一个节点,直接把状态置为“已通过”。这样设计之后,业务调整流程只需要改数据库里的配置,完全不用动代码。

4. 后端核心接口与业务逻辑实现

4.1 登录鉴权:微信登录流程与Token无感知刷新

微信小程序登录和网页登录不同,它没有传统的用户名密码输入,核心流程是:

  1. 前端调用wx.login(),拿到一个临时凭证code。
  2. 前端把code发送到后端提供的登录接口。
  3. 后端拿code去请求微信的code2Session接口,换取openid和session_key。
  4. 后端拿着openid去数据库查用户,如果不存在则自动注册(新用户)。
  5. 后端生成一个自定义Token字符串,把它返回给前端并存入小程序的storage。
  6. 前端后续所有请求都在header里带上这个Token,后端根据Token识别用户身份。

这里我踩过一个坑:直接用openid做用户标识传到小程序端。看起来没毛病,但openid如果暴露在前端,相当于把用户的唯一标识暴露给所有能看到请求的人,存在伪装风险。正确的做法是后端生成一个随机Token(可以用UUID),服务端维护Token到用户ID的映射关系,前端永远只拿Token说话。Token可以设置有效期(比如7天),过期之后前端再用wx.login换新code重新获取,用户无感知。

Java端用拦截器(HandlerInterceptor)统一做Token解析,核心逻辑大致是:

@Component public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录接口 if (request.getRequestURI().contains("/login")) { return true; } String token = request.getHeader("Authorization"); @SuppressWarnings("unchecked") Map<String, Object> tokenInfo = redisUtil.get("TOKEN_" + token); if (tokenInfo == null) { throw new BusinessException(401, "登录状态已过期,请重新登录"); } // 通过ThreadLocal把当前用户上下文传给业务层 UserContext.set(tokenInfo); return true; } }

这里的逻辑很直白:每个需要登录的请求进来,先去Redis里查这个Token还能不能用;能就放行,不能就返回401,前端再触发wx.login换新Token。用Redis缓存Token而不是每次都查数据库,是为了减少数据库压力,当然如果项目规模很小、没有Redis环境,用一个内存ConcurrentHashMap也可以顶住,只是重启会丢,要接受这个取舍。

4.2 报销单提交:事务、状态机与并发控制

报销单提交这个接口是整个系统里最容易出问题的地方。因为在一次提交操作里,它需要同时干好几件事:

  1. 保存报销单主表数据(state为“审批中”)。
  2. 保存报销单明细数据(多张子表记录)。
  3. 根据金额和类型查审批流配置,生成审批链。
  4. 把第一级审批人的ID写到主表的current_approver_id。
  5. 记录一条审批历史(提交动作),向审批人发送订阅消息通知。

这五步任意一步失败,都不能让数据处于“半提交”状态,所以必须用@Transactional事务把整个过程包起来。Spring Boot里面这很简单,就是在一个方法上加注解。但我提醒一下,事务必须在最外层入口类方法上加,如果加在内部调用方法上,可能因为方法自调用绕过AOP代理,导致事务失效。这是一个很经典也很隐蔽的问题。

并发控制同样重要。用户可能双击提交按钮,或者手机网络延时导致前端重试,同一个报销单瞬间发来两个POST请求。如果后端不做幂等控制,数据库里就会多出一条重复的报销单。我的解决方案是:在提交接口入参里加一个clientRequestId(前端每次进入提交页生成一个UUID),后端在Redis里用SETNX命令把该ID标记为“已处理”,重复请求直接被拦截。操作顺序大致是:

// 伪代码 if (redis.setIfAbsent("CLIENT_REQ_" + clientRequestId, "1", 10分钟)) { // 第一次进入,正常的保存逻辑 } else { // 重复请求,直接返回"已受理,请勿重复提交" }

4.3 审批流转:状态机的核心代码实现

审批通过和驳回是整个业务逻辑的重头戏。我在实际项目里把这段逻辑抽象成了一个独立的ApprovalFlowService,核心代码逻辑大致如下(简化版,实际还要加各种校验):

public void approve(ApprovalRequest req) { ExpenseOrder order = expenseOrderMapper.selectById(req.getOrderId()); // 校验状态:只有审批中才能继续 if (order.getStatus() != ExpenseOrderStatus.PROCESSING) { throw new BusinessException("当前单据状态不允许审批操作"); } // 校验权限:当前登录人必须是当前待审批人 if (!order.getCurrentApproverId().equals(UserContext.getUserId())) { throw new BusinessException("您不是当前节点的审批人"); } // 查询该单据的审批链(也可以从缓存读) FlowConfig flowConfig = getFlowConfig(order.getExpenseTypeId(), order.getTotalAmount()); List<NodeConfig> nodes = parse(flowConfig.getNodeConfigJson()); Integer newStatus = null;// 后续要改成的状态,先声明 int currentLevel = getCurrentLevel(order.getId()); if (req.getAction() == ApprovalAction.REJECT) { // 驳回:无论当前是第几级,状态都回到驳回 newStatus = ExpenseOrderStatus.REJECTED; } else { // 通过:判断是否还有下一节点 if (currentLevel >= nodes.size() - 1) { newStatus = ExpenseOrderStatus.APPROVED; // 最后一级也通过了 } else { newStatus = ExpenseOrderStatus.PROCESSING; // 把待审批人推进到下一级 NodeConfig nextNode = nodes.get(currentLevel + 1); order.setCurrentApproverId(findUserIdByRole(nextNode.getRole())); } } // 更新单据状态 order.setStatus(newStatus); expenseOrderMapper.updateById(order); // 记录审批历史 ApproveRecord record = new ApproveRecord(); record.setOrderId(order.getId()); record.setApproverId(UserContext.getUserId()); record.setApproveLevel(currentLevel); record.setApproveAction(req.getAction()); record.setApproveComment(req.getComment()); approveRecordMapper.insert(record); // 如果最终驳回或通过,可以发通知给申请人 sendResultNotify(order, newStatus); }

说一下这段代码里比较关键的两个校验点。第一个是状态校验,只有status为“审批中”的单据才允许审批,避免出现“已驳回的单子又被审批人通过”这种逻辑上的矛盾。第二个是权限校验,当前登录人必须等于单据的currentApproverId,这个校验必须放在后端,不能只在前端做按钮隐藏。很多安全漏洞都是因为后端没校验,前端隐藏了按钮但接口照样能被调用。

4.4 图片上传与静态文件服务的处理

发票拍照上传是报销场景的刚需,处理不好会成为整个体验的短板。微信小程序上传文件和H5不太一样,它有自己的wx.uploadFile接口,需要在小程序端调用:

wx.chooseMedia({ count: 9, mediaType: ['image'], sourceType: ['camera', 'album'], success(res) { const tempFilePath = res.tempFiles[0].tempFilePath; wx.uploadFile({ url: 'https://api.example.com/api/upload', filePath: tempFilePath, name: 'file', formData: { token: getToken() }, success(uploadRes) { const data = JSON.parse(uploadRes.data); // data.url 就是上传后的图片地址 } }); } });

后端接收文件后,有几个规范建议:

  • 重命名:不要直接用原始文件名,而是用“日期+随机字符串”重命名,避免中文文件名和路径遍历问题。
  • 限制文件类型和大小:只允许jpg/png/webp,单张不超过5MB,前端chooseMedia有sizeType限制,后端也要二次校验,防止绕过前端直接传大文件。
  • 存储位置:如果是独立服务器,建议存到本机磁盘上一个专门的目录,不要写进项目发布目录里面,这样重新部署时不会丢文件;发布目录在部署时会被覆盖,到时候好不容易收集的照片全没了,我实际见人踩过这坑。
  • 访问路径:需要给静态资源目录配一个HTTP访问路径(如/upload/**),并且设置跨域配置,否则真机预览时图片可能加载不出来。

如果公司有条件用阿里云OSS等对象存储,那就更省心,后端只负责生成签名URL,前端直传/回显都走云存储,服务器压力小很多。

5. 小程序端核心页面与交互设计

5.1 登录页与身份识别:头像昵称和普通人要做区分吗

小程序登录页设计很有意思。市面上很多教程直接推荐用wx.getUserProfile拉取头像昵称,但后来微信官方调整了规则,用户拒绝授权时依然可以正常使用。所以更合理的做法是:

  1. 不强制用户必须“授权头像昵称”,优先用wx.login静默登录。
  2. 用户基本信息(姓名、部门)最好是由管理员在后台统一录入或让用户首次填个简单表单,而不是依赖微信提供的昵称。
  3. 头像可以用微信默认,也可以后续在个人中心让用户自愿设置。

为什么这样建议?因为企业内部系统最看重的是真实身份。微信昵称可能叫“我是xxx”、“A小王”这类乱七八糟的,后端如果拿昵称来做审批人判断,很容易出问题。正确的做法是:微信openid只用来做登录凭证和账号关联,真实姓名、工号、部门通过企业人事数据导入或用户自助填写维护。

5.2 首页报销列表:状态视觉化与下拉刷新

首页的设计决定第一印象。一个简洁有力的方案是:

  • 顶部放一个“我要报销”的按钮,引导提交。
  • 下面是历史报销单列表,用四个状态维度去展示:待审批、已通过、已驳回、全部。
  • 每一条报销单卡片上显示:时间、报销类型、金额、当前状态标签。
  • 状态标签用不同颜色区分:审批中是黄色,已通过是绿色,已驳回是红色,已打款是蓝色。

还有一个容易被忽略的细节:列表一定要支持下拉刷新和触底加载。企业用户在公司里用手机网络环境不稳定,如果数据没有及时刷新,用户会认为“我提交的单子是不是丢了”。下拉刷新直接调用wx.startPullDownRefresh配合onPullDownRefresh生命周期,成本很低,但能明显提升安全感。

审批人的待办列表其实可以复用这套列表组件,只需要在请求参数上区分:?status=待审批&approverId=当前用户ID,不需要重新写一套页面。这里就是前面数据结构设计得好的红利。

5.3 表单提交页:金额格式、必填校验与草稿保存

报销表单的字段不用太多,但有几个细节要注意:

  • 金额输入:小数位限制两位,单位默认“元”,别让用户自己输小数点以外的内容,用type="digit"调起数字键盘。
  • 费用明细:支持动态添加多行(出差涉及交通+住宿+餐饮就很常见),每一行包含费用类型、金额、说明。总金额由前端实时汇总显示,提交时后端再计算校验一次,防止前端计算错误。
  • 发票必传:报销单原则上必须上传发票图片,后端接口加个非空校验,前端用组件提示。但注意有些场景(比如无票补贴)需要豁免,可以在后端加一个hasInvoice字段来标记,而不要把所有单子都卡死。
  • 草稿保存:这是一个很提升好感度的功能。用户填到一半被别的事情打断是家常便饭,如果页面一关数据全没,体验非常糟糕。做法是:表单组件在onUnload时把当前页面所有表单值存到本地storage,下次进入如果检测到草稿且不是刚提交过,就询问用户“发现草稿,是否恢复?”这个逻辑不算复杂,但对用户感知的提升非常明显。

5.4 审批操作页:详情、意见与图片预览

审批人点开一个待办,需要能看到什么?我认为至少包括:

  1. 报销单基本信息(单号、类型、申请人、部门、总金额)。
  2. 费用明细列表(每一项的名称、金额、说明)。
  3. 发票图片缩略图,点开可放大预览(wx.previewImage)。
  4. 申请人的提交备注(如果填了的话)。
  5. 这个单据之前所有审批节点的流转记录(级别、审批人、时间、意见)。

在审批操作上,推荐“通过”和“驳回”使用两个不同方向的按钮,驳回时强制必填原因,这样能避免审批人说一句“不同意”就让员工一脸懵地猜原因。实际到前端实现的时候,弹层对话框里加个必填校验就好。

6. 系统权限设计与多级审批策略

6.1 基于角色的访问控制(RBAC)设计

这个系统里存在不同角色,权限控制的核心是RBAC(Role-Based Access Control),即把“权限”绑定到“角色”上而非直接绑定到“用户”上。具体落地到功能侧:

  • 普通员工角色:只能查自己的单子,只能新增报销单,不能看到其他人的单据。
  • 部门主管角色:能查看本部门所有单据,能处理“部门经理审批”节点的待办。
  • 财务角色:能查看所有状态为“审批中/已通过”的单据,能处理“财务复核”节点的待办,且能看到发票图片。
  • 管理员角色:可以管理用户、部门、审批流程配置、查看所有单据和数据统计。

在后端实现上,我建议用一张简单的用户角色映射表,然后在Service层通过@RequiresRole之类的注解做拦截。如果没用现成框架,可以自己写一个方法校验:checkRole(UserContext.getUser().getRole(), "finance"),不满足就抛403异常。这里想强调的一点是:前端根据角色隐藏按钮只是体验层的优化,真正的安全防线永远在后端接口的权限校验。即使是毕设项目,也要有这个意识,因为面试官非常容易问“你这个系统的权限是怎么控制的,前端隐藏和后端校验有什么区别”。

6.2 多级审批的可视化与流转规则

多级审批最常见的是这两种模式:

  • 固定级次:比如金额无论多少,固定“部门经理->财务”两级审批。
  • 金额区分级次:金额小于1000元只要部门经理审批;1000到5000需要部门经理+财务;超过5000还需要总经理。

实现上,第二种更接近真实企业的需求,也能在答辩或演示时讲出亮点。做法就是我前面提到的审批流配置表approve_flow_config,用费率区间和级次配置组合就能满足。

需要特别注意的是审批流转中“撤回”和“驳回后重新提交”的处理。真实的业务场景中,员工提交错了金额或者填错了类型,希望撤回;或者单据被驳回后,员工改完信息再重新提交。这两块逻辑如果处理不好,会在演示时露馅。我的经验是:

  • 撤回:只有单据状态为“审批中”,且当前待审批节点还是第一级(还没被人处理过),才允许申请人撤回;一旦有任何人操作过,就不允许撤回了。
  • 驳回后重新提交:把整个审批链重置,重新从第一级开始走。相当于一份新单据但保留了原始的单据号和历史记录,这样审计的时候能看到“这张单子在历史上被驳回过一次”。

6.3 操作日志:没有日志的系统不敢上线

日志这个点容易被新手忽略,但它对报销系统的价值非常重要。财务系统最怕扯皮:为什么这笔报销过了?是谁批的?批准的时候有没有附件?如果系统里只存最终状态,出了纠纷根本说不清。

所以我在设计时加了一张operate_log表,里面存:操作人ID、操作人姓名、操作类型(提交/审批通过/驳回/撤回/修改)、目标单据号、操作时间、IP地址(可选)。前端和后端各自记录一层,后端用AOP切面统一记录,简单可靠。

还有一个实用小技巧:日志表的数据不跟着业务数据删掉。哪怕财务漏录了一张单子或者管理员删除了某张测试单,日志里也要保留痕迹。所以删除报销单的功能,要么不做(只做状态流转),要么做成逻辑删除(加个deleted字段),尽量不物理删除。

7. 测试、部署与上线踩坑实录

7.1 本地开发环境搭建:从下载工具到跑通登录

一个典型的最小开发环境,需要准备这些东西:

  • 微信开发者工具:从官网下载,用自己微信号扫码即可登录调试小程序。
  • JDK 8+:Java后端本地开发环境。
  • MySQL 5.7+:数据库,建表导入sql脚本。
  • Redis(可选):用于Token和缓存,不装也可以用内存替代。
  • 内网穿透工具:用于手机上真机预览时,让小程序能访问到你电脑上的后端服务。

这里有个常见坑:微信开发者工具的“不校验合法域名”选项。开发阶段勾上它,请求任意域名的接口都能通;一旦“上线”按钮提交审核时,这个选项就不生效了,必须在小程序后台配置HTTPS合法域名。很多新手在开发环境跑得很欢,一到真机预览连接口都连不上,就是没分清开发环境校验和上线校验的区别。

还有一个小建议:后端如果放在本机,Swagger接口文档一定要开。springfox或者springdoc依赖一引入,访问/doc.html就能看到全量接口列表,前后端联调效率翻倍。毕设答辩的时候打开Swagger页面,评审老师一眼就能看到你定义了哪些接口,效果比口述强得多。

7.2 云开发版与自建后端:取舍分析

提到小程序,很多人自然会想到微信云开发(CloudBase)。云开发提供了云数据库、云函数、云存储,可以完全不搭后端。那它适合这个项目吗?我的分析如下:

  • 云开发版:开发极快,云函数写逻辑,数据库零运维,一个多月就能上线。但云开发和传统后端语言的生态差异大,如果目标是求职Java/Python开发,写云函数对你简历帮助有限;如果只是产品Demo或课程设计,那云开发准没错。
  • 自建后端版:用Java/Python写一套标准后端,需要自己搞定服务器、数据库、部署,过程更“工程化”,知识迁移性强。面试时可以明确说“我完整实现了一套前后端分离的API服务”,说服力更强。

如果做毕设答辩,我建议自建后端;如果你只是帮某个小公司快速落地一个工具,云开发效率高得多。这两种路径做的系统,对外展示时完全可以各取所长。

7.3 常见错误排查速查表(呕心沥血整理)

这部分是我实际联调过程中最常遇到的问题,压箱底的东西直接公开。

症状可能原因排查步骤
wx.request报406/403前端请求头未带Token或Token过期先断点看请求header,再查Redis里Token是否存在、是否过期
登录后每次刷新都掉线Token全局变量存得不对或接口路径拼错检查Token有没有写入storage,检查请求拦截器有没有统一读取header
真机预览图片加载不出来HTTPS域名未配置合法域名去小程序后台把图片所在域名加入downloadFile合法域名
提交报销单提示“系统繁忙”后端事务回滚看异常日志,多半是审批流配置解析失败或明细插入为空
待办审批列表显示为空status字段值不对或currentApproverId没更新手动在数据库里UPDATE一条订单,改到正确的节点人,再查列表是否显示
订阅消息发不出去模板ID配置错误或用户未订阅检查小程序订阅消息模板ID、用户是否点击过订阅授权
部署后接口可以访问但小程序请求失败跨域或HTTPS证书问题后端加CORS配置,域名证书确认是有效HTTPS

这里尤其想讲一下订阅消息的问题。微信的订阅消息和公众号模板消息不一样,不是随便就能推的。用户在触发某个事件时必须主动授权订阅,且授权一次只能发一条。所以合理的交互是:在用户提交报销单的按钮上,让用户同时点击“订阅审批结果通知”,点击量允许订阅几次,才能保证后续发通知的次数足够。另外订阅通知经常有1分钟以上的延迟,测试时不要以为没发出来就是代码有问题,等一会再看。

7.4 演示录像的制作小技巧

如果这个项目是拿来面试演示或当作品集展示的,我强烈建议大家录一个3分钟短视频,而不是让人直接扫小程序码。视频脚本可以这样安排:

  1. 前10秒:首页展示报销列表和核心功能入口。
  2. 30秒:演示提交一个报销单,包含上传发票、填明细、提交。
  3. 40秒:切换账号到审批人视角,演示待办审批、通过/驳回。
  4. 30秒:演示财务角色复核通过。
  5. 最后30秒:展示“我提交的单据的审批状态流转记录”,说明每一步都有日志可追溯。

录屏时记得把网络请求面板打开(或把Charles等工具的请求抓包窗口放在副屏),评审的人看到正常的HTTP请求、后端日志、数据库变化,会觉得这个系统的完整性远超普通课程设计。

8. 如何基于这个项目快速变体延伸

8.1 从报销系统扩展成通用审批平台

报销单只是审批流的一个业务载体。当我把审批流引擎独立出来之后,其实可以直接换成请假单、采购申请单、用款申请单。

做法上只需要抽象几张表:业务表单表(存各类表单的公共字段:表单类型、表单标题、状态、创建人等)+表单明细JSON(不同类型表单的字段用JSON存)+审批流配置表(按表单类型区分流程)。这样做完,用户就可以在同一个系统里发起不同类型的审批,而审批引擎完全通用。

这条扩展路径的实际价值很高:公司内部原来有三套系统分别管报销、请假、采购,如果能看到一个系统把它们统一成一站式审批工作台,那项目含金量会明显上升。

8.2 增加数据统计与预算控制

企业报销系统如果只有审批流,会显得有点单薄。加一个统计看板之后,价值提升会很明显:

  • 按部门统计:本月各部门累计报销金额,柱状图对比。
  • 按人员统计:谁报销最多、哪类费用超标。
  • 按费用类型统计:差旅费占比、餐费异常走高预警。
  • 预算联动:如果某个部门本月报销金额已经超过预算的80%,提交新报销单时前端给出提示;超过100%则不能提交(或需要更高级别审批)。

统计接口不用复杂,一个SQL中的GROUP BY就能实现:

SELECT dept_id, SUM(total_amount) AS total FROM expense_order WHERE status IN (2, 4) AND DATE_FORMAT(apply_time, '%Y-%m') = #{month} GROUP BY dept_id;

重点是把数据用echarts等图画出来。小程序原生不建议直接引全量echarts,体积太大,用ec-canvas那个组件包刚好合适。图表呈现出来的效果,在答辩或演示时是很加分的,因为它让人一眼看出来“这不是一个简单CRUD”。

8.3 移动端适配与性能优化

企业用户使用的手机终端差异比较大,从iPhone 12到几百块的安卓千元机都存在。为了保证体验一致,有两个优化点值得做:

  • 图片压缩:发票图片通常像素很高,但上传5MB到服务器既慢又耗流量。小程序端wx.compressImage压缩一下(质量80%、宽1500px),一张图基本能压到300KB以内,体验差距非常明显。
  • 列表分页:前端滚动加载(每页20条),后端LIMIT分页或游标分页。不要一次把几千条记录全查出来,小程序setData大数据量会明显卡顿。
  • 骨架屏:首次进入首页时,用一个简单骨架屏组件占位(灰色块模拟卡片),等数据返回后替换,视觉上比一直转圈好很多。实现上就是一个动态伸缩的div加上wx.showSkeleton这类简易组件,成本很低。

9. 风险提示、避坑心得与项目学习路线

9.1 合规与个人隐私数据保护的注意事项

企业内部系统必然涉及员工个人信息和财务数据,有几个合规红线要特别注意:

  • 不得采集与业务无关的个人信息。小程序端如果不需要真名,就不要强制收集身份证号等信息,避免触碰个保法边界。
  • 发票图片属于敏感数据:上传后要设置访问权限,不能让人通过“猜URL”的方式浏览到别人的发票;存储目录按日期分层、文件名随机化是很好的防遍历手段。
  • 系统上线前做好等保自评估:如果数据是真实的财务数据,不能随便用个免费服务器裸奔;有条件就加HTTPS、数据库备份、安全组白名单。

毕设项目如果用真实数据演示,建议把发票图片换成打码或者“示例发票”的图片,脚本数据单独建一套测试数据,避免真实数据泄露风险。

9.2 毕设避坑:这些问题最容易导致返工

基于我这些年见过很多学生项目的经验,最容易踩坑的地方集中在以下几处:

  • 表结构设计阶段没人思考审批流,后面写代码时才发现流程和业务全耦合,代码写来写去不可维护。
  • 不做权限校验,页面全靠按钮隐藏来“保护”,抽查接口时一抓一个准。
  • 照片上传路径写死在生产目录,重新部署就丢文件,也很容易踩。
  • 没有做日志,被问“谁在什么时间改了这条数据”就答不上来。
  • 多级审批节点配置硬编码在代码里,改个审批级次要重新改代码发版。
  • 异常体系不统一:有的接口返回JSON,有的直接500,前端统一拦截无从下手。

这些问题看起来都是细节,但它们几乎决定了这份代码在职场面试官或答辩老师心中属于“认真做过”还是“随便糊弄”。我的建议是:哪怕时间再紧,也把审批流引擎、日志、异常处理这三样先落地,其他可以慢慢补。

9.3 面向求职的学习路线建议

如果你的目标是拿这个项目去找开发岗位,在完整实现的基础上,我建议再从三个维度加深理解:

  • 把项目文档写好:README里写清楚架构图、接口文档、部署步骤、核心难点。有条件的可以画一张简洁的架构图,让面试官30秒就能看懂。
  • 针对一个点深入:比如审批流引擎的状态机设计,可以拿它跟Activiti/Flowable等成熟工作流引擎做个横向对比;能说清楚自己实现和Flowable这类引擎的差别、优缺点,面试深度马上不一样。
  • 准备3-5个“亮点问答”:例如“你的审批流如果并发通过会怎样?”“你的Token过期了怎么刷新?”“如果报销单涉及预算控制,怎么设计?”这些问题的答案在这篇文章里其实都覆盖到了,关键是自己要能用口头表达出来。

9.4 最后的一点个人体会分享

做了不少企业内部工具之后,我个人最大的体会是:这类项目表面上是在写代码,实际上是在管理“期望和不确定性”。员工希望报销尽快到账,领导希望审批流程严格合规,财务希望账目清晰可查。写代码的人如果没有从这三类角色的角度审视系统,做出来的工具一定在某处别扭。

我记得第一次做一个审批系统的时候,忽略了一个很小的场景:报销人被驳回后,改动数据重新提交,历史审批记录被我清空了。结果财务复核时看到一张单子金额很大却没有历史痕迹,当场质疑有没有走完流程。后来我把“历史版本保留”和“驳回记录不删除”当成铁律写进开发规范里,后面所有版本都没再被这个问题困扰过。

如果你是第一次做这类管理系统,不要焦虑,先按照需求梳理、表结构、后端接口、小程序页面这个顺序稳步推进,遇到不会的模块先画出来再动手写,基本两周左右就能跑通全套流程。报销审批系统作为练手项目,难度适中、覆盖面广,非常值得认真做一遍。祝所有读者都能顺利跑通自己的版本。

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

Spring Boot项目用jpackage打包成exe/msi安装包完整指南

1. 聊聊天天被追着要“一个能双击运行的东西”的那种痛 你发出去的 jar 包&#xff0c;对开发来说只是一条命令的事&#xff0c;对收到它的人可能就是一场小型灾难。我见过太多开发同事把 Spring Boot 的 fat jar 丢给前端、测试或现场同事&#xff0c;然后补一句“本地装个 JD…

作者头像 李华
网站建设 2026/10/16 9:51:29

day1编程入门三件套:面积周长、数列和、解方程背后的核心细节

第一次看到“day1&#xff1a;求长方形的面积和周长、数列和、解方程”这种题单时&#xff0c;我第一反应是&#xff1a;这也太基础了吧&#xff1f;但实际带过几个新人、也自己在不同语言里重新写过这些题目之后&#xff0c;我反而觉得这种“入门三件套”恰恰是最值得反复咀嚼…

作者头像 李华
网站建设 2026/10/16 9:50:53

IPv6与IPv4地理定位差异全解析:从原理到业务调优

两个月前我把一个对外服务切成了双栈接入&#xff0c;上线一周就遇到一个诡异场景&#xff1a;IPv4 用户的归属地判断基本靠谱&#xff0c;城市级准确率能做到九成上下&#xff1b;但只要是走 IPv6 进来的请求&#xff0c;归属地经常整段跳变&#xff0c;昨天还指向华东某市&am…

作者头像 李华
网站建设 2026/10/16 9:50:43

易语言+YOLO自动化实战:从目标检测到桌面操作的完整技术链路

前一阵有个圈内朋友发来一个项目标题&#xff0c;开头四个字就把我勾住了——“易语言YOLO脚本源码”。后面还缀着“自动搬砖工具”和“虚拟机版本地一体版”。懂行的人一眼就能看出来&#xff0c;这不是前端又冒出什么新框架&#xff0c;而是一套靠视觉识别驱动的自动化工程方…

作者头像 李华
网站建设 2026/10/16 9:49:07

具身导航入门指南:从语义建图到Sim-to-Real的完整技术路线

对于刚接触具身智能或者机器人导航方向的人来说&#xff0c;“具身导航”这四个字挺唬人&#xff0c;一听就像是融合了计算机视觉、机器人学、强化学习、SLAM的一大锅粥。网上相关的课程资源、开源项目、Demo视频其实不少&#xff0c;但真正能让人系统地从零开始搞清楚“我到底…

作者头像 李华
网站建设 2026/10/16 9:45:18

单机与洗烘灌封整线采购成本差异分析

单机与洗烘灌封整线采购成本差异分析"买单机省钱还是上整线省钱&#xff1f;"这个问题不能只看设备标价——单机便宜是真便宜&#xff0c;但省下的钱会以别的形式慢慢花出去。这篇把单机方案和整线方案的成本拆到细处&#xff0c;算一笔三年总账&#xff0c;看完你自…

作者头像 李华