ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于微信小程序的办公用品管理系统设计与实现

基于微信小程序的办公用品管理系统设计与实现

最近刚完整做完一套“基于微信小程序的办公用品管理系统”的交付,包含源码、文档、调试和讲解服务。这算是办公自动化类项目里很典型的一个方向,也是很多在校生在课程设计或毕业设计中会碰到的选题。这个系统解决的核心问题特别实在:公司或学校内部的办公用品申领,以前靠纸质登记、Excel表格、口头报备,流程混乱、库存对不上、谁领了多少也查不清。用小程序做前端,员工直接用手机就能查库存、提交领用申请,管理员在后台完成审批和库存管理,整个流程清晰可控。

这篇文章会把整个项目的设计思路、技术选型、核心代码、调试心得和文档讲解交付经验完整梳理一遍。适合正在做类似选题的同学,也适合刚接触小程序开发、想了解一个完整项目如何落地的开发者。你会看到整个系统从业务分析到数据建模、从接口实现到真机调试的完整链路,所有内容和代码都基于实际跑通的项目,可以直接参考。

1. 业务场景与功能设计拆解

1.1 办公用品管理的核心痛点

做系统前先把业务想清楚,这是整个项目能不能让人信服的关键。办公用品管理看似简单,实际上的痛点非常具体:员工领笔、领纸、领文件夹,以前要跑到行政那里说一声,行政翻一下纸质台账或者Excel表格记录一下。如果公司有几十上百号人,这种模式立刻暴露问题——台账记录不完整、实际库存和账面对不上、临时查看数量只能靠人工清点、月底统计消耗量要翻半天记录。

我把这个业务拆成了三个核心诉求:谁在什么时候领了什么用品、库存还剩多少、领用是否合规需要审批。围绕这三个诉求,系统必须有清晰的用户身份边界,也就是角色权限管理。员工可以提交领用申请、查看自己的申请记录,管理员可以处理审批、管理用品库存、查看全公司领用数据。还有一个看板角色也可以考虑,比如领导层只看统计数据,但这在小项目里通常会并入管理员。

1.2 功能模块与权限矩阵设计

整个系统的功能可以用一张权限矩阵讲清楚:

功能模块普通员工管理员
用品分类与列表浏览查看增加/删除/修改
库存查询查看(可用数量)查看(含预警状态)
领用申请提交/撤销审批/驳回
领用记录查看自己的记录查看全量记录
用户管理无添加/禁用账号
数据统计无按月份/部门统计

业务闭环是:员工提交申请→管理员收到待审批请求→管理员同意后库存扣减→员工看到审批结果。这里有一个重要的设计点:什么时候扣库存。有人会把库存扣减放在提交申请时,但这样会导致申请被驳回时库存已经不对了。正确做法是扣减动作放在审批通过之后,驳回或撤销时则不动库存。

考虑到不同公司的实际诉求,还可以加上“办公室地点”这个维度,例如总部一楼有库房,分部二楼有储位,用品表里预留一个location字段标识存放位置,领用时的出库位置就更准确。虽然这会增加一定的复杂度,但对演示项目来说反而是个不错的加分点。

1.3 页面流程与交互设计

小程序端我规划了五个主要页面:登录页、首页(用品列表)、用品详情页、申请记录页和个人中心。首页不能是简单列表,要支持顶部按分类筛选,同时把库存数量用明显颜色标识出来,库存充足的显示正常色,低于预警值的用红色提醒,这样员工一看就知道哪些可以放心申请,哪些可能没货。

申请流程是:用户从用品列表点进详情页,看到规格、单位、当前库存数,点击“申请领用”后填写数量和使用原因。这里要防止用户提交明显不合理的大数量,前端要校验申请数量不能超过当前库存,后端接口还要再做一次校验。双端校验是这类项目的基本功,只做前端校验很容易被绕过。

个人中心里展示当前登录用户的信息和申请记录列表。每一条记录用状态标识区分:待审批、已通过、已驳回。已通过的可以查看审批时间,已驳回的显示驳回原因。

2. 技术选型与整体架构

2.1 为什么选择微信小程序加后端服务的模式

办公用品管理系统的实际使用场景决定了它的平台选择:用户是公司内部员工,需要用尽可能轻的方式访问。微信小程序天然满足这个需求——用户不用安装单独App,打开微信就能用,用完即走,非常契合内部工具的使用习惯。

小程序再搭配一套后端服务和数据库,构成完整的前后端分离架构。有些同学在做这类项目时想用云开发,把数据库和云函数都放在微信云托管上。这样确实省事,不用搭建本地服务,但存在两个问题:一是云开发环境免费额度有限,超出要付费;二是如果用于课程设计或简历项目,传统的前后端分离结构更能展示数据库设计能力和接口开发能力。所以我选择自建后端服务。

2.2 后端框架与数据库设计方案

后端我用的是Spring Boot加MyBatis Plus加MySQL的组合。Spring Boot是目前Java开发最主流的框架,起步依赖很方便,内置了Tomcat,打包成Jar就可以直接部署。MyBatis Plus在MyBatis基础上做了增强,单表CRUD几乎不用写SQL,节省大量精力。MySQL是关系型数据库里最通用的,适合存储结构化的业务数据。

数据库设计了五张核心表:

  • user:用户表,包含openid、姓名、部门、角色等
  • category:用品分类表,包含分类名称、排序
  • material:用品表,包含名称、规格、单位、库存总量、库存预警值、所在位置、图片URL、是否启用
  • apply_record:申请记录表,包含申请用户ID、关联用品ID、申请数量、申请原因、审批人ID、审批状态、审批意见、创建时间
  • operation_log:操作日志表,记录关键操作

这里要说一下openid的用法。用户第一次通过wx.login拿到code,后端调用微信接口换取openid,这是用户在微信体系里的唯一标识。openid要存在user表里,下次登录时通过openid找到对应用户,而用户名和密码这个传统方案的体验完全不同——员工不用注册,直接进入系统,系统自动根据openid识别身份。

2.3 请求链路与技术要点

小程序前端通过wx.request发起HTTP请求到后端接口,后端统一接收后分发到不同Controller层。请求链路的顺序是:请求进入网关(项目里没有单独网关,Spring Boot就是入口)、经过Controller参数接收和统一响应包装、Service层处理业务逻辑、Mapper层访问数据库、结果返回前端。

技术要点上有两个需要特别注意。第一是跨域问题,小程序端请求域名必须是HTTPS协议才能在正式环境下使用,但开发阶段可以在微信开发者工具里勾选“不校验合法域名、web-view(业务域名)、TLS版本以及HTTPS证书”,这样本地调试就能用http协议加上局域网IP访问后端。第二是后端需要实现简单的token机制,用户登录后后端生成一个随机token返回给前端,前端把token存储在storage中,后续每个请求都在header里带上token,保证用户身份可识别。

3. 核心模块实现与代码解析

3.1 登录鉴权模块

登录鉴权是整个系统的地基,所有接口都要依赖当前用户身份。微信小程序的登录流程分三步:前端调用wx.login获取临时code、后端根据code加appid和secret调用微信的接口换取openid和session_key、后端根据openid查询用户,如果存在就生成token返回。

这里需要看一次关键的代码逻辑。后端部分:

@PostMapping("/login") public Result login(@RequestBody LoginRequest request) { String code = request.getCode(); // 调用微信接口凭证校验接口换取openid String openid = authService.getOpenidByCode(code); if (openid == null) { return Result.error("登录失败"); } User user = userService.findByOpenid(openid); if (user == null) { // 新用户默认注册为普通员工角色 user = userService.register(openid, request.getNickname()); } if (user.getStatus() == 0) { return Result.error("账号已被禁用,请联系管理员"); } String token = jwtUtil.generateToken(user.getId(), user.getRole()); return Result.success(token); }

前端小程序对应部分:

wx.login({ success: async (res) => { const code = res.code; const loginRes = await request.post('/api/login', { code }); if (loginRes.code === 1) { wx.setStorageSync('token', loginRes.data.token); wx.setStorageSync('userInfo', loginRes.data.userInfo); } } });

这一段里有个值得留意的点:普通员工首次登录时应该自动注册,不能因为没有账号就拒绝进入。管理员的账号则需要预先由系统初始化完成。我在项目初始化时写了一个数据脚本,预置一个管理员账号,并将第一次通过该账号登录的用户赋予管理员角色。这样做的好处是项目演示时不用额外注册多个账号再切换角色,体验上很顺滑。

3.2 用品列表与分类查询

用品列表是员工最常访问的页面,接口设计上要考虑分类筛选、分页加载和库存状态标识这三个能力。后端MyBatis Plus的QueryWrapper可以很方便地组合查询条件:

public PageResult<MaterialDTO> listMaterial(Long categoryId, Integer page, Integer size) { Page<Material> p = new Page<>(page, size); LambdaQueryWrapper<Material> wrapper = new LambdaQueryWrapper<>(); if (categoryId != null && categoryId > 0) { wrapper.eq(Material::getCategoryId, categoryId); } wrapper.eq(Material::getStatus, 1).orderByAsc(Material::getSortOrder); Page<Material> result = materialMapper.selectPage(p, wrapper); // 转换为DTO,附带库存状态标识 List<MaterialDTO> dtoList = result.getRecords().stream() .map(m -> new MaterialDTO(m, m.getStock() <= m.getWarningValue())) .collect(Collectors.toList()); return new PageResult<>(dtoList, result.getTotal()); }

前端页面效果上,用品卡片展示图标、名称、规格、库存数量和所在位置。库存充足显示“可申请”,不足显示“库存紧张”并标红。每张卡片点击后进入详情页,这个详情页同时承载领用申请功能,所以页面结构分上下两块:上方物品信息,下方申请表单。

3.3 领用申请与审批状态机

领用申请是整个系统的业务核心,状态管理决定了系统逻辑是否完善。我把状态设计成四个值:1待审批、2已通过、3已驳回、4已撤销。初始创建时为1,管理员审批时改为2或3,用户按申请记录里的撤销操作改为4。状态流转有明确边界:只有待审批状态才能被修改,已审批通过的单子不能再回退。

提交申请的后端逻辑:

@PostMapping("/apply") public Result apply(@RequestBody ApplyRequest request, @RequestHeader("token") String token) { Long userId = authService.getUserIdByToken(token); Material material = materialService.getById(request.getMaterialId()); if (material == null || material.getStatus() == 0) { return Result.error("用品不存在或已下架"); } Integer stock = material.getStock(); if (stock < request.getCount()) { return Result.error("库存不足,当前剩余:" + stock); } ApplyRecord record = new ApplyRecord(); record.setUserId(userId); record.setMaterialId(request.getMaterialId()); record.setCount(request.getCount()); record.setReason(request.getReason()); record.setStatus(1); record.setCreateTime(new Date()); applyRecordMapper.insert(record); return Result.success("申请已提交,等待管理员审批"); }

审批通过后的库存扣减要考虑并发问题。两个人同时申请最后一件物品,如果都在提交时校验库存充足,审批时都直接扣减,库存就可能变成负数。我的处理方式是在审批通过时使用乐观锁更新库存:

UPDATE material SET stock = stock - #{count}, version = version + 1 WHERE id = #{id} AND stock >= #{count} AND version = #{version}

如果执行更新后影响行数为0,说明库存不足或版本冲突,返回审批失败信息让管理员联系申请人调整数量。这段逻辑是项目中一个很实在的亮点,面试时讲出来会有加分效果。

3.4 管理员端接口设计

管理员端的核心功能是审批、库存管理和数据统计。审批列表要展示申请人的姓名、部门、用品名称、申请数量、申请理由、申请时间,管理员审阅后可以选择通过或驳回,驳回时填写驳回原因。

库存管理接口包括新增用品、编辑用品、调整库存、开启或关闭用品状态。调整库存的场景比较有意思,采购补货和损耗报损都要调库存,但含义不同。我设计成库存变更记录,记录变更前后的数值差异和变更原因,这样每一条数据都能追踪来源。这个设计在文档里可以作为数据库设计的亮点来写。

数据统计部分用三个接口实现:按月份统计申请次数、按用品统计领用数量排行、按部门统计消耗量。统计逻辑用SQL的GROUP BY聚合实现,返回结果是简单的列表结构,前端用小程序原生的canvas绘制柱状图或折线图展示。canvas绘制对新手来说有点门槛,但代码量不大,值得在讲解时重点提一下。

4. 调试过程与常见问题排查

4.1 局域网联调与真机调试配置

小程序开发中最常见的问题就是前端请求不到后端。开发阶段我推荐这样配置:后端启动时绑定0.0.0.0地址,小程序端的request URL写成http://局域网IP:8080/api/xxx。同一台开发电脑上启动后端,用微信开发者工具模拟器可以正常访问。

换到真机调试时,局域网IP必须改成手机能访问到的IP,即开发电脑在局域网内的IPv4地址。这里有一个很容易踩的坑:电脑开了防火墙或者连了两个网络,导致手机实际上无法访问到开发电脑的IP。排查方法是在手机的浏览器里直接输入后端的接口地址,如果能访问说明网络通,不能访问就检查防火墙入站规则。

正式发布时还需注意,小程序后台必须配置服务器域名,域名必须备案且支持HTTPS。这个流程比较繁琐,所以在交付演示时通常直接用开发模式,勾选不校验合法域名即可。

4.2 常见报错与解决方案速查

报错现象产生原因解决方案
提示“request:fail”后端服务未启动或网络不通检查后端日志与防火墙,确认手机可访问后端IP
登录后页面空白用户信息存储异常查看storage中的token,清缓存重新登录
提交申请后提示“系统错误”后端抛异常未捕获查看后端控制台日志,多半是SQL或参数问题
审批通过后库存变为负数并发扣减控制缺失使用乐观锁更新库存,并返回失败信息
中文乱码数据库编码不是utf8mb4建库时指定utf8mb4,连接参数加characterEncoding=utf8
时间显示多了8小时时区未设置UTC 8数据库连接参数加serverTimezone=Asia/Shanghai
真机上看不到图片图片域名没配置或是本地路径开发模式勾选不校验域名,或改用线上图片URL
刷新后token失效token过期时间太短设置合理的过期时长,小程序端收到401时重新登录

以上都是实际调试中非常高频的问题。我建议在项目文档里单独开一个“常见问题与解决方案”章节,把这些问题和解决方案写进去,既方便复盘,也方便答辩或讲解时让听众看到你真实踩过来坑的痕迹,这种经历比完美的代码更有说服力。

4.3 调试工具与效率技巧

微信开发者工具里我常用的调试技巧有两个。第一个是Network面板,每个请求的耗时、请求参数、响应数据都看得一清二楚,前端传参错误或后端返回异常时,用这个面板定位问题最快。第二个是vConsole,在真机调试时可以看到前端console日志,这点非常关键,因为真机环境复现问题时开发者工具的模拟器不一定暴露同样的问题。

后端调试方面,我习惯用IDEA的断点调试功能。在Service方法入口打断点,逐行查看参数值和变量变化。有一次排查审批后库存没扣减的问题,就是用断点发现事务注解没加对地方,Service内部方法调用的自调用导致事务失效。这类问题不打断点根本看不出原因。

这里有一个想特别提醒的点:如果后端服务使用Spring Boot,一定要搞懂事务失效的几种情况。同类方法调用、异常被捕获、类没有被Spring管理,都会导致@Transactional失效。我在项目里就遇到过一次审批记录刷新了但库存没变更的情况,最后定位是事务没生效,整个方法执行过程中前边更新成功了后边异常回滚不掉。这种坑在正常开发中不常见,但在讲解演示时可以特意提一句,显得你确实对事务机制有理解。

5. 文档编写与项目讲解的交付心得

5.1 一套好的项目文档应该包含什么

很多同学写项目文档就是应付一下,把代码贴一遍。我理解中的文档核心是让别人不看代码也能知道整个系统的来龙去脉。我的文档结构比较固定:需求分析、系统设计、数据库设计、接口文档、系统部署、使用说明、常见问题。

需求分析要写清楚业务背景、参与者角色、功能需求和非功能需求。非功能需求容易被忽略,但实际很重要,比如并发库存扣减的安全性、接口响应时间、数据备份方案。系统设计包含架构图(文字说明也可以)和模块拆分说明。数据库设计重点讲每张表每个字段的含义,以及表与表之间的关联关系。接口文档用表格列出请求路径、请求方式、参数说明和返回示例。部署说明要写两套,一套是本地开发环境启动步骤,一套是服务器部署的流程。

接口文档我会贴出真实请求和响应示例,比如:

POST /api/apply Content-Type: application/json Header: token=abc123 Body: { "materialId": 3, "count": 5, "reason": "部门例会用笔" } Response: { "code": 1, "message": "申请已提交,等待管理员审批", "data": null }

这样读者根据文档可以直接测试接口,不需要去翻前端代码猜测字段叫什么。文档的价值就在这里,用最小成本让别人运行起这个系统。

5.2 完整交付的讲解流程与演示技巧

“源码加文档加调试加讲解”这套交付方式,重点在于把项目串起来。我个人习惯的讲解流程是:先讲业务,用实际的办公场景引出痛点;再讲解决方案,说明为什么用小程序而不是网页或桌面软件;紧接着演示前端页面,逐一展示每个功能模块;然后讲解数据库设计,将表结构和业务对应起来;最后讲核心代码逻辑,挑登录鉴权、审批状态机、库存并发扣减这几个亮点展开。

演示时有一个重要的技巧:预先造好一批数据,不要让演示现场出现空白列表或待审批列表为空的情况。我的做法是在清空数据库后导入一份demo数据,包含两个部门、五个分类、二十种用品、若干条申请记录。这样演示过程中可以直接展示“待审批→通过→库存扣减”的完整链路,而不需要临时花时间造数。

讲解阶段要用好类比。比如Spring Boot的Controller、Service、Mapper三层结构,可以类比成餐厅的传菜员、后厨和大厨:Controller接收请求传菜,Service处理业务逻辑炒菜,Mapper负责从冰箱拿原料(操作数据库)。这个类比虽然简单,但对初学者理解代码分层结构非常有效。

5.3 面试或答辩时如何讲出亮点

这套系统的亮点不能只体现在“能跑”,要能在讲解时展现设计能力。我总结了几处值得深挖的点:库存扣减采用乐观锁控制并发,避免了超卖问题;审批通过后才扣库存,保证数据一致性;用户通过openid自动注册,降低使用门槛;库存变更记录可追踪,方便追溯数据来源;操作日志记录关键行为,方便安全审计。

每个亮点都可以用“遇到了什么问题→我怎么解决→解决了之后效果如何”的结构来讲。比如库存超卖:申请人同时提交两笔申请,都通过审批后发现库存变成负数,于是我在扣减操作中使用版本号控制并发,保证数据库层面的原子操作,再配合事务实现要么全部成功要么全部回滚。这样一个有故事、有分析、有解决方案的表达,比单纯背代码的效果好很多。

6. 从项目完成到系统扩展

6.1 可以继续优化的方向

这套系统作为课程设计或简历项目已经完整,但距离真正的商用系统还有一段距离。真实场景里往往还需要这几个能力:审批规则多样化,比如金额在一定范围内自动审批,超过范围才走人工审批;消息通知即时化,审批结果通过模板消息推送给员工微信;数据报表图形化,通过图表展示月度消耗趋势;移动端适配差异化,针对管理员的大屏工作台。

我最建议加入的是消息订阅功能。员工提交申请后等待审批,如果不在系统里刷新,不知道审批结果。通过小程序的订阅消息能力,审批完成后给员工手机发一条模板消息,体验会上一整个档次。这个功能开发量不大,但效果非常明显。如果你要扩展项目,优先从这一个点入手。

6.2 多端复用的思考

微信小程序版的办公用品管理系统完成度已经足够,但在实际部署时你可能会遇到一个需求:不习惯用微信的员工怎么办。一套成熟的产品会考虑多端复用,比如同一套后端可以同时支撑小程序端和管理后台。现在很多公司内部系统都是小程序做前端,管理后台用Vue加Element Plus搭建,前后端分离,后端完全复用。

从这个角度看,后端接口设计时就要注意返回结构的通用性,还是建议从一开始就统一使用Result封装响应。提醒一下,如果你决定扩展管理后台,接口权限这层需要重新设计。小程序端和管理后台虽然调用同一套数据层,但接口本身的鉴权机制不同,管理后台的接口对安全性要求更高,建议token超时时间更短,操作审批类接口要重新校验管理员身份,不能只依赖前端隐藏入口来保证安全。

最后再说点实在的

整个项目做下来,我个人体会最深的一点是:业务逻辑比技术难想清楚,数据库设计决定了代码写起来顺不顺畅。如果你只把精力放在页面上而忽略了审批状态流转、库存扣减、权限边界这些深一层的设计,做出来的东西即使页面好看,逻辑也会站不住。刚开始动手时,不要急着写代码,先花一晚上把业务闭环想透,把每张表的字段和状态值列出来,后续编码会快很多。这套项目的源码和文档我整理了完整版,部署环境是Java 8加Maven加Node环境,拿到后只要按文档里的步骤配置好数据库连接和微信小程序appid,就能完整跑起来。如果你在跑通的过程中遇到任何报错,优先查一下常见问题表,大部分坑我在调试时都已经踩过一遍了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进