如何同时运行discord.py机器人与Flask应用并共享Users列表?
解决discord.py机器人与Flask应用同时运行及数据共享问题
核心问题分析
- 无法同时运行:
app.run()和bot.run()都是阻塞式调用,会独占主线程,导致另一个服务无法启动。 - 数据共享不可靠:使用
globals()存储用户数据,在多线程环境下存在线程安全问题,且代码可维护性差。
解决方案
一、让Flask与discord.py同时运行
将其中一个服务放到子线程中执行,避免阻塞主线程。推荐把Flask放到线程,discord.py在主线程运行(discord.py的事件循环更适合主线程)。
修改步骤:
- 导入
threading模块。 - 封装Flask启动逻辑到函数中,关闭自动重载(避免重复启动)。
- 启动Flask线程后,再启动discord.py。
import threading # ... 其他原有代码 ... def run_flask(): # 关闭use_reloader,防止Flask自动重载时重复创建线程 app.run(port=80, host="127.0.0.1", use_reloader=False) # 启动Flask子线程 flask_thread = threading.Thread(target=run_flask) flask_thread.start() # 启动discord.py(主线程阻塞) bot.run("你的Bot Token")
二、安全共享用户数据
放弃globals(),改用线程安全的内存存储或数据库实现数据共享。
方案1:线程安全的内存字典(适合小项目/测试)
用threading.Lock保护全局字典,确保多线程下读写安全。
- 定义全局存储和锁:
import threading # 键:用户Discord ID,值:用户对象 user_store = {} # 线程锁,保证读写操作互斥 user_lock = threading.Lock()
- 修改
on_ready事件初始化用户数据:
@bot.event async def on_ready(): server = bot.guilds[0] with user_lock: for member in server.members: if member.id not in user_store: user_store[member.id] = p(member.name, member.id) print("Bot is Online !")
- 修改机器人命令:
@bot.command() async def get_data(ctx): user_id = ctx.author.id with user_lock: usr = user_store.get(user_id) if usr: await ctx.send(f"age = {usr.age}") else: await ctx.send("未找到你的数据") @bot.command() async def edit_data(ctx, arg1): try: new_age = int(arg1) except ValueError: await ctx.send("请输入有效数字") return user_id = ctx.author.id with user_lock: usr = user_store.get(user_id) if usr: usr.age = new_age await ctx.send(f"age 更新为 {usr.age}") else: await ctx.send("未找到你的数据")
- 修改Flask路由:
@app.route("/me/") @requires_authorization def me(): user = disc.fetch_user() with user_lock: usr = user_store.get(user.id) age = usr.age if usr else "未找到数据" return f""" <html> <head> <title>{user.name}</title> </head> <body> <img src='{user.avatar_url}' /> <p>用户年龄:{age}</p> </body> </html>"""
方案2:使用SQLite数据库(适合长期/持久化需求)
用数据库存储数据,天然支持多线程共享,且数据不会随程序重启丢失。
- 初始化数据库:
import sqlite3 def init_database(): conn = sqlite3.connect("users.db") cursor = conn.cursor() # 创建用户表,默认年龄17 cursor.execute('''CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER DEFAULT 17)''') conn.commit() conn.close() # 启动前初始化数据库 init_database()
- 修改
on_ready事件导入用户:
@bot.event async def on_ready(): server = bot.guilds[0] conn = sqlite3.connect("users.db") cursor = conn.cursor() for member in server.members: # 不存在则插入,避免重复 cursor.execute("INSERT OR IGNORE INTO users (id, name) VALUES (?, ?)", (member.id, member.name)) conn.commit() conn.close() print("Bot is Online !")
- 修改机器人命令:
@bot.command() async def get_data(ctx): user_id = ctx.author.id conn = sqlite3.connect("users.db") cursor = conn.cursor() cursor.execute("SELECT age FROM users WHERE id = ?", (user_id,)) result = cursor.fetchone() conn.close() if result: await ctx.send(f"age = {result[0]}") else: await ctx.send("未找到你的数据") @bot.command() async def edit_data(ctx, arg1): try: new_age = int(arg1) except ValueError: await ctx.send("请输入有效数字") return user_id = ctx.author.id conn = sqlite3.connect("users.db") cursor = conn.cursor() cursor.execute("UPDATE users SET age = ? WHERE id = ?", (new_age, user_id)) conn.commit() conn.close() await ctx.send(f"age 更新为 {new_age}")
- 修改Flask路由:
@app.route("/me/") @requires_authorization def me(): user = disc.fetch_user() conn = sqlite3.connect("users.db") cursor = conn.cursor() cursor.execute("SELECT age FROM users WHERE id = ?", (user.id,)) result = cursor.fetchone() conn.close() age = result[0] if result else "未找到数据" return f""" <html> <head> <title>{user.name}</title> </head> <body> <img src='{user.avatar_url}' /> <p>用户年龄:{age}</p> </body> </html>"""
注意事项
- 确保Discord开发者后台的
重定向URI与Flask配置的DISCORD_REDIRECT_URI完全一致(包括http/https)。 - 测试时
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "true"仅用于本地开发,生产环境需使用HTTPS。 - 如果使用线程方案,避免在Flask中直接调用discord.py的异步方法,如需调用需使用
bot.loop.run_until_complete()。
内容的提问来源于stack exchange,提问作者Joseph Yosoevsky
相关产品推荐
相关产品推荐

