首页 / 资讯中心 / 文章详情

微信小程序来访预约审批系统:状态机设计与访客码核销源码解析

微信小程序来访预约审批系统:状态机设计与访客码核销源码解析 ★ FEATURED ARTICLE
简介面向企业园区、写字楼、大学校园等场景的来访预约审批管理系统基于微信小程序实现涵盖申请用户、访客、审批人员、门卫核验人员四种角色解决来访登记、审批流转、到访核验等环节的管理痛点。系统源码共497个文件压缩包大小6.31MB其中以183个js文件承载核心业务逻辑102个wxss负责界面样式82个wxml定义页面结构76个json管理配置并搭配47个png等静态资源目录组织清晰便于按模块阅读和二次开发。系统提供预约管理、审批流程、通知提醒、门卫核验及后台管理等功能借助微信消息提醒显著提升审批效率适合需要实现此类管理系统的开发者作为参考也可直接用于相关课题设计与毕设演示。目前已有293人学习下载配套内容预览中可见安装使用手册等文档有助于快速部署运行。1. 微信小程序来访预约审批管理系统这套源码到底能解决什么问题来访预约审批这需求看着简单真做起来比想象中琐碎。门卫还在用纸质登记本的单位不在少数访客到门口打不通接待人电话行政在微信里来回问“这人你认识吗”领导出差时审批单压在抽屉里没人管——这套基于微信小程序的来访预约审批管理系统就是把这条线下链路完整搬进小程序访客端填预约单、审批端逐级处理、通过后生成访客码、门岗扫码核销闭环。源码覆盖小程序前端、后端接口和数据库表结构不是只画了界面的 demo是能直接改字段上线的完整骨架。适合两类人一是要给园区、公司、学校做访客管理的开发二是拿真实业务场景练手的课程设计。接下来我把流程、数据表、踩坑点逐层拆开你照着改就能用。2. 从流程到数据模型预约审批的状态机与五张核心表2.1 预约审批的状态流转六个状态与操作边界审批系统最怕的是状态乱一个预约单在前端显示“已通过”后端库里还是“待审批”用户刷新一下又跳回去。设计这套源码的时候状态定义得比较明确0 待审批、1 已通过、2 已拒绝、3 已取消、4 已签到、5 已完成。从命名就能看出来它不是简单的是/否二元状态而是把门岗核验也纳入流程预约单从创建到归档一共走六个节点。每个状态能触发什么操作要画清楚边界。普通访客只能操作自己的单待审批时可取消已通过后由门岗核验置为已签到其余状态不允许改。审批人能做的只有审批动作对待审批的单通过或拒绝。这里最容易出问题的是“已拒绝”之后能不能重新提交——很多实现直接禁止这套源码里允许被拒的单修改后重新提交提交后回到待审批状态审批记录里保留上一次的拒绝原因。这一点对真实业务很重要访客填错手机号被拒是常态不允许重提的话只能重新开单数据越堆越乱。状态机这块看着简单实际是整张预约链路里最容易被改坏的部分。前后端各维护一套状态枚举前端漏一个颜色映射后端少一个前置校验都会变成用户眼里的玄学 bug。后面第 5 章我会专门讲几个真实翻车现场。2.2 数据表结构五张核心表如何支撑整个流程访客系统的表不止五张但核心链路围绕五张表展开。sys_user 存审批人和管理员账号visitor_info 存访客基本信息appointment 是预约主表approval_record 是审批流水visitor_code 存访客码。把每张表的职责和关键字段拆开看能理解为什么这么拆。表名职责关键字段说明sys_user账号体系id、username、role、dept_idrole 区分访客/审批人/管理员visitor_info访客档案id、name、phone、id_card同一访客多次预约不用重复填appointment预约主表id、visitor_id、date、time_slot、reason、statusstatus 即上节六个状态approval_record审批流水id、appointment_id、approver_id、action、comment每次审批追加一条visitor_code访客码id、appointment_id、code、expire_time、used_time审批通过后才生成记录这个设计里有几个值得留意的点。visitor_info 单独拆表而不是把姓名电话直接存在预约单里理由是同一访客重复来访时可以复用档案减少小程序端的输入量审批流水单独拆表是为了留下完整操作痕迹后面管理端要查“谁在什么时间批过这张单”直接查这张表不用去翻操作日志。预约主表只存业务状态不做冗余字段这是比较规范的做法。如果你想给预约单加“来访人数”或“随行车辆”优先加到 appointment 表不要塞进 visitor_info因为人数和车辆是某一次预约的属性不是访客的固定属性。2.3 状态机落成代码审批动作的前置校验状态机光画图没用要落到代码里做前置校验。常见做法是后端在更新前先查一次当前状态再决定是否允许操作。以“审批通过”为例逻辑是查预约单 → 判断当前状态是否为待审批 → 状态是才更新为已通过 → 更新时带版本号条件防并发。// 伪代码审批通过预约单 public Result approve(Long appointmentId, String approverId) { Appointment appointment appointmentMapper.selectById(appointmentId); // 前置校验只有待审批状态能通过 if (appointment.getStatus() ! AppointmentStatus.PENDING) { return Result.error(当前状态不可审批刷新后重试); } // 乐观锁更新version 不匹配说明被别人改过了 int rows appointmentMapper.updateStatusWithVersion( appointmentId, AppointmentStatus.APPROVED, appointment.getVersion()); if (rows 0) { return Result.error(操作冲突请刷新后重试); } // 追加审批流水 approvalRecordMapper.insert(new ApprovalRecord( appointmentId, approverId, approve, 同意来访)); // 生成访客码 visitorCodeService.generate(appointmentId); return Result.success(); }这段逻辑有两个参数容易改错。第一个是乐观锁的 version 字段更新条件必须带“当前查询到的 version”不能直接 update by id否则两个审批人同时打开同一张单、先后点击通过第二次更新会覆盖第一次导致审批记录和业务状态对不上。第二个是审批流水的插入时机必须在状态更新成功后插入如果先插流水再更新状态状态更新失败时会出现“有审批记录但预约单还是待审批”的脏数据。提示推送审批结果通知也应该放在这里状态更新成功后调用订阅消息接口这一步在第 4 章展开。3. 小程序端落地预约表单、访客码与我的记录三个模块3.1 预约表单日期时间选择与动态访客字段小程序端第一个要实现的页面就是预约表单。访客填的数据一般包括来访日期、时间段、来访人数、来访事由、被访人。日期和时间段推荐用原生 picker不要自己写日历组件——小程序原生 picker 在 iOS 和 Android 上的表现有差异但至少不会踩自定义组件的坑维护成本低很多。预约表单页面的结构大概是这样picker modedate value{{date}} start{{today}} bindchangeonDateChange view classform-item来访日期{{date || 请选择}}/view /picker picker modemultiSelector value{{timeIndex}} range{{timeSlots}} bindchangeonTimeChange view classform-item来访时段{{timeSlots[timeIndex[0]]}} {{timeSlots[timeIndex[1]]}}/view /picker view classform-item view来访人员/view block wx:for{{visitors}} wx:keyindex input placeholder姓名 value{{item.name}}>import hmac, hashlib, time def generate_visitor_code(appointment_id: int, expire_hours: int 3) - str: # 过期时间戳审批通过后 3 小时内有效 expire int(time.time()) expire_hours * 3600 # app_secret 是服务端密钥绝不返回给前端 sign hmac.new( byour_app_secret, f{appointment_id}{expire}.encode(), hashlib.sha256 ).hexdigest()[:16] return f{appointment_id}-{expire}-{sign}参数说明appointment_id 决定这张码绑定的预约单expire_hours 是有效期3 小时比较合适太长访客码在门岗核验时已经过了业务时效太短访客在路上耽误一下就过期sign 是签名串截取前 16 位是为了缩短二维码承载内容核验时后端用同样的密钥重新算一遍签名不一致直接拒绝。门岗核验接口需要先查预约单状态只有已通过状态的码能核销已签到和已完成都不允许重复核销。3.3 我的记录分页加载与状态回显“我的记录”是访客查看自己所有预约单的页面。列表页的写法没有太多花样但有一个坑分页。刚接触小程序的开发者喜欢一次性把全部记录 load 出来数据量到几百条时页面明显卡顿。这套源码的做法是后端接口传 page 和 pageSize前端用 onReachBottom 触底加载下一页。// 小程序端加载我的预约列表 Page({ data: { list: [], page: 1, pageSize: 10, hasMore: true }, async loadList() { const res await wx.request({ url: /api/appointment/mine, data: { page: this.data.page, pageSize: this.data.pageSize } }); const list this.data.list.concat(res.data.list); this.setData({ list, page: this.data.page 1, hasMore: res.data.list.length this.data.pageSize }); }, onReachBottom() { if (this.data.hasMore) this.loadList(); } });逻辑说明hasMore 的判断依据是“当前页返回条数是否等于 pageSize”等于说明还有下一页不等于说明到底了。这里用「等于」而不是用「大于 0」是为了避免最后一页正好满 10 条时下一页发出去还能拿到空数组白白多一次请求。状态回显用后端返回的 status 字段做映射0 灰色「待审批」、1 绿色「已通过」、2 红色「已拒绝」、3 橙色「已取消」、4 蓝色「已签到」、5 灰色「已完成」。颜色映射表建议放在前端统一管理不要后端返回颜色值否则改一次 UI 要动后端逻辑。4. 管理端审批链多级审批、订阅消息与核销闭环4.1 多级审批配置单级、双级与委托审批不同单位对来访审批的层级要求不一样。小公司一个行政就能批大厂要「访客 → 被访人 → 部门负责人」两级甚至三级审批。这套源码把审批链做成了可配置而不是写死在代码里核心是一张审批流程配置表记录某条规则下每一步的审批角色。字段至少要有三个rule_id、step 序号、approver_type。有个玄学点approver_type 的设计决定了整条链路的灵活度。常见做法是支持三种类型R角色、U指定用户、D按被访人部门动态找负责人。第三种最实用——被访人填的是「张三」系统自动找张三所在部门的负责人来审批而不是在配置表里指定某个用户。这样人员离职调动时流程不用跟着改。具体配置数据大致长这样rule_idstepapprover_typeapprover_value11Ddept_manager12Rsecurity_admin第一级按被访人部门找到部门经理第二级固定给安保管理员。规则配置到这一步基本能覆盖大部分单位的审批习惯。如果有人请假可以在管理端把待办转交给别人这是后话第 5 章会说离职场景的坑。4.2 订阅消息一次性订阅的触达策略访客提交预约后最关心的就是审批结果。小程序不能随时推送必须用订阅消息。wx.requestSubscribeMessage 是每次授权一次下次要用得重新授权这个限制让很多开发者头疼。源码里的做法是提交预约单的同时弹出订阅授权一次授权就是一次消息额度审批通过后立刻消耗这条额度发送结果通知。// 提交预约单时申请订阅消息授权 wx.requestSubscribeMessage({ tmplIds: [审批结果通知模板ID], success(res) { // 用户点了允许就拿到了这一次下发额度 if (res[审批结果通知模板ID] accept) { this.submitAppointment(true); } else { // 拒绝授权不能拦着用户提交降级为站内查看 this.submitAppointment(false); } } });参数说明tmplIds 填小程序后台申请的模板 ID一次最多传三个用户点击允许后小程序端只拿到「这一次的发送资格」实际推送必须由后端调用 subscribeMessage.send 接口完成。一个实际教训审批结果通知要在审批动作发生后立刻发不要攒到晚上统一发因为额度有时效最长保留 7 天过期作废。攒批的结果是用户收不到通知跑来门卫问怎么没通过体验很差。4.3 核销闭环防止二维码复用与核销失败门岗核验是整个系统的最后一环最容易出问题的是二维码复用。访客码生成后如果只校验签名不校验状态访客截屏给第二个人第二个人也能进。源码里的核销接口做了三道检查签名校验、过期时间校验、状态校验。状态校验是关键核销成功后将预约单从已通过置为已签到这个更新必须带「当前状态 1」的条件。-- 核销时带状态条件防止同一张码被并发用两次 UPDATE appointment SET status 4, checkin_time NOW(), checkin_user #{operatorId} WHERE id #{appointmentId} AND status 1 AND version #{version};逻辑说明WHERE 里的 status 1 是核心它保证了只有「已通过」状态的单能变成「已签到」。如果返回值 rows 0说明已经被核销过或者状态不对前端提示「该访客码无效或已使用」。checkin_user 记录操作人用于事后追责。这里建议把核销动作和取访客信息的接口分开门岗先扫一次拿到访客姓名和事由确认无误后再点「确认放行」触发核销避免误扫直接消掉一条有效预约。5. 避坑排查审批链路、小程序审核与客户端差异的六个翻车现场这套源码整体跑通不难真正让人血压升高的是业务边界和小程序端的各种兼容问题。我按自己的复现经历整理了六个高频问题前三个集中在审批链路和数据一致性后三个集中在小程序前端与客户端差异。每条按「现象 → 原因 → 解决」三步写方便你直接对照排查。5.1 审批链路与数据一致性三个典型问题审批人离职后流程卡死在待审批。现象管理员收到访客投诉说预约单交了三天没人审一查发现审批人是已离职员工账号被禁用待办流不走了。原因审批链配置成了指定用户类型该用户离职后没有转移审批关系流程没有兜底。解决配置审批链时优先用角色或按部门动态找负责人少用指定具体用户。如果业务必须指定某个人离职操作时强制走一遍「审批关系转移」把该用户名下的待办批量转给接任者这一步值得做成管理员后台的独立功能。被拒绝的预约单无法修改重提。现象访客被拒后想改手机号重新提交前端报了「该预约单已完成」的错误。原因状态机里把「已拒绝」当终态处理了没设计回到待审批的路径。解决给已拒绝的单提供一个 re-submit 接口接口里校验当前状态必须为「已拒绝」然后新建一条审批流水、把状态置为待审批。注意要保留原来的拒绝原因访客能看到自己为什么被拒不然他会反复提交同一张错误单。数据库时间字段与小程序端相差 8 小时。现象访客在小程序里选的日期被访人那边显示成前一天。原因后端存的是 UTC 时间前端读取后没做时区转换安卓和 iOS 的时区行为还不一致。解决后端接口统一返回带时区的 ISO 字符串或者直接返回时间戳前端用 dayjs 转成当地日期。数据库存北京时间也行但那属于团队约定接口层返回标准化时间戳最省事。5.2 小程序前端与客户端差异三个高频问题访客码在部分安卓机上加载不出二维码。现象同一套生成接口iOS 正常安卓真机上二维码图片空白。原因二维码组件在 WebView 渲染时对 canvas 的兼容性差异常见于用了老版本 canvas 组件的写法。解决换成官方 canvas 2d 接口生成后以图片形式渲染。如果实在没办法退一步同时在页面展示一串数字核销码门岗手动输入也能核销至少业务能跑通。订阅消息用户拒绝授权后完全没有通知。现象访客提交预约后没收到任何提醒自己也不知道审批结果到门口才发现没通过。原因用户点击订阅消息对话框的「拒绝」后前端没做降级处理直接静默提交了表单消息发送额度为空。解决拒绝授权不能阻止提交但提交成功后的提示要明确写「审批结果可在我的记录中查看」把预期拉低。更主动的做法是在结果页直接展示预约单当前状态访客自己刷新就能看到。表单页面滚动时输入框被键盘顶乱。现象访客填写来访人员手机号时输入框被键盘遮挡页面整体错位。原因小程序页面对键盘事件处理不当键盘弹出时 WebView 高度改变fixed 定位的元素跟着漂。解决避免在表单页使用 fixed 定位做提交按钮容器改用页面底部的普通布局或者监听键盘高度做偏移补偿。这是复现率最高的 UI 问题没有一劳永逸的样式只能按页面实测去调。提示还有一个不太常见但很坑的问题——管理员后台修改预约单状态后访客端列表一直显示旧状态。原因是列表页没有下拉刷新和缓存失效策略。审批这种低频变更实时请求成本很低建议「我的记录」页启用 onPullDownRefresh每次下拉重新请求接口不要缓存超过 5 分钟。6. 把源码改成你业务的预约场景四个验证步骤与替换清单6.1 业务字段替换清单不同业务的预约字段有差异。食堂参观要填人数实验室来访要填事由固定资产借用要填归还时间。替换字段时建议按这个顺序先改数据库 DDL再改后端 VO最后改小程序表单页。常见替换点如下原字段替换场景改动位置reason 来访事由改成携带物品申报appointment 表 表单页time_slot 时段改成按小时选择表单页 timeSlots 配置visitor_number 人数改成车辆车牌号visitor_info 表 表单页dept_id 部门改成校区/楼栋sys_user 表 审批链配置6.2 上线前验证四步第一步后端启动后用 Postman 过一遍审批链路接口重点看状态校验和版本冲突处理。第二步小程序开发者工具里用不同角色预览访客提交、审批人审批、门岗核销三个角色各走一遍完整流程。第三步真机预览至少覆盖一台 iOS 和一台安卓重点看日期 picker 和二维码渲染这两个是兼容性重灾区。第四步连上测试环境把审批链配成两级验证访客提交后两个审批人按顺序收到待办。这套源码我前前后后改过三轮第一次是给园区做访客管理后来给学校实验室预约做改造。改动最多的地方从来不是页面而是状态机和审批链配置——页面改字段半小时状态机改错一个节点要排查一整天。从那以后我每次换场景都强制先把六个状态和操作边界画一遍再动代码这个习惯帮我省掉了不少后悔药。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站