news 2026/9/6 10:44:08

Telegram发卡机器人实战:Python+aiogram+Redis防超卖架构详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Telegram发卡机器人实战:Python+aiogram+Redis防超卖架构详解

简介:资源是一套基于Python开发的Telegram发卡机器人源码,面向需要搭建电报自动发货场景的中小运营者或Python开发者,可完成商品管理、订单处理、卡密发放与支付回调校验等核心环节。包体共9个文件,以4个Python脚本为主线:config.py负责参数配置,epay.py对接易支付接口,func.py封装业务逻辑,main.py为程序启动入口;另附requirements.txt、README.md、SQLite数据库文件与演示GIF,压缩包仅3.08MB,结构轻量、便于本地部署和二次修改。目前已有1368人学习使用。资源内置可直接读取的数据库示例,并针对易支付聚合接口做适配,适合基于Python 3.6+环境的快速实战;演示动图可直观展示管理员面板与用户购卡流程,整体源码量不算大,对想了解电报机器人+支付集成方案的开发者有直接参考价值。

1. 发卡机器人到底在解决什么问题

1.1 从人工发货到程序发货的转折

先讲个场景:你手上有几百个卡密、激活码、兑换码要卖,以前怎么发?逐个私聊复制粘贴,发完记到Excel里。单子少还行,一天几十单就开始手忙脚乱,发错卡、漏发、重复发全都来了。更要命的是,买家凌晨两三点下单,你没法两三点还守在电脑前,等第二天醒来,退款都退了七八个。

这其实就是发卡机器人的核心价值:把"用户付款→拿卡密"这条链路完全自动化。用户通过Telegram和Bot对话,选商品、下单、付款、系统自动把卡密私聊发给买家,全程不需要人工介入。你只需要维护库存,剩下的交给程序。

我做的这个tg_faka_bot,就是基于Python实现的这样一套系统。跑了大半年,发了五万多张卡密,订单记录一条没乱过。这篇文章我会把这套系统的完整思路讲清楚,包括技术选型、数据库设计、核心代码逻辑、部署运维,以及我踩过的一些坑。适合有Python基础、想自己做一套自动售卖系统的开发者参考。

1.2 为什么选Telegram作为售卖载体

抛开访问层面的争议,Telegram的Bot API可以说是目前对开发者最友好的聊天机器人平台。它提供了完整的交互组件,不需要处理复杂的认证逻辑,官方文档清晰,社区资料多。而且Telegram的私聊会话天然适合承载卡密这种"一次性敏感内容",用户下单后,机器人通过私聊把卡密发过去,消息记录就在买家自己手里,后续查单也方便。

和自建网站相比,Telegram Bot的另一个优势是没有前端页面的开发成本。选商品、下单、支付确认,全部用Bot的对话框、内联键盘和回调按钮完成,一套交互流程写下来,比写网页端省太多时间。对个体开发者来说,这是性价比极高的方案。

2. 整体架构与技术选型

2.1 Bot框架:python-telegram-bot还是aiogram

这是所有做Telegram Bot的人都会面临的第一个选择。这两个库都足够成熟,但设计理念差别很大。

python-telegram-bot(PTB)是同步风格为主,虽然也有异步分支,但整体更适合快速原型开发。如果你只是想做一个简单的通知机器人,PTB上手更快。但发卡机器人涉及订单状态流转、支付回调、数据库读写,对并发处理能力有一定要求,我最终选择了aiogram 3.x。

aiogram基于asyncio实现,天然异步,处理高频消息时不会阻塞。发卡场景里最常见的并发情况是:同一款商品同时被多个人下单,异步框架可以更好地处理这种瞬时流量。此外aiogram的F(滤波器)机制让状态管理变得很干净,后面写代码时会体会到这一点。

环境准备方面,建议使用Python 3.10以上版本,用虚拟环境隔离依赖。我习惯用venv:

python -m venv venv source venv/bin/activate pip install aiogram sqlalchemy redis qrcode Pillow

aiogram负责Bot交互,SQLAlchemy做数据库ORM,redis用来处理库存扣减和接口频率限制,qrcode和Pillow用于生成支付二维码。requirements.txt锁好版本,部署时直接pip install -r requirements.txt

2.2 数据层与缓存层怎么分工

很多人做这种项目习惯把订单状态、库存、用户数据全部塞在内存里,跑起来没问题,一重启全丢了。正确做法是数据库为主、缓存为辅。

我用的组合是SQLite起步,数据量大了之后平滑迁移到PostgreSQL。在项目初期,SQLite完全够用——它支持事务,占用资源小,单文件备份也方便。但要注意,SQLite对并发写的支持有限,如果日订单量上千,还是建议早点换到PostgreSQL。

Redis在这里扮演两个角色:一是库存扣减的原子操作通道,二是接口层面的限流。库存扣减为什么用Redis?因为一个商品同时被上百人抢购时,如果都去查数据库再更新,容易产生超卖。Redis的DECR操作是原子性的,天然避免这个问题。最终销量再异步同步到数据库。

提示:Redis不是必须的。前期用户量少时,直接用数据库事务也能防超卖,代码反而更简单。Redis更适合在你有明显的并发压力之后再加。

3. 核心流程的代码实现

3.1 数据库模型设计

发卡机器人的核心表就三张:商品表、卡密表、订单表。

商品表记录卖什么、卖多少钱、库存用尽没有。卡密表是独立的,因为同一款商品可能对应几千张卡密,每张的状态(未售/已售)要单独跟踪。订单表则把"谁、什么时候、买了什么、付了多少钱、发货没有"全部串起来。

# models.py from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean from sqlalchemy.orm import declarative_base, relationship from datetime import datetime Base = declarative_base() class Product(Base): __tablename__ = 'products' id = Column(Integer, primary_key=True) name = Column(String(100), nullable=False) description = Column(String(500), default='') price = Column(Integer, nullable=False) # 以最小货币单位存储,避免浮点误差 is_active = Column(Boolean, default=True) created_at = Column(DateTime, default=datetime.utcnow) cards = relationship('Card', back_populates='product') orders = relationship('Order', back_populates='product') class Card(Base): __tablename__ = 'cards' id = Column(Integer, primary_key=True) product_id = Column(Integer, ForeignKey('products.id')) content = Column(String(500), nullable=False) # 卡密内容 status = Column(Integer, default=0) # 0未售 1已售 2锁定中 order_id = Column(Integer, ForeignKey('orders.id'), nullable=True) sold_at = Column(DateTime, nullable=True) product = relationship('Product', back_populates='cards') order = relationship('Order') class Order(Base): __tablename__ = 'orders' id = Column(Integer, primary_key=True) order_no = Column(String(32), unique=True, nullable=False) user_id = Column(Integer, nullable=False) # Telegram user_id product_id = Column(Integer, ForeignKey('products.id')) amount = Column(Integer, nullable=False) status = Column(Integer, default=0) # 0待支付 1已支付 2已发货 3已取消 created_at = Column(DateTime, default=datetime.utcnow) paid_at = Column(DateTime, nullable=True) product = relationship('Product', back_populates='orders')

几个细节值得注意:

  • 金额用最小货币单位(如分)存储,避免浮点精度问题。如果对接加密货币,则存到小数点后对应的最小单位。
  • Card表里的status=2是"锁定中",这个状态很关键。买家发起购买后,程序会先锁住一张卡,等付款确认后再正式标记为已售。如果超时未付,锁定期结束自动释放。
  • 订单号自己生成,别依赖自增ID。我用的方案是时间戳 + 用户ID后四位 + 随机字符,格式类似20250115A3F9K2

3.2 下单到发货的完整链路

一条正常的购买流程是这样的:

  1. 用户在Bot里输入/start,看到商品列表
  2. 用户点击某个商品,Bot内联键盘弹出购买按钮
  3. 用户确认购买,系统创建"待支付"订单,锁定一张卡密
  4. 系统返回支付地址和金额,并生成二维码
  5. 用户完成付款,系统检测到链上到账(或用户点"我已付款"等待人工/自动确认)
  6. 订单状态变为"已支付",卡密标记为"已售",通过私聊发给用户
  7. 如果用户长时间未付款,订单过期,卡密释放回库存

这七步里,最容易出问题的是第5步——支付确认。我初期接的是加密货币支付,用TRC20协议的USDT。比特币网络确认太慢,TRC20几秒钟就能到账,体验好很多。链上到账检测一般有两种方式:轮询链上API,或者接入交易回调Webhook。轮询简单粗暴,写个定时任务每30秒查一次钱包地址的流水,匹配到对应金额就把订单标记为已支付。Webhook更实时,但需要公网接口,部署复杂度高一些。

对大多数个人项目来说,轮询就够了。定期检查加上状态去重,不会漏单也不会重复发货。

3.3 发货与防超卖的核心代码

这是整个系统的心脏。发卡这个动作的本质是:从卡密表里取一张未售的卡,把它和订单绑定。听起来简单,但并发场景下必须保证同一张卡不会被发给两个人。

我第一次实现的时候没考虑并发,代码长这样:

# 错误示范 card = session.query(Card).filter(Card.product_id == product_id, Card.status == 0).first() card.status = 1 card.order_id = order.id session.commit()

两个用户同时下单时,可能查到同一张卡,一人发一次,卡重复销售了。这个问题我第一次上线就遇到了——凌晨两点有个爆款产品上架,几十个人同时下单,一个买家投诉"我收到的卡是别人用过的",数据一查,果然重复发货了。

正确的做法是用数据库行级锁,把"查询未售卡 + 标记已售"变成原子操作:

from sqlalchemy import select, update from sqlalchemy.orm import Session def allocate_card(session: Session, product_id: int, order_id: str): """原子地分配一张卡给订单,返回卡内容""" # 找出最早的一张未售卡,锁定该行,SKIP LOCKED跳过已被其他事务锁定的行 stmt = ( select(Card) .where(Card.product_id == product_id, Card.status == 0) .order_by(Card.id) .limit(1) .with_for_update(skip_locked=True) ) card = session.execute(stmt).scalar_one_or_none() if not card: return None # 无卡可发 # 同一事务内更新卡密状态和订单状态 card.status = 1 # 已售 card.order_id = order_id card.sold_at = datetime.utcnow() order = session.get(Order, order_id) order.status = 2 # 已发货 session.commit() return card.content

这里最关键的是with_for_update(skip_locked=True)FOR UPDATE把查到的行锁住,其他事务要操作这行必须等锁释放;SKIP LOCKED则让其他事务跳过已经被锁的行,直接取下一张。这样一来,并发下单时数据库会自动串行化"取卡"这个过程,不会超卖。

如果用了Redis做扣减,流程就是先DECR库存,扣成功再执行数据库分配。Redis用来做门槛,数据库用来做最终一致性保证,两者不冲突。

注意:千万不要把"扣Redis库存"当成最终结果。Redis本身不保证持久化,万一宕机可能丢数据。数据库里的卡密分布才是唯一真理,Redis库存只用于快速判断"还有没有货"。

4. 上线部署与长期维护经验

4.1 用systemd还是Docker

我在部署上纠结过一阵。早期用Docker Compose,把Bot、Redis、数据库全部容器化,一台小机器跑起来很省心。但后来发现问题:每次改代码要重新构建镜像,调试不太直观;日志分散在容器里,排查问题要docker logs一条条翻。

后来我干脆回归传统方式:裸机 + systemd。服务器上装好Python环境,代码用Git同步,systemd托管整个Bot进程。日志统一进 journalctl,重启、启动、看状态都用一套命令解决。

这样做的好处是调试成本低。改了代码,systemctl restart tg-faka就生效,前后不到三秒。对于个人项目,这种快速迭代的方式比容器化更舒服。

一个最小可用的systemd服务配置:

# /etc/systemd/system/tg-faka.service [Unit] Description=TG Faka Bot After=network.target redis.service postgresql.service [Service] User=www-data WorkingDirectory=/opt/tg_faka_bot ExecStart=/opt/tg_faka_bot/venv/bin/python main.py Restart=always RestartSec=5 [Install] WantedBy=multi-user.target

Restart=always很关键,防止进程意外退出后没人管。如果你担心系统重启后服务不自动拉起,执行systemctl enable tg-faka就好。

4.2 日志、监控与数据备份

个人项目最容易忽略的就是日志。早期我的Bot只有print输出,出了问题全靠猜。后来我老老实实用了Python标准库的logging模块,把日志分成两类:操作日志和错误日志。

操作日志记录关键业务节点:谁下单了、订单状态怎么变的、卡密发给了谁。错误日志记录异常堆栈,方便定位代码问题。日志格式里带上时间戳和订单号,排查问题时用grep一搜就能定位。

我习惯每天凌晨用cron跑一次数据库备份:

# backup.sh #!/bin/bash BACKUP_DIR="/backup/tg_faka" DATE=$(date +%Y%m%d) mkdir -p "$BACKUP_DIR" pg_dump tg_faka > "$BACKUP_DIR/tg_faka_$DATE.sql" find "$BACKUP_DIR" -name "*.sql" -mtime +7 -delete

备份保留7天,配合对象存储或者网盘同步,出问题也能恢复到最近状态。没有备份的发卡系统就像没有安全带的赛车——平时没事,出事就是大事。

5. 进阶玩法与扩展思路

5.1 从单一发卡走向商城化

基础版本的发卡机器人只能"选一个商品→买一个",用户买多件要下好几次单。我在第二版就做了购物车和商品分类的功能。

购物车的实现思路不复杂:用Redis存用户的临时购物数据,key为cart:{user_id},value是商品ID到数量的哈希表。用户每次发起购买时,把购物车里的商品一次性转成订单列表。结算页展示所有商品和总价,用户一次付款,系统逐个商品分配卡密。

商品分类则是在Product表上加一个category字段,展示时按分类分组。Telegram的InlineKeyboard支持多行按钮,把分类做成第一层菜单,点进去再展示分类下商品,交互体验会好很多。

5.2 防滥用与风控设计

发卡机器人本质上是线上交易系统,天然会招来各种恶意行为。我总结了几种最常见的攻击方式:

  • 批量下单不付款:占着卡密锁定了却不付款,导致库存被锁死。解决方案是给订单加过期时间,一般15分钟;超时未支付,自动释放锁定的卡密,订单标记为"已取消"。
  • 刷接口/暴力尝试:用Redis做用户维度限流,比如每个用户每分钟最多发起10次查询类操作。
  • 恶意举报:这个比较无解,只能尽量让自己的商品和文案合规,避免下游纠纷。

此外,所有管理操作(添加商品、上架卡密、查询订单)都只允许管理员账号调用。管理员列表写进配置文件中,Bot启动时加载到内存。判断用户身份时,直接比对message.from_user.id是否在管理员列表里,简单可靠。

5.3 多级代理分销

这是后来一个朋友强烈要求加的。他的业务模式是:有渠道帮他卖卡,收益按比例分成。于是我做了代理体系。

实现思路:用户表里加一个referrer_id字段,记录谁邀请来的。代理每次购买商品,系统自动计算佣金给上级,佣金记录在单独的佣金流水表里。下级买了卡,上级的余额实时变化,余额可以提现,也可以抵扣购买。

代理分成比例放在商品表里,每个商品单独设置,比如5%、10%、20%不等。分销逻辑不复杂,但要注意佣金计算的时序——必须在下级订单确认成功后才能入账,防止下级退款了上级还拿佣金。

写在最后的一些体会

这个项目做下来,我最深的感受是:发卡机器人表面上是个"聊天机器人项目",本质上是一个简化的电商交易系统。库存一致性、订单状态机、支付确认、防超卖、数据备份,这些东西随便拿一个出来都是电商系统要解决的问题,只不过交互载体换成了Telegram。

如果你想做类似的东西,我的建议是不要一上来就追求功能齐全。先把"下单→付款→发货"最小闭环跑通,哪怕支付确认用最傻的人工确认方式,也比永远停留在设计阶段强。系统跑起来之后,再根据真实流量和用户反馈逐步完善。

最后分享一个小技巧:给每个订单生成支付二维码。无论对接什么支付方式,在付款页面展示一个大大的二维码,用户扫码就能付,比让用户手动复制地址、金额靠谱得多。我把这个功能加上后,支付失败率明显下降,用户咨询量也少了一大截。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/6 1:30:31

为什么你的 Coding Agent 每次都在从零开始?

昨天花半小时跟 Agent 解释清楚的业务逻辑,今天一开新会话,它又什么都不记得了。我和 Agent 的"失忆"日常上周我在赶一个支付模块的重构。项目用的是一套比较冷门的内部框架,文档不全,很多设计意图藏在老代码的注释里。…

作者头像 李华
网站建设 2026/9/5 18:40:08

测试开发笔试题怎么准备?从网易真题看核心考点与备考策略

正值春招季,每年这个时候都会有人问我"测试开发到底怎么准备""笔试考什么"。我接触测试开发这个方向也有不少年头了,带过团队,也出过笔试题,见过很多候选人在笔试环节吃亏踩坑。今天借网易2018年实习生招聘测…

作者头像 李华
网站建设 2026/9/5 1:00:43

阿里校招笔试题解析:从算法到数据库,夯实计算机基础

2013年秋天,我参加了阿里巴巴研发工程师的校招笔试。那个年代互联网大厂的笔试还没有全面搬到线上,多数还是纸质试卷,两个小时,一叠A3纸正反两面印满题目。考完出来,候考区一片安静,不是放空,是…

作者头像 李华
网站建设 2026/9/5 17:29:04

Proxmox VE一键初始化:换源、去订阅提示、硬件直通脚本详解

简介:本资源是面向Proxmox VE 7.x至9.x系统管理员与虚拟化技术爱好者的Shell自动化运维工具包,聚焦解决换源加速、关闭订阅提示干扰及GPU/PCIe硬件直通配置三大高频痛点,尤其适用于国内网络环境下的私有云部署、实验室测试及个人NAS虚拟化场景…

作者头像 李华