尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
用Python+Django开发网吧会员上机计费系统实战指南
接网吧管理系统这类活儿十年前跟朋友合伙做网咖维护的时候我就没少折腾过。现在很多年轻人想接这种单子练手或者给亲戚开的网吧做套会员计费系统聊得最多的就是“Python Django 能不能搞定”。说实话完全可以而且比我早期用 PHP 写的那套省心太多。这个标题里说的“网吧网咖会员上机管理系统”本质上就是一台本地局域网机器上跑的收银台系统核心就三件事会员充值与登记、按时间计费、机位状态切换。它的难点不在于页面多花哨而在于计费逻辑必须严谨尤其是并发和重复操作的防护搞不好就会让人觉得“这系统吞钱”。我这次就以一个完整项目为例从需求拆解、模型设计、计费流程、代码结构到部署排查把整条链路捋一遍。整个工程代号我习惯叫它 eas18u43内部项目名没有特殊含义就是当时一个版本号。下面按我实际开发的顺序来讲内容会比较长但每一步都是能直接落到键盘上的干货。1. 从需求梳理到模块划分为什么先想清业务边界1.1 网吧管理系统真正要做的事很多人一听到“网吧管理系统”脑子里立刻浮现出三个屏幕一套后台那种工业级产品什么远程锁定、云端监控、自动下机听着很唬人。但回到我实际接过的场景大多数中小网吧要的就是一张桌子、一台机器、一个老板盯着收银台的朴素流程客人进来要么办卡充值要么交押金临时上机选个空闲机位系统记下开始时间结账时按分钟算钱顺便可能还要买瓶饮料刷包薯片。把这条链路跑顺系统就已经值一大半钱了。所以功能清单不需要一开始就列得很长我一般先分成四个核心域会员域会员注册、实名登记有些地区需要身份证、充值钱包、等级折扣、计费优惠。机位域机位编号、状态空闲/占用/维护、当前上机的会话记录。计费域按时间生成费用、支持按小时或包段计费、自动续费与结算。商品域网咖小卖部的商品销售金额可以直接从会员余额里扣。这四个域一旦理清楚后台管理端的菜单基本就固定了会员列表、机位管理、上机记录、充值流水、商品库存、账单报表。这个标题里的“会员上机管理”其实已经把最关键的轮廓刻画出来了后台就是围绕这六个页面做增删改查加状态流转。提示跟老板谈需求时最忌讳上来就画大饼。二三十台机器的小网吧用不上自动排队和云端监控先把结账算明白比什么都强。1.2 Django 在这类系统里的取舍为什么我会推荐 Django 而不是 Flask 或者 FastAPI因为这种内部管理系统80% 的功能是“表单 列表 统计”Django 自带 Admin、ORM、表单校验和迁移工具能在极短时间内把后台搭出来。特别是 Django Admin稍微定制一下就已经能顶一套专业后台的日常运营功能会员搜索、机位筛选、流水过滤全都有。更重要的是Django 的 ORM 对事务的支持非常好我的计费核心流程说白了就是“创建会话、扣余额、改机位状态”这几次数据库写操作用transaction.atomic包起来以后任何一个环节失败都能整体回滚这在金钱相关系统里是最起码的底线。当然Flask 也能做但这属于“所有细节都要自己拼”的模式适合练手但不适合快速交付。对大多数中小型网吧团队可能只有一两个开发后期维护也是老板找的兼职Django 的学习门槛和后期可维护性都更均衡算是这个场景里的“标准答案”。聊项目时我常打一个比方Flask 是给你一堆零件让你自己造车Django 是给你一辆能跑但局部可以改的整车。内网管理系统要的是稳定不是炫技。2. 数据模型落地把会员、座位和账本绑成一张网2.1 会员账户余额与等级是两套逻辑设计数据表时一开始最容易犯的错就是把所有信息塞进一张表里比如会员名称、手机号、余额、等级、累计消费、积分全部堆在Member里。看起来简单但一旦涉及多笔充值和多次消费账目就会越来越难对。我的习惯是“用户身份”和“资金账户”分开。Django 自带User表后台员工用这表没错但我不会把普通会员直接挂到User上因为会员生命周期跟员工完全不同。会员映射到Member表字段包括name称呼或真实姓名phone手机号作为账号password_hash虽然很多小网吧不要求会员登录但后续要做小程序查询就必须留level会员等级普通/银卡/金卡discount折扣率或者从等级表里取balance账户余额total_consumption累计消费金额created_at/updated_at余额我直接用DecimalField绝对不用FloatField。FloatField会出现 0.1 0.2 ! 0.3 的经典问题金额算错可不是闹着玩的。DecimalField指定max_digits10, decimal_places2在 Python 里就是Decimal对象计算精度完全可控。折扣逻辑也值得多想一步。会员等级和折扣是一对多的关系但初期不需要做完整的 RBAC 权限系统只需要在会员表里加一个等级字段或者单独一张MemberLevel表等级表里存level_name和discount_rate。实际计费时读取会员等级对应的折扣生成订单快照把折扣率记录在订单里。这么做的好处是以后等级策略变了历史订单不会受影响。2.2 机位与会话记录状态机是核心机位表ComputerSeat本身很简单就是seat_no、zone区域、status三个字段最关键。但机位是“状态敏感”的资源系统里最怕出现“界面上写着空闲实际有人坐了半天”的情况。所以status字段一定要用明确的状态常量我一般定义成三种available空闲可用in_use有人使用中maintenance维护中管理端所有入口都以这个状态为准。比如开台时先检查座位是available才允许创建会话下机结账时先把会话状态置为finished再把座位改回available。这些操作必须在同一个数据库事务里完成防止某一步失败导致状态错乱。真正撑起核心业务的是上机记录Session表。每个会话就是一次完整的上机过程它的字段比机位重要得多seat外键指向机位member可选如果是临时散客则可以为空start_time开台时间end_time结算时间status进行中/已结束/异常中止rate当时生效的分钟费率这个是要做快照的total_amount最终订单金额operator操作员工把费率快照存在会话表里很有必要。因为网吧的费率会调整如果不做快照回头补历史账单时拿现在的费率去计算账目全是错的。这也是很多初学项目最容易忽略的地方。提示Session是 Django 自带的会话存储表为了避免命名冲突我会在 app 里直接叫ComputerSession或者UseRecord。取名时要多看几眼不然迁移时容易跟框架内置表搞混。2.3 商品销售和充值流水每一分钱都要留有痕商品销售不是主业务但一定得有不然收银员一边开系统一边还得手算零食钱效率太低。我建两张表一张Goods商品一张OrderItem下单明细。订单可以关联到ComputerSession因为很多客人是上机中间点饮料随后直接结账一起扣款。字段上需要注意price是商品当前单价quantity是数量subtotal是单项小计订单头里存total_amount支付方式标记为balance余额支付还有一张非常重要的表BalanceRecord也就是资金流水表。凡是会员余额变动不管是充值、消费、退款、赠送都要在这里留一行记录。字段包括member外键change_type充值/消费/退款/调整amount变动金额正负balance_after变动后余额快照remark备注created_at为什么必须记录balance_after我曾经吃过不记快照的亏。有一次财务对账发现某会员的余额和流水加总对不上因为中间有一次程序改造把初始值改了没有快照就再也查不出当时账户到底是几块钱。加了balance_after后所有问题都能从某一条流水开始重新推导这个字段非常便宜但能省掉巨大麻烦。3. 上机计费流程开台、结算、防重扣的具体实现3.1 开台上机从选择座位到建立会话开台流程大概是收银员看到客人进来在页面上输入会员手机号或者直接选“散客”然后选一个空闲座位点开台。后端要做的事我按顺序写一下查询座位并确认状态是available。查询会员余额如果余额低于某个阈值比如不足一小时费用可以允许开台但给个提示。这里不同网吧策略不同有些要求余额必须够预付押金有些允许先上机后结账。在同一个事务里把ComputerSeat.status更新为in_use同时创建一条ComputerSession状态为active记录当前时间为start_time。费率从当前生效的费率表中读取写入会话快照。这个时候不需要立刻扣钱只做“计时开始”。真正扣费发生在结算时。这样设计也是为了应付那种“客人玩了半小时说要下机一看余额不足”的场景不用来回折腾复杂的预授权。注意如果网吧要求“押金上机”那就是开台时先锁定一个预计金额。实现上可以在会话里增加一个hold_amount字段结算时用最终费用减去这个预扣金额多退少补。但这个功能不建议第一版就做先把结算跑通更重要。3.2 下机结账费用试算与余额支付下机结账是整个系统的命脉我见过好几个项目在这里写得稀烂。核心公式其实很朴素费用 向上取整到分钟的时长 × 分钟费率 × 会员折扣时长计算要注意时区。Django 默认USE_TZTrue启动项目时我建议把TIME_ZONE设为本地时区例如在中国内地就设置成Asia/Shanghai。但即使设置了DateTimeField存储的依然是 UTCPython 代码里拿now()时也要确保用的是django.utils.timezone.now()而不是datetime.datetime.now()否则很容易出现相差 8 个小时的诡异问题。分钟数建议向上取整。客人实际玩了 61 分钟按 61 分钟算不能四舍五入成 1 小时。这是网吧行业的通行做法也是老板最在意的一条规则。把分钟数total_minutes ceil(seconds / 60)写出逻辑后在视图里单独抽成一个函数方便测试。结算事务内部的步骤依次为查询ComputerSession确认状态为active否则直接返回“重复结算”错误。计算使用时长和总费用。如果关联了会员检查余额是否足够。如果不够从界面提示收款方式切换为现金或扫码补差。扣除会员余额创建BalanceRecord流水。将会话状态更新为finished写入end_time和total_amount。把机位状态从in_use改回available。这六步必须包在transaction.atomic里。我自己见过太多因为“前台看到结算成功、后台机位没复位”而半夜被老板电话吵醒的情况。把这个事务包牢就能从根上避免这类脏数据。3.3 并发与重复操作用数据库事务兜底很多新手不理解一个收银台系统怎么会遇到并发问题答案是——会有两个人在几乎同一瞬间操作同一个会员或同一台机器。举例来说收银员 A 在给会员 X 结算下机同时收银员 B 看到 X 买了瓶饮料也在扣 X 的余额。如果不加锁两次读到的余额都是 50 元各扣 10 元最后数据库里余额变成 40 元而不是 30 元。这就是经典的并发超扣。对付这个问题最稳妥的方式是利用数据库行锁。Django ORM 里对应的就是select_for_update()。在我结算的函数里先这样查会话from django.db import transaction with transaction.atomic(): session ( ComputerSession.objects.select_for_update() .select_related(seat, member) .get(pksession_id, statusactive) ) # 后续计算与扣款都在这个事务里完成select_for_update()会锁定这一行直到事务提交或回滚。第二个请求过来时只能等待等第一个人处理完再继续。对于网吧这种量级行锁已经完全够用不需要引入 Redis 分布式锁。接口幂等性也很重要。最直接的方案是前端在结算按钮点击后立刻禁用按钮后端判断会话状态只能从active到finished一旦不是active就直接报“该会话已结算”。这样即使请求被重复发送也不会扣第二次钱。实操心得我后来在结算入口加了一个request_id每次点按钮生成一个唯一 ID后端把它存在会话表里。就算网络抖动导致前端重发请求第二次也能根据同一request_id直接返回第一次的结果。这招对做桌面收银客户端尤其管用。4. 关键代码结构与后台配置4.1 目录组织与核心模块以我常用的工程布局来说Django 项目内部一般分两个 appaccount处理会员、充值、登录鉴权seat处理机位、上机、计费、商品订单。如果有更复杂的报表需求再单独拆一个reportapp但初期两个足够。project_root/ ├── manage.py ├── config/ # 项目配置 │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── account/ │ ├── models.py # Member, BalanceRecord, MemberLevel │ ├── views.py # 会员列表、充值接口、开卡接口 │ ├── admin.py │ └── urls.py ├── seat/ │ ├── models.py # ComputerSeat, ComputerSession, Goods, OrderItem │ ├── views.py # 机位列表、开台、结算、商品下单 │ ├── services.py # 计费计算、事务流程 │ ├── admin.py │ └── urls.py └── templates/ ├── member_list.html ├── seat_board.html └── session_detail.htmlservices.py这个文件是我特别推荐的。把计费、开台、结算这些核心业务逻辑全部抽到 services 层不要在views.py里写大段事务代码。好处有两点一是视图变薄看起来清爽二是单元测试可以直接调用 services 层不用每次起 HTTP 请求。计费函数的骨架长这样from decimal import Decimal, ROUND_CEILING from datetime import timedelta from django.utils import timezone class BillingService: staticmethod def calculate_amount(session): delta timezone.now() - session.start_time total_seconds max(delta.total_seconds(), 0) total_minutes int((total_seconds 59) // 60) # 向上取整到分钟 base_amount Decimal(total_minutes) * session.rate if session.member and session.member.discount: amount base_amount * session.member.discount else: amount base_amount return Decimal(amount).quantize(Decimal(0.01), roundingROUND_CEILING)注意我用了(total_seconds 59) // 60这个整数算法来模拟ceil避免引入额外浮点误差。金额小数位用quantize统一保留两位并且向上取整。这一点老板喜欢——因为即使只有 1 秒的零头也按一整分钟收钱。4.2 Django Admin 的高效配置Django Admin 是给运营和管理员用的。我不建议把它隐藏到几乎不可用毕竟小网吧根本不会请专业运维老板自己点几下要能看明白。以会员列表为例Admin 里必须配置的几项是list_display手机号、姓名、余额、等级、累计消费、更新时间。search_fields至少包括手机号和姓名。list_filter会员等级、创建日期。readonly_fieldsbalance、total_consumption不允许直接在后台手工改余额。要改可以走手动充值流水接口这样每一笔变动都有迹可循。机位管理的list_display加上机位号、区域、状态再用list_editable直接支持批量维护。但注意机位状态这块不要让管理员随手点最好通过“下机结算”流程来变更否则容易造成和会话表不一致。4.3 前台页面与 API 对接要点现在很多网吧收银端其实是一个 HTML 页面跑在收银机的浏览器里外加一个大的展示屏显示机位状态。页面难点不复杂主要看你会不会用 Django 模板。我常用的做法是机位大屏用seat_board.html每隔几秒通过 JavaScript 请求一个接口/api/seat-status/返回所有座位的状态和当前已用时长。前端不直接算而是由后端返回数据这样座位的状态来源永远是数据库。setInterval(async () { const resp await fetch(/api/seat-status/); const data await resp.json(); data.forEach((seat) { const el document.getElementById(seat-${seat.id}); el.className seat.status; el.querySelector(.duration).textContent seat.duration_text; }); }, 5000);这个方案对收银机和 LED 大屏都适用。只要保证 Django 开启了csrf_exempt或者正确传递 CSRF Token 即可。内部局域网调用的话为了省事我常把这类只读接口写成不需要登录的但前提是 Django 只监听内网 IP不要绑到公网。安全提醒内部系统也别完全裸奔。Djangosettings.py里把ALLOWED_HOSTS配好DEBUGFalse管理后台 URL 改成一个不常见的路径比如把admin/换成manage/。这些操作十分钟能搞定却能挡掉大部分扫描脚本。5. 部署上线与常见问题排查5.1 生产环境选型开发阶段跑python manage.py runserver就够了但生产环境不建议这么用Django 自带服务器是单线程的而且没有任何安全处理。我在生产环境用的是 Linux Nginx Gunicorn MySQL或者 PostgreSQL。具体部署流程整理一下在服务器上建虚拟环境安装requirements.txt。执行python manage.py migrate。用python manage.py collectstatic收集静态文件。配置 Nginx 反代到 Gunicorn 监听的本地端口比如127.0.0.1:8001。把 Nginx 的root指向静态文件目录。数据库方面不要用 SQLite 跑正式环境。SQLite 对高并发写支持很差多人同时开台结账时容易出现“数据库被锁”的报错。哪怕只有二三十台机器我也建议上 MySQL并用 InnoDB 引擎。表不多建库后基本不需要专门优化但事务和行锁能力完全不是一个级别。5.2 高频问题排查速查表我会把实际运营中被问得最多的问题整理成下面这张表方便你直接对号入座。现象最可能原因解决思路计时多算了 8 小时TIME_ZONE设置不对或代码里混用了 Python 原生datetime.now()全部统一使用django.utils.timezone.now()TIME_ZONE设为本地时区金额出现 5.0000000001 这种数使用了FloatField或者float计算金额模型字段和计算全部换成DecimalField并quantize到两位小数点击下机后页面报错机位仍占用结算事务中出现异常但没有正确回滚确认transaction.atomic包裹了全部步骤并查看错误日志定位异常位置并发结算导致余额变负数扣款前读取余额未加锁使用select_for_update()锁定会员行或会话行会话状态一直是active但机位空闲有人直接从 Admin 改了机位状态绕过了会话流程管理端机位状态不要开放给人工修改统一走服务层方法变更同一个会员余额查询和页面显示不一致有未刷新缓存的查询或直接改库养成每次从数据库重新查询的习惯禁止在 Admin 里直接改余额通过流水接口调整排查的时候我一般有个固定思路先看日志有没有 500 错误再看数据库里关联的几张表状态是否一致最后把DEBUG临时打开复现一下。这套三板斧用来对付中小系统的疑难杂症基本百试百灵。技巧为了监控异常我习惯在LOGGING配置里加一个FileHandler把 ERROR 级别的日志单独写到logs/error.log。出了问题先tail -f logs/error.log比到处翻 Nginx 日志快得多。5.3 备份与每日巡检网吧系统的数据量不大但每天都产生流水备份策略必须固定。我一般写一个定时任务每天凌晨 3 点用mysqldump把核心库备份出来然后保留最近 7 天的备份文件上传到另一台机器或网盘。这步看似基础但很多老板从来没做过一旦硬盘坏了会员余额和上机记录全都找不回来那麻烦就大了。日常巡检也不用太复杂每天早上看三张表就够了前一天的流水总数、会员总余额、未结束的会话数。如果出现未结束会话特别多的情况多半是哪个环节没结算干净及时处理掉就行。最后再分享一个小经验这套系统看着小实际开发中我踩过最大的坑就是过早地把精力放在花哨功能上。第一版先做好固定的“开台-计费-结算-充值”四大流程比什么都重要。等到老板说“客人想在线看余额”或者“想用手机扫码付押金”再去迭代小程序端也来得及。Django 的好处就在这时候体现出来数据库结构设计得清晰后面加接口就是几天的活儿而不是推翻重来。做这类系统宁可数据模型多花时间想清楚也别着急把页面配色搞得天花乱坠。账目和状态不乱系统就已经成功了一大半。
RELATED

相关推荐

嵌入式板端计算机视觉落地实战:从‘看见’到‘看懂’

嵌入式板端计算机视觉落地实战:从‘看见’到‘看懂’

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/18 1:27:02
xerj code --mode hybrid 在混合映射语料上静默丢结果的诊断与修复:从 1146 复现到 PR 1177

xerj code --mode hybrid 在混合映射语料上静默丢结果的诊断与修复:从 1146 复现到 PR 1177

人工智能搜索引擎本地部署后端MCP 服务 【免费下载链接】xerj XERJ is the new way for AI to search data. Its autoindex capability activates agents to know your data without the token waste of grep and sed. One command indexes code, docs, logs and PDFs for sear…

📅 2026/10/18 1:27:02
163MusicLyrics 完整指南:用网易云和 QQ 音乐链接快速下载 LRC 歌词

163MusicLyrics 完整指南:用网易云和 QQ 音乐链接快速下载 LRC 歌词

163MusicLyrics 完整指南:用网易云和 QQ 音乐链接快速下载 LRC 歌词 【免费下载链接】163MusicLyrics 云音乐歌词获取处理工具【网易云、QQ音乐】 项目地址: https://gitcode.com/GitHub_Trending/16/163MusicLyrics 凌晨一点,你想给一首日文歌配…

📅 2026/10/18 1:27:02
MORE NEWS

更多资讯

📰

OpenResearch:面向科研工作流的本地优先CLI协议栈

1. 项目概述:OpenResearch 是什么,它解决的不是“另一个 CLI 工具”,而是研究工作流的结构性失衡OpenResearch 不是一个新发布的命令行工具二进制包,也不是某个大厂刚开源的 AI 编程插件。它是一套面向真实科研与工程研究场景的本…

📰

AIUI数据库客户端真实压力测试实录

1. 这不是Demo跑通,是真实数据库客户端的“压力考场”TestStar 这个名字在内部测试圈里,早就不只是个工具代号了——它是一套被OceanBase、TiDB、达梦等国产数据库团队反复验证过的真机测试框架。而这次标题里的“AIUI 真实数据库客户端测试Case执行实录…

📰

Milvus+LangChain+Ollama打造生产级RAG:元数据与标签过滤实战

Milvus LangChain Ollama 这套组合,我大概见过不少团队在搭,但说实话,十有八九都卡在同一个地方:不是向量检索跑不起来,而是元数据(Metadata)和标签(Tag)的处理太糙了。…

📰

Android自定义图片SeekBar:接管onDraw实现精准触摸进度条

简介:Android自定义SeekBar实例工程,面向有一定Android基础、希望美化控件样式的开发者。资源讲解如何用图片替代系统默认进度条背景与滑块,通过继承SeekBar、定义自定义属性并在布局中引用,实现个性化视觉效果,可应用…

📰

芯片全流程低功耗设计:从UPF规范到物理签核的PPA协同优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

biliTickerBuy 抢票辅助工具:扫码登录到一键开抢的完整上手指南

biliTickerBuy 抢票辅助工具:扫码登录到一键开抢的完整上手指南 【免费下载链接】biliTickerBuy b站会员购购票辅助工具 项目地址: https://gitcode.com/GitHub_Trending/bi/biliTickerBuy 晚上八点开票,你在会员购页面狂点刷新,手速再…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬