资讯动态

LiveHouse演出管理系统:Redis原子库存与二维码验票实战解析

发布时间:2026/10/6 8:37:03 来源:尧图企业网站定制
简介这是一套面向LiveHouse演出现场的后台管理系统覆盖用户、角色、商品、客户、采购、销售、退货及库存报溢等典型业务流程适合Java Web学习者参考也可作为毕业设计或小型演艺运营方的管理工具。压缩包共909个文件总体积仅1.61MB内含120个Java源文件及对应class编译文件、122个JavaScript脚本、184个HTML页面与134个CSS样式搭配PNG/GIF图片、JSON数据、SQL数据库脚本和YML配置前后端结构清晰导入开发工具即可快速搭建运行环境。目前已有73人学习下载。资源并非单纯代码堆砌而是将管理员、角色权限、商品、采购单、销售单、退货单、报溢单等模块统一组织控制器与页面一一对应可以帮助读者从数据库表设计、后端接口到前端交互完整理解调用链路二次开发时也能直接复用基础工程或作为课程设计参考骨架。1. LiveHouse演出管理系统是什么从预售票到艺人结算一个晚上只靠一套后台走完晚上八点开场下午六点半场地还在改调音顺序门口已经有人排队问有没有现场票——LiveHouse 演出管理系统的作用就是把预售、验票、节目单和艺人结算放进同一个后台。LiveHouse演出管理系统.zip 是这类项目最常见的交付形态一个离线压缩包后端、前端、数据库脚本和部署说明都在里面解压就能评估不依赖远程仓库。适合要给场地做票务系统的开发者、想快速看效果的主理人以及接到演出管理外包、需要判断交付物质量的工程师。下面按我实际接手这类系统的顺序从解包、起库到核心链路实现再把演出当晚最容易翻车的几个点讲透。2. 先把 .zip 跑起来解包、起库、初始化到网页能点收到这类压缩包我一般不会急着解压就跑。先看压缩包内的目录层次判断它是完整可运行的交付还是只有源码没有脚本的半成品。多数 LiveHouse 演出管理系统会按 backend / web / sql / docs 四块组织README 里写明版本要求。先花三分钟读 README能避免后面二十分钟的瞎折腾。2.1 认识压缩包结构解压前先看这四个目录mkdir -p /opt/livehouse unzip LiveHouse演出管理系统.zip -d /opt/livehouse cd /opt/livehouse tree -L 2解压后如果没有 tree直接用ls -la也能看。常见结构如下不同项目命名可能有出入但“代码、配置、脚本、文档分开放”这个习惯是通用的。目录常见内容评估时要确认backend/后端工程、Controller、Service、MapperJDK 或 Node 版本和本机是否兼容web/管理后台前端源码依赖安装命令、构建产物sql/schema.sql、seed.sql字符集是不是 utf8mb4是否带测试数据docs/部署手册、接口文档文档里的端口和账号是否和配置一致我见过不少压缩包的问题是后端是 Spring Boot 3部署文档却只写了 JDK 8前端是 Vue 2node_modules 没打包需要联网安装。所以拿到包的第一件事不是跑是核对版本。版本对不上的时候先切 IDE 的运行时版本看能不能编译再决定要不要动代码。2.2 用 Docker Compose 拉起 MySQL 和 Redis一条命令省心开局评估阶段我一般用 Docker Compose 起中间件原因很简单不动本机已有的 MySQL也不用手动装 Redis。LiveHouse 系统的高频操作——库存扣减、验票计数、订单查询——后面都要靠这两个组件先把它们跑稳。services: mysql: image: mysql:5.7 container_name: lh-mysql environment: MYSQL_ROOT_PASSWORD: livehouse123 MYSQL_DATABASE: livehouse ports: - 3306:3306 command: --character-set-serverutf8mb4 --collation-serverutf8mb4_general_ci volumes: - ./sql:/docker-entrypoint-initdb.d:ro - lh-mysql-data:/var/lib/mysql redis: image: redis:6.2 container_name: lh-redis ports: - 6379:6379 volumes: lh-mysql-data:MYSQL_DATABASE 会让容器首次启动时自动建库MYSQL_ROOT_PASSWORD 是管理密码评估阶段用简单密码没关系上线前记得换。command里显式指定 utf8mb4是为了避免后面出现中文乱码和排序差异。./sql:/docker-entrypoint-initdb.d:ro会把 SQL 脚本挂进容器的初始化目录首次启动自动执行如果这个 MySQL 镜像已经跑过一次该目录不会重复执行需要手动导脚本。Redis 这里只做端口映射不配置持久化评估阶段够用。docker compose up -d docker compose ps看到 mysql 和 redis 状态都是 Up基本就算起来了。这时用docker logs lh-mysql能确认初始化脚本有没有报错如果 SQL 用了本机 MySQL 不支持的语法日志里会直接打出来不用靠猜。2.3 初始化数据库建库脚本执行后必须复查的三件事如果容器不是首次启动或者你用了外部 MySQL就手动执行初始化脚本。顺序是先建库再导表最后核对结果。mysql -h 127.0.0.1 -uroot -plivehouse123 livehouse sql/schema.sql mysql -h 127.0.0.1 -uroot -plivehouse123 livehouse -e SHOW TABLES;SELECT table_name, engine, table_collation FROM information_schema.tables WHERE table_schema livehouse;脚本执行后必须看三件事。第一表字符集是不是 utf8mb4不是的话后面中文备注和用户昵称都可能乱。第二核心表是否齐全票务订单表 ticket_order、场次表 show_event、节目单表 show_schedule、结算表 settlement 至少要有缺表说明交付物不完整。第三有没有 seed 数据很多压缩包为了演示会预置一批测试订单评估完成后正式使用前要清掉否则库存数字从一开始就是错的。后端配置里的连接信息也要对应检查grep -r datasource\|redis backend/src/main/resources/application*.yml这里主要确认数据库地址、账号、Redis 地址和端口和你刚启动的一致。常见坑是配置里写的是远端 IP 或默认密码启动就一直报连接失败报错信息往往只有一行看起来像玄学实际就是连接串没对上。后端和前端怎么跑起来在 docs 里通常会有没有的话常见做法是后端直接以 Spring Boot 应用启动cd backend mvn spring-boot:run -Dspring-boot.run.profileslocal如果用 Node 后端就把启动命令换成 npm run start并确认环境变量里的 DATABASE_URL 指向本机。前端一般先装依赖再起开发服务cd web npm install npm run dev这一步最常耗时间的是 npm 下载慢尤其前后端分离工程会把依赖装到一半才报错。下载中断时别反复重启进程先看 npm 缓存和 registry换镜像源后重来。登录页能打开、默认管理员账号能进后台这个 .zip 的骨架就算跑通了可以开始逐模块评估业务逻辑。3. 预售票库存与二维码验票LiveHouse 高频场景的两条核心链路LiveHouse 演出的票务和普通演唱会不太一样预售票和现场票同时存在现场票随到随卖库存以场所实际容量为边界验票往往发生在门口网络信号时好时坏。所以这两条链路的设计决定了系统到底是好用还是难用。3.1 库存扣减为什么放在 Redis原子性与超卖救场早年很多人写扣库存是先 SELECT 再 UPDATE单机没问题一旦预售票放出去几百人在线抢两个订单同时读到余票 1就会卖成 2。LiveHouse 票量不算大但这种并发错误一旦发生就是门口超员处理起来非常被动。把库存扣减放进 Redis用 Lua 脚本保证原子性是当前最常见的做法。import redis r redis.Redis(host127.0.0.1, port6379, db0, decode_responsesTrue) GRAB_SCRIPT local stock tonumber(redis.call(GET, KEYS[1]) or 0) if stock 0 then redis.call(DECR, KEYS[1]) redis.call(SADD, KEYS[2], ARGV[1]) return 1 end return 0 def grab_ticket(event_id: int, order_no: str) - bool: return bool(r.eval( GRAB_SCRIPT, 2, flivehouse:event:{event_id}:stock, flivehouse:event:{event_id}:sold_set, order_no, ))Lua 脚本在 Redis 里是整体执行的中间不会有别的命令插进来所以 DECR 和 SADD 要么都成功要么都不执行。KEYS[1] 是剩余库存KEYS[2] 是已售集合ARGV[1] 是订单号。用集合而不是单独计数的好处是后续对账能直接列出所有已抢订单号。订单支付失败时要补偿把订单号从集合里移出再把库存加回去。注意库存 key 要在演出建档时初始化并设置 TTL比如演出结束后 48 小时过期避免内存里堆一堆废弃 key。3.2 生成二维码并验签验票端的离线兜底方案二维码如果只编码订单号任何人都能伪造。常见做法是加签名票面带上事件 ID、订单号、票号和发券时间后端用密钥算出签名拼在 payload 里验票时先验签名再查是否已核销。import hashlib, json, time, qrcode TICKET_SECRET replace_me_in_prod def sign_ticket(payload: dict) - str: raw .join(f{k}{payload[k]} for k in sorted(payload)) return hashlib.sha256((TICKET_SECRET raw).encode()).hexdigest()[:16] payload { event_id: 120, order_no: LH202408150001, ticket_no: T1200001, issue_at: int(time.time()), } payload[sig] sign_ticket(payload) qrcode.make(json.dumps(payload, ensure_asciiFalse).encode()).save(ticket_sample.png)签名用的 payload 必须先排序再拼串不然两端字段顺序只要不一致验签就一定失败而报错表现只是“二维码无效”排查起来特别像玄学。issue_at用于设置有效期常见做法是发券后 24 小时或演出开始后 1 小时失效防止过期票囤着。验票端拿到码后先本地验签再调用后端核销接口后端把 ticket_no 存进已核销集合断网时验票 app 用本地白名单先放行网络恢复后再批量上报核销这是 LiveHouse 门口最常见的兜底方式。3.3 现场余票与线上库存合并算对可售票数的口径现场票能不能卖卖多少不是简单地用“容量减已售”来算。还有媒体票、赠票、乐队预留票这些不算钱但占名额的票。给一个常见口径的 SQLSELECT (SELECT capacity FROM venue WHERE id :venue_id) - (SELECT COALESCE(SUM(quantity),0) FROM ticket_order WHERE event_id :event_id AND status PAID) - (SELECT COALESCE(SUM(quantity),0) FROM ticket_order WHERE event_id :event_id AND status RESERVED) AS available_tickets;这里的关键是 status 的取值要在同一个事务里读避免统计到一半有人下单。capacity 不要用观众区座位数LiveHouse 多数是站席要按消防和场地安全评估的总客流上限来填否则现场容易失控。这个口径会直接显示在现场售票员的屏幕上卖到接近 0 时系统要自动切到“暂停售票”。4. 演出编排与艺人结算环节多且最容易返工的三张表LiveHouse 一场演出通常有暖场、主咖甚至串场和 DJ set。节目单不是简单排个顺序每队还要分调音时间、试音时长和正式演出时长。排好的表演出前一天经常被改了又改没有冲突检测的话到了现场才发现两支乐队共用同一段时间只能让观众多等二十分钟。这一章讲节目单、结算单和对账单。4.1 节目单与调音时间的冲突检测先用区间比较别急着上图排期里我一般把调音时段和演出时段分开存。冲突检测用两端区间比较就能完成不需要上甘特图之类的重组件from datetime import time schedules [ {band: 暖场乐队, sound: (time(18, 0), time(18, 30)), play: (time(20, 0), time(20, 30))}, {band: 主咖, sound: (time(18, 30), time(19, 0)), play: (time(20, 45), time(21, 30))}, ] def conflict(seg1, seg2): return seg1[0] seg2[1] and seg2[0] seg1[1] for i, item in enumerate(schedules): for other in schedules[i 1:]: if conflict(item[play], other[play]) or conflict(item[sound], other[sound]): print(f{item[band]} 与 {other[band]} 的时间有重叠)判断区间重叠用“开始时间小于对方结束时间且对方开始时间小于自己的结束时间”这是左闭右开区间的标准写法。注意调音和演出都占用舞台同一块物理空间所以两个阶段都要参与冲突检测否则就会出现演出不冲突但调音撞车的情况。实际操作里数据要按 sound_start 和 play_start 建索引演出当天频繁改单时查询才不会越来越慢。4.2 分账结算按票价档位和渠道拆分的计算脚本演出结束后主办和场地之间要分票房渠道还要抽服务费。结算逻辑不复杂但精度问题很容易翻车尤其涉及百分比和手续费时。常见的错误是用 float 算钱差几分钱在总额上能看出来却很难定位是哪张票出的问题。我的做法是从一开始就全链路用 Decimal这是用 float 算钱换来的血泪经验。from decimal import Decimal def settle_ticket(price_str: str, channel: str, venue_ratio: Decimal) - dict: price Decimal(price_str) platform_fee (price * Decimal(0.03)).quantize(Decimal(0.01)) venue_share (price * venue_ratio).quantize(Decimal(0.01)) band_share price - platform_fee - venue_share return { price: price, channel: channel, platform_fee: platform_fee, venue_share: venue_share, band_share: band_share, } print(settle_ticket(120.00, showstart, Decimal(0.15)))三个需要扣的项渠道服务费、场地分成、艺人分成先后顺序固定并且每一步都做 quantize 到分。场地分成比例和渠道手续费率不要写死在代码里放到场地设置表或渠道配置表里否则下次谈了个不同比例要改代码再重新打包。退款单处理不要新建负数订单要在原订单上做冲减否则结算时同一个订单出现两条记录对账就乱了。4.3 演出结束后的逐单对账别只比总额比流水号支付渠道给的对账单和本地订单表并不是天然一致的。支付回调偶尔延迟用户可能支付成功但系统里订单还挂着“待支付”。只对比汇总金额会把几笔差账互相抵消感觉上“好像对得上”。逐单对账最稳SELECT order_no, status, amount, channel_order_no, updated_at FROM ticket_order WHERE event_id :event_id AND status IN (PAID, REFUNDED) ORDER BY updated_at;拿这张表导出后和支付渠道后台导出的交易明细按 channel_order_no 逐一匹配。匹配不上的分两类渠道有但系统无说明回调漏了系统有但渠道无说明是测试数据或手动补录。前者补单后者标记作废。这个环节看着笨但能救回不少看似“差几块钱”的大问题。5. 部署和演出当晚最常踩的五个坑排查从库存对不上到导出Excel翻车接手 LiveHouse 演出管理系统以来踩坑记录最集中的不是功能开发而是上线当晚和导出报表时刻。下面按现象、原因、解决的顺序写。5.1 预售票卖超了 50 张现象后台显示还有 80 张实际卖出 130 张开场前门口排着等退票的人。原因库存扣减用的“先查再改”并发请求下两个线程同时读到同一个余数各自扣减后数据库里还是同一个值。解决库存扣减收敛到 Redis Lua 原子脚本里下单和支付成功都走同一入口另外每晚对账任务要比较 ticket_order 的 PAID 数量与 Redis 中 sold_set 的数量发现不一致立刻告警。没有对账任务这个问题会在演出当天才暴露。5.2 现场二维码扫出来一片白现象扫码后手机显示空白或一直转圈观众说票打不开验票员以为系统卡了。原因二维码 payload 里放了发券时间验签服务要求五分钟内有效但现场手机时间不准或者网络延迟签名校验直接失败另一种常见原因是前后端字段排序不一致两边拼出来的签名永远对不上。解决票面验签有效期放宽到 30 分钟并让验票端以服务器时间为准验签字符串统一做 sorted(payload) 拼接验票 app 本地缓存最近 10 分钟内的有效票断网时允许人工核销并在网络恢复后补传。5.3 演出前 15 分钟管理后台打不开现象页面转圈接口偶发 502门口验票也间歇失败。原因默认数据库连接池只有 20 个连接票务查询集中在开场前半小时被打满订单表没建 (event_id, status) 联合索引查询变成全表扫描一个慢查询顶住几十个连接。解决连接池 maximum-pool-size 调到 100 到 200具体按并发预估给 ticket_order 加联合索引ALTER TABLE ticket_order ADD INDEX idx_event_status (event_id, status);验票核销结果先写 Redis不直接写库能明显降低开场瞬间的数据库压力。5.4 导出结算 Excel 报错现象导出到一半提示Illegal mix of collations或者文件生成了但打开是乱码。原因表库字符集不一致连接串没指定 utf8mb4另一个隐藏原因是大批量导出走 HTTP 同步请求后端超时前端以为失败。解决MySQL 连接串显式加useUnicodetruecharacterEncodingutf8把所有表统一字符集SELECT table_name, table_collation FROM information_schema.tables WHERE table_schemalivehouse; ALTER TABLE ticket_order CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;导出大文件改为异步任务先落盘再提供下载链接前端轮询任务状态。这个改动不算大但能少接不少“导出怎么又坏了”的抱怨。5.5 结算单和票务平台差四块钱现象系统里算出的场地分成和票务平台后台导出的结算单差几块钱怎么加都对不上。原因代码用了 float 算钱千分之三的手续费在不同订单上四舍五入方式不一致几笔一分钱误差汇总成几块钱。解决金额相关字段全部用 decimal代码全链路 Decimal 并显式 quantize手续费率、分成比例统一从配置表读取对账按 4.3 的逐单方式而不是只比总额。6. 让系统陪你走更远多场地复用与现场人流大屏演出管理系统的价值不只在开票当晚多场地、现场展示这些需求通常很快就会冒出来。这里讲两项不难但收益高的扩展。6.1 多场地复用把场地抽成一张配置表第二家店开出来时不用复制整个系统把场地抽成配置表就行。给场次表加 venue_id所有业务按场地过滤一次小迁移就能完成ALTER TABLE livehouse_event ADD COLUMN venue_id BIGINT NOT NULL DEFAULT 0; ALTER TABLE livehouse_event ADD INDEX idx_venue_event (venue_id, start_at);venue 表里存容量、主办方分成比例、联系人这几个核心字段所有查询都带上 venue_id票务、结算、报表自然按场地隔离。6.2 门口人流大屏SSE 推个位数频率就够演出场馆门口摆一块屏显示“已入场人数”最轻的做法是服务端向页面单向推送统计不必上 WebSocket。SSE 就够用from flask import Response import redis, time, json def sse_stream(event_id): r redis.Redis(host127.0.0.1, port6379, db0) while True: entered r.scard(flivehouse:event:{event_id}:entered) yield fdata: {json.dumps({entered: entered})}\n\n time.sleep(2)推送频率 2 秒一次足够CPU 和网络占用都很低Nginx 做反向代理时要把 SSE 的 buffering 关掉否则数据会在代理层攒一批再发大屏看起来像卡住。验票端每核销一个 ticket_no 就往 entered 集合里加一个人数统计和核销逻辑完全同步。我第一次给场地做这套系统时排期冲突检测漏了调音时段演出前两小时被电话叫醒改节目单后来把检测前移到保存时刻再没出过同类问题。LiveHouse 系统的复杂度不高但库存、验票、排期、结算每个环节都需要兜底。先按上面把核心链路口径定清楚再逐步加直播收入、周边售卖这些业务希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑