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

智慧景区管理系统源码解析:票务、设备、停车与权限设计

智慧景区管理系统源码解析:票务、设备、停车与权限设计 ★ FEATURED ARTICLE
简介智慧景区管理系统完整源码说明面向计算机相关专业学生、企业开发者及项目入门者可支撑课程设计、毕业设计、初期立项演示等场景也适合作为前后端分离项目的实战练习尤其适合希望快速掌握完整项目链路的小白用户。压缩包共426个文件大小约10.77MB内容以js、css、html前端资源为主含大量png/jpg图片素材、一套sql数据库脚本、md说明文档以及json、yml等配置文件前后端结构完整、目录清晰便于按模块查阅。目前已有250人学习。功能上已完成票务系统、设备管理、停车场管理、用户权限控制、设备权限控制、小程序售票等模块完整展现了智慧景区常见的业务链路与权限模型。源码均经过功能测试可直接运行配合附带说明和前端构建产物可帮助读者理解前后端交互细节、数据库表设计并在此基础上快速二次开发或用于毕业设计、项目文档撰写。1. 智慧景区管理系统拿了源码别急着跑先把六块功能的边界画清楚一套智慧景区管理系统已完成票务、设备管理、停车场管理、用户权限控制、设备权限控制、小程序售票的源码包拿到手第一件事不是跑起来看界面而是先回答三个问题给谁用、业务怎么流转、上线第一天会先崩在哪。很多团队把 demo 跑通就觉得完事结果卡在权限越权、库存超卖、停车计费对不上账根子都在没理顺模块边界。这六个功能看似独立实际上是一条数据主线拆出来的游客在小程序买票订单落到票务系统入园时闸机核销设备由设备模块统一管车辆进场记入停车系统出场时计费每个操作背后还要过用户权限和设备权限双重校验。任一块都能单独成系统只有拼在一起才算景区管理系统。这里只讲通用落地路径先拆模块、再读源码、后补联调、最后排坑。适合要做二次开发的人、要评估选型的技术负责人、以及正在自建同类平台的初学者。2. 模块拆解票务、设备、停车、权限怎么拼成一套系统拿到源码先别急着打开代码编辑器第一件事是看业务模块之间的关系。票务系统管订单和核销设备管理管闸机和道闸这些硬件停车场管车辆进出和计费用户权限控制管什么人能进什么页面设备权限控制管什么人能操作什么设备。它们不是五个独立的小程序而是一套数据模型下的五个业务面数据共享同一套用户体系和组织架构。我用一个实际的例子把这五个模块串起来游客在小程序买了一张成人票订单在票务系统生成游客到景区入口检票员用手持机扫票码核销请求同时更新订单状态和设备状态一辆车开进停车场道闸识别车牌写入停车记录到了晚上财务在后台看全天的门票营收和停车费流水。整个流程里每一个动作都涉及权限校验少一环就会出安全漏洞。2.1 票务链路从下单到闸机核销的完整状态机票务系统的核心不是卖票页面而是订单状态机。常见做法是把订单状态拆成五个待支付、已支付、已检票、已退票、已关闭。其中最容易踩坑的是「部分检票」这个状态一张订单买了五张票游客分两批入园第一批三个先检票这时候订单既不是已检票也不是已支付必须有一个中间状态处理。设计订单表的时候要把「订单」和「票」拆成两层订单记录一次购买行为票记录每一张可核销的凭证。也就是说订单号和核销码要分开核销码一人一码不能一个订单一个码。我见过一个失败的设计一个订单一个核销码结果五个人排队扫同一个码闸机不知道谁进了谁没进只能靠人工数人数。票务状态流转还涉及票价策略票种、日期、人群优惠的组合要在单独的票价计划表里维护而不是在代码里写if else。模块拆解时还要注意票务的库存概念。景区门票不是无限供应的每天每个票种有总量控制这是后面并发章节的基础。这里先记住一个结论库存扣减必须和订单事务绑定不能先扣库存再单独发消息中间崩了就没法对这账了。2.2 设备管理为什么要单独做一套权限设备管理模块在标题里单独出现这版源码把设备权限控制和用户权限控制分开列出来说明作者在权限设计上是认真想过的。景区设备种类不多但数量大入口闸机、手持检票机、停车场道闸、监控摄像头、LED信息屏。设备管理不只是设备的增删改查还包括状态上报在线、离线、故障、心跳监测、远程操作命令开闸、重启、升级。为什么设备权限要单独做不能直接用菜单权限代替因为菜单权限回答的是「这个人能不能进入某个功能页面」设备权限回答的是「这个人能不能操作某一台具体设备」这是两个维度的问题。举个例子三个检票员都有「检票」这个菜单权限但 A 只能操作东门的闸机B 只能操作西门的闸机C 被调去北门支援时需要临时给他北门的设备权限但不代表他能操作东门闸机。这个粒度用传统 RBAC 菜单权限是做不到的。所以设备管理的表结构通常是三张设备表、设备分组表、角色-设备权限关联表。设备挂在分组下角色和分组建立授权关系。操作设备时后端动态校验当前用户和这台设备所在的组是否有授权关系。特别注意设备本身不是登录用户设备通过设备码和密钥跟后端通信操作命令要经过权限拦截器验证操作者身份两个身份不能混为一谈。2.3 停车场系统与用户权限的边界财务、运营、外包各看各的数停车场系统最微妙的不是计费算法而是多角色数据隔离。景区的停车场经常外包给第三方运营但系统统一部署在景区平台上这时候就出现了一个典型的权限需求外包收费员只能看到自己班次的记录停车场主管只能看到自己管辖的停车场景区财务需要看到全量流水和分成前的应收账但看不到外包方的内部人员排班。这一块建议在用户权限控制里用「数据归属」来实现默认支持三个维度按操作人只见自己经手的单、按组织见本部门数据、按全局见所有数据。每个查询接口都要带数据权限条件用切面统一拼接而不是在各个 Service 里手动写死。停车场业务本身相对独立入场生成记录、出场计算费用、每日出财务报表。计费规则要单独建参数表维护免费时长、首小时价格、续时单价、每日封顶、跨天策略每项都是数据库字段而不是常量。停车场和票务可以打通比如住景区酒店的房费含停车费或买套票送停车券这时候停车场模块要提供一个「外部抵扣」的接口。模块边界就清晰了停车场只管出场计费外部抵扣由营销模块统一算好金额传过来。如果停车场自己去查订单详情模块之间的耦合就加重了后面改一处崩一片。3. 源码走读主线先看订单与权限的落地方式拿到这套源码建议按两条主线去读票务订单线和权限控制线。票务订单线能让你看懂完整的业务数据流从下单到核销到退票权限控制线能让你看懂这套系统怎么保证不同角色各归其位。很多源码不写资料但代码结构和表设计本身就是文档会读表的人十分钟就能摸清系统有几斤几两。3.1 先定位四张核心表订单、票种、角色、设备我拿到一套 Java Spring Boot 风格的景区系统源码第一件事是打开数据库脚本看四张表订单表、票种表、角色表、设备表。这四张表是系统的骨架读懂了它们就掌握了八成业务。订单表看状态字段和关联的核销记录票种表看价格策略字段角色表看权限模型是简单的 user_role 还是加了数据权限设备表看设备分组字段这决定了设备权限的实现方式。订单表建议拆两张订单主表和订单明细表。主表存订单号、总金额、人数、状态、支付信息明细表存每张票的核销码、使用状态、绑定人信息。很多简化版源码只有一张订单表用 JSON 字段存票列表顶多支持打印个订单列表一旦要接闸机、做部分检票就麻了。下面是一个典型订单主表结构字段不多但覆盖了票务核心场景CREATE TABLE t_ticket_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT 订单号业务唯一, sku_id BIGINT NOT NULL COMMENT 票种ID关联票价计划, visit_date DATE NOT NULL COMMENT 游玩日期, quantity INT NOT NULL COMMENT 购票数量, total_amount DECIMAL(10,2) NOT NULL COMMENT 订单总金额单位元, status TINYINT NOT NULL COMMENT 0待支付 1已支付 2部分检票 3全部检票 4退票中 5已退票 6已关闭, out_trade_no VARCHAR(64) COMMENT 第三方支付单号, pay_time DATETIME COMMENT 支付时间, create_time DATETIME NOT NULL, update_time DATETIME NOT NULL );字段的讲究在 status 和 out_trade_no 上。status 用了 0-6 七个值把部分检票和退票中这种「中间状态」也纳入状态机这样业务不用靠其他字段反推状态。out_trade_no 是第三方支付单号必须允许为空因为订单创建时用户还没支付支付成功后回填。如果你在源码里看到 status 只有「已支付/未支付」两个值基本可以判断这套票务系统没法直接对接闸机做部分核销。3.2 创建订单接口的代码骨架库存、金额、状态一次写完订单创建是票务系统最核心的接口代码写法直接决定后面会不会超卖。我一般会在源码里找这个接口的注解风格看它是直接用事务注解、还是手写事务管理器。下面是常见的创建订单核心逻辑Transactional(rollbackFor Exception.class) public CreateOrderResult createOrder(CreateOrderRequest req) { // 1. 查询票种并加行锁防止同一天同一票种库存超卖 TicketSku sku ticketSkuMapper.selectBySkuIdForUpdate(req.getSkuId()); if (sku null) { throw new BizException(票种不存在); } // 2. 校验游玩日期是否在该票种的可售日期范围内 if (!sku.isSaleableForDate(req.getVisitDate())) { throw new BizException(所选日期不可售); } // 3. 扣减库存使用乐观锁条件里带上版本号 int updated ticketSkuMapper.deductStock(sku.getId(), req.getQuantity(), sku.getVersion()); if (updated 0) { throw new BizException(该日期库存不足); } // 4. 计算金额金额计算必须有独立的方法不能被外部传入 BigDecimal amount calculateAmount(sku, req.getQuantity(), req.getChannel()); // 5. 创建订单初始状态为待支付 TicketOrder order TicketOrder.create(sku, req, amount); ticketOrderMapper.insert(order); // 6. 生成子票记录每个子票一个核销码 batchCreateTicketCodes(order); return CreateOrderResult.success(order); }这段代码有三个必须保持的逻辑点。第一selectBySkuIdForUpdate是一个行锁查询保证同一个票种在并发下单时被串行处理如果源码里没有这一步只是先查一下库存够不够再 update那并发两个请求都会通过检查最后库存变成负数。第二库存扣减的 SQL 必须带stock 0或不等于条件这里用了版本号机制防止脏写。第三金额计算不允许前端传总价只允许传数量单价以服务端票价计划表为准否则用户可以构造请求改价格。很多新手会在这一步纠结「要不要用 Redis 当库存」我的建议是先看数据库层有没有兜底。Redis 预扣减能做流量削峰但只有数据库行锁或乐观锁才能真正保证不超卖纯 Redis 方案在宕机恢复时容易库存和订单对不上。先读懂这里用的是哪种方案再决定要不要改。3.3 用户权限与设备权限的代码落点一个是拦截器一个是业务校验用户权限控制现在的成熟方案是 RBAC 加数据权限在 Spring Boot 里通常用拦截器统一校验。我先读源码里的权限拦截器看它校验的是「登录状态」还是「接口权限码」。本来登录过了就放行的系统一个游客接口和一个管理员接口没有任何区别这就是权限漏洞的来源。合理设计是三段式拦截器认登录态、注解声明接口权限码、业务代码里再查数据范围。设备权限的落点不一样它不能只放在拦截器里。因为设备操作接口可能长这样device/command?deviceId1001actionopenGate拦截器只能判断「当前用户有操作设备功能的权限」但判断不了「他能不能操作 1001 号闸机」。所以设备权限校验要写进业务代码在调用设备命令之前查一次角色-设备组授权关系public void executeDeviceCommand(String userId, Long deviceId, String action) { // 1. 先走拦截器验证用户已登录且有设备操作菜单权限前面已经完成 // 2. 业务代码里动态查询用户所属角色是否被授权了设备所在的组 Device device deviceMapper.selectById(deviceId); if (device null) { throw new BizException(设备不存在); } int authCount roleDeviceMapper.checkRoleDeviceAuth(userId, device.getGroupId()); if (authCount 0) { throw new BizException(无权操作该设备); } // 3. 权限通过后再下发命令到设备网关 deviceCommandSender.send(device.getDeviceCode(), action); }注意这里每一步的意义selectById是为了拿到设备所属的分组 IDcheckRoleDeviceAuth是动态查询关联表确保授权状态是实时的而不是登录时缓存下来的。如果你在源码里看到的设备操作直接调了send方法中间没有权限校验这一步那这就是一个明显的越权漏洞二次开发时要补上。4. 小程序售票与停车场接口联调参数设计对了能省一半事故小程序售票是这套系统里跟游客接触最近的一块它的问题集中在这几个方面登录态怎么换取、票种信息怎么同步、余票显示的准不准、支付回调怎么处理。停车场模块相对独立但它有一个著名的坑点计费规则配错了账就对不上。这一章专门讲联调时要抠的参数。4.1 小程序售票三件事登录态、票种配置、余票实时同步小程序登录和网页登录不一样小程序没有密码输入框用的是微信的 code 换 openid 机制。一个合格的小程序售票接口至少要包含下面这个流程PostMapping(/api/wx/login) public Result wxLogin(RequestBody WxLoginRequest req) { // 1. 用 wx.login 拿到的临时 code 换取 openid WxSession session wxService.code2Session(req.getCode()); if (session null || session.getOpenid() null) { return Result.error(登录态失效请重新点击授权); } // 2. 查本地用户表首次登录自动注册 WxUser user wxUserMapper.selectByOpenid(session.getOpenid()); if (user null) { user WxUser.register(session.getOpenid()); wxUserMapper.insert(user); } // 3. 生成本次会话 token有效期设 2 小时用 Redis 存 key String token UUID.randomUUID().toString().replace(-, ); redisTemplate.opsForValue().set( wx:token: token, String.valueOf(user.getId()), Duration.ofHours(2) ); return Result.success(Collections.singletonMap(token, token)); }这段里有三个特别注意的参数。code2Session的 code 是一次性的不能用第二次所以前端每次进入小程序都要重新 wx.login不能缓存 code。token 有效期 2 小时是常见值但景区游客逛一圈普遍超过 2 小时如果用户支付中途 token 过期会报登录失效建议游客身份的有效期放宽到 12 小时或 24 小时反正 key 存在 Redis 里随时能失效。Redis key 里带了wx:token:前缀这是为了和其他业务 key 隔离运维排查的时候也好按前缀搜索。票种配置是联调时最容易漏的地方。小程序端显示的票种列表、价格、库存不能直接查数据库表要提供一个专门的接口/api/wx/ticket-list?visitDate2025-07-23后端根据游玩日期动态过滤掉不可售的票种。注意日期参数必须由后端校验不能信用前端传的日历因为票价策略是按日期浮动生效的前端时钟不准会导致显示错误。余票同步这里不展开详见 4.3 节。4.2 停车场计费跨天、免费时长、封顶价三个参数最容易配错停车场模块核心就是两个接口入场和出场。入场记录相对简单出场计算费用时容易出问题。常见计费参数表大概长这样参数项示例值说明free_minutes15免费时长入场15分钟内出场不收费first_hour_price10首个计费小时价格元extra_hour_price5每增加一小时的单价元max_daily_price30单日封顶价元超过按封顶算billing_cycle30不足30分钟按30分钟计超过按1小时计计费逻辑最容易翻车的地方不是加减乘除而是两个边界跨天和免费时长重复。先说免费时长一个车停了 10 分钟出场免费这没问题但有的源码实现是「每次续费都重新享受免费时长」比如这辆车连续多次进出每次都免 15 分钟等于无限免费停车。正确做法是免费时长只在连续停车区间内生效重新入场算新的一单不能靠出场后重新入场来无限薅羊毛。这个逻辑要在入场时判断上次出场时间如果离上次出场超过 5 分钟才允许重新计算免费时长。跨天的逻辑更麻烦。一辆车停了两天很多简化实现是「按天分别计算再相加」但这样的话每天都会重新计算一次免费时长和首小时价总额比实际连续停放贵很多。正确做法是按自然分钟差值计算总时长再根据计费规则分段计价跨天只是在每日封顶的维度上做限制。下面这段是通用的计费伪逻辑public BigDecimal calcParkingFee(LocalDateTime entryTime, LocalDateTime exitTime, ParkingRule rule) { // 1. 计算总停车分钟数 long totalMinutes Duration.between(entryTime, exitTime).toMinutes(); if (totalMinutes rule.getFreeMinutes()) { return BigDecimal.ZERO; } // 2. 扣除免费时长后剩余分钟参与计费 long billableMinutes totalMinutes - rule.getFreeMinutes(); // 3. 按封顶价和跨天规则计算最终费用 int days Math.toIntExact(Duration.between(entryTime.toLocalDate(), exitTime.toLocalDate()).toDays()) 1; BigDecimal dailyCapTotal rule.getMaxDailyPrice().multiply(BigDecimal.valueOf(days)); BigDecimal normalFee calcByStep(billableMinutes, rule); // 4. 取正常计费和封顶总价中的较小值 return normalFee.min(dailyCapTotal); }这段代码解决的核心问题是「总时长连续计算」。calcByStep里按 billing_cycle 做阶梯计价normalFee.min(dailyCapTotal)保证多天停车费用不会超过每日封顶之和。我在实际项目里发现这块最容易出 bug 的其实是时区问题数据库存的create_time是服务器时区但停车场本地用的是北京时间如果不统一时区跨天判断会平白多一天或少一天上线第一天就能对不上账。4.3 库存不超卖Redis 预扣减 数据库兜底票务系统的并发压力集中在节假日早上九点那一波大客流景区可能同时有一千个人在抢同一个日期的票。如果直接查数据库库存、判断、扣减数据库会被打爆。常见且可靠的做法是 Redis 预扣减加数据库兜底public CreateOrderResult createOrderWithRedis(CreateOrderRequest req) { String stockKey stock:sku: req.getSkuId() :date: req.getVisitDate(); // 1. Redis 预扣减返回扣减后的剩余值 Long remain redisTemplate.opsForValue().decrement(stockKey, req.getQuantity()); if (remain null || remain 0) { // 2. 扣成负数说明超卖立即回补并拒绝 redisTemplate.opsForValue().increment(stockKey, req.getQuantity()); throw new BizException(手速太快该日期库存不足); } try { // 3. 数据库层兜底创建订单内部仍走行锁校验 return createOrder(req); } catch (Exception e) { // 4. 下单失败要回补 Redis 库存 redisTemplate.opsForValue().increment(stockKey, req.getQuantity()); throw e; } }这个方案的执行要点有三条第一Redis 扣减必须用decrement的返回值判断是否小于零不能先 get 再 set那两步之间有并发窗口。第二数据库层的createOrder方法里仍然要保留行锁和库存校验Redis 只是挡前面的流量真正保证一致性的还是数据库。第三回补逻辑要放在 catch 里而且是立即回补不能靠定时任务异步回补否则失败订单的库存要等很久才能还回来。还有一个细节是库存预热。Redis 里的库存 key 不能等到第一个请求来了才设置要有一个定时任务每天早上初始化当天各票种的库存值。不然系统重启后 Redis 是空的decrement会反直觉地返回负数所有请求都被拒绝。这个坑我见得太多了十套票务系统里至少三套会在重启后出这个问题。5. 部署与避坑把 demo 变成能扛事的系统5条血泪经验源码跑通和正式上线之间隔着一条河这条河里全是坑。这一章我把踩过的坑按「现象、原因、解决」三段式整理出来。这些坑不一定每套源码都有但出现概率极高网上搜项目部的问题往往是这几个。先列一个部署前环境检查清单再讲五个高频问题。5.1 部署前的环境配置检查清单拿到源码后别急着mvn spring-boot:run先按下面这个清单核对一遍环境配置。这套系统依赖的东西比较多缺一个配置启动都能成功但跑到特定功能才报错排查起来更痛苦。检查项配置位置检查要点数据库application.yml时区参数加 serverTimezoneAsia/Shanghai否则时间字段全偏Redisapplication.yml确认密码和 db index票务库存和 token 都依赖 Redis文件存储对象存储配置证件照、公告图上传路径本地存储和云存储切换要小心小程序 appid/secret管理后台系统配置表前后端各配一遍前后台不一致会导致扫码登录失败HTTPS 证书Nginx 配置小程序生产环境要求合法域名必须走 HTTPS日志级别logback.xml生产环境调到 INFO但 keep-alive 心跳日志要关闭避免刷盘检查的重点不是「有没有配置」而是「配的值对不对」。比如时区MySQL 连接串没加serverTimezone默认按服务器时区解析你的订单时间在数据库里看着是对的但跨天停车计费和每日报表汇总就全错了。同样服务器上也建议执行timedatectl set-timezone Asia/Shanghai双保险。5.2 坑一支付回调丢了订单卡在待支付现象游客在小程序里付了款但后台订单状态一直是待支付票没有自动发放。原因微信支付回调接口没有做幂等保护同一个回调因为网络重试发了两次第一次处理成功后第二次又处理了一次第二次更新就把订单状态改坏了或者回调接口事务异常代码抛了异常但支付平台已经收到了成功响应导致平台不再重试。根本原因是对回调处理没有结构化设计。解决回调接口做三件事。第一步用第三方支付单号查本地流水表如果这条流水已经处理过就直接返回成功不再走业务逻辑第二步校验回调金额和订单金额是否一致不一致必须告警第三步在同一个事务里更新支付状态、创建核销码、记录支付回调流水。另外加一个定时任务每隔五分钟把「已支付但本地未更新」的订单主动到支付平台查一次状态这是最后一道保险。5.3 坑二改了角色权限用户刷新还是旧菜单现象管理员给某个角色新增了「报表查看」权限该角色用户退出重新登录后菜单还是旧的。原因权限数据虽然存到了数据库但用户登录时生成的 token 里缓存了权限列表权限变更没有触发缓存失效。加上前端小程序的菜单是本地写死的没有从后端拉取「当前用户可见菜单」两个原因叠加权限变更就完全没生效。解决后端在权限变更接口里主动删除该角色的所有用户缓存 key代码里调用类似redisService.deleteByPrefix(wx:token:*)的方式粗暴但对景区系统够用。前端小程序要做一次改造登录后调/api/user/menus返回当前用户可见的菜单和按钮权限码动态渲染页面而不是每个版本把菜单写死在代码里。权限是安全边界不能用「改代码发版」代替「配置生效」。5.4 坑三停车场跨天计费对不上账每天差几十块现象财务对账发现停车场收入比收费员的交班记录少仔细核对发现过夜车辆的计费金额和人工算的不一致。原因计费实现是按「日期分段」算的车辆停了两天系统把停车时长拆成了两个自然日每天单独计费后相加。这样免费时长被用了两次封顶价也被算了两次总额偏高或偏低都有可能出现。解决改成「累计停车时长连续计算」的方案按 4.2 节的代码逻辑处理。关键改动有两个一是把计费入口从「按天拆分」改成「按总分钟数计算」二是跨天封顶只封单日连续时段不封跨天累计。改完后用一组跨天测试用例回归当天 23:50 入场次日 00:10 出场应当按 20 分钟计费而不是按「第一个自然日免时长 新的一天再免时长」来处理。把这种边界用例写进测试库以后不会再犯。5.5 坑四小程序开发者工具里一切正常真机一调用就失败现象接口在开发者工具里调用通畅扫码用真机预览所有请求全部报错。原因开发者工具默认开启了「不校验合法域名」选项局域网调试时用 http 协议和 IP 地址也能通过真机环境下微信强制校验必须满足两个条件——HTTPS 协议、域名在小程序管理后台配置为业务域名。另一个常见原因真机预览时手机和后端服务器不在同一个局域网访问不到开发机 IP。解决首先在微信公众平台配置服务器域名request 合法域名填https://api.xxx.com并在 Nginx 上配置好证书。开发阶段如果你确实只用局域网调试把后端地址从http://localhost:8080改成http://192.168.x.x:8080同时保持开发者工具勾选不校验域名App 右上角打开调试模式。但生产环境不讨论灰色手段正规上线的唯一正道是配置合法域名并启用 HTTPS。注意小程序和 H5 的域名配置是两个入口不要只配了 H5 忘记小程序后台那份。5.6 坑五把设备操作接口当作普通管理接口权限被绕过现象普通检票员通过抓包拿到了「远程开闸」的接口地址直接调用接口把闸机打开系统没有拦截。原因前端只做了按钮隐藏后端管理接口没有任何权限校验或者只在拦截器里校验了「是否登录」没校验当前用户是否有该设备分组的操作授权。设备操作接口天然暴露在管理端很容易被构造出越权请求。解决在设备命令下发之前强制走一次角色-设备权限校验使用的是 3.3 节中的checkRoleDeviceAuth方法。每次请求都动态查询授权关系授权利于缓存但是要有可见的失效时间不要永不过期。同时给这类高风险操作加上操作日志记录操作人、操作设备、动作、IP 和时间一旦发现问题能追溯到人。设备权限是这个源码包的亮点二次开发时千万别把它当普通 CRUD 给简化掉了。6. 从能用变好用补一张订单流水表系统不再是黑匣子这套源码完成度已经不低了但你要二次开发时最先补充的不是新功能而是一张订单流水表。前文讲过的支付回调幂等、权限变更追踪、操作审计都依赖一条完整的流水记录。没有流水表出问题就只能翻日志猜原因系统就是个黑匣子。我在类似的票务项目里吃过亏某一次线上出现「订单已支付但没出票」当时日志不全支付平台说回调成功了本地订单表里却没有支付时间两边各执一词最后是靠支付平台的对账单一条条核对才找出是哪一步断了。后来我养成了一个习惯所有涉及订单的操作不管成功失败先记流水再改数据。下面这张表是我最常用的流水表结构CREATE TABLE t_order_flow ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT 订单号, action VARCHAR(32) NOT NULL COMMENT 动作CREATE/PAY_CALLBACK/CHECK_IN/REFUND/ADMIN_UPDATE, before_status TINYINT COMMENT 操作前状态, after_status TINYINT COMMENT 操作后状态, operator VARCHAR(64) COMMENT 操作人ID或系统标识, source VARCHAR(32) COMMENT 来源wx_pay/alipay/admin_system/device_gate, remark VARCHAR(255) COMMENT 备注存放回调单号、异常原因等, create_time DATETIME NOT NULL );写入时机有讲究和业务操作同一个事务里写不能异步写。比如支付回调事务里更新订单状态的同时插入一条PAY_CALLBACK流水事务回滚流水也跟着没了才能保证流水和实际数据一致。remark字段不要省把第三方回调单号、异常堆栈摘要、操作前状态值都塞进去排查问题时这条记录会帮你省几小时时间。配一个简单的查询接口按订单号查全部流水就能像看时间轴一样还原这个订单的一生。再补一个验证习惯每次改动票务相关代码跑一遍「订单主链路」用例——从小程序选票下单、支付回调、部分核销、全部核销、退票、对账六个步骤的流水记录全部正确再生效。我在吃过线上的亏之后就把这套用例沉淀成了固定的回归清单。无常回归不成系统这算是我最后一条经验希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站