You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python+Discord.py开发中JSON文件莫名变为随机字符串的问题求助

解决Discord.py积分系统中points.json文件乱码问题

我之前开发Discord Bot的类似积分系统时,也碰到过一模一样的问题!你的points.json变成随机字符串,本质是异步环境下的文件并发读写冲突,再加上代码里的文件操作缺少安全处理导致的。下面一步步给你分析问题和解决方案:

问题根源拆解

  1. 异步并发冲突:Discord Bot是异步运行的,多个用户同时触发命令时,可能有多个任务同时打开、读写points.json。当一个任务还在写入(此时文件内容是不完整的JSON),另一个任务读取了这份无效内容,后续写入就会把乱码保存下来。
  2. 文件流管理不严谨:你手动调用f.close(),但分支逻辑中如果出现异常(比如用户输入中断),可能导致文件流未正确关闭,缓冲区内容没完全写入磁盘,进而损坏文件。
  3. 用户标识不合理:用str(user)作为键,这个值是用户名#1234格式,用户修改昵称后字符串会变化,导致旧积分数据丢失——这虽不是乱码直接原因,但也是严重的潜在问题。

修复步骤及优化代码

1. 用with语句管理文件(强制自动关闭)

with语句会自动处理文件的打开和关闭,哪怕中间出异常也能保证文件流被正确关闭,避免资源泄漏。修改你的积分读取函数:

import json
from typing import Optional

def get_points(bot, user_id: str) -> int:
    try:
        with open('points.json', 'r') as f:
            points = json.load(f)
        return points.get(user_id, 0)  # 用户未注册时默认返回0
    except FileNotFoundError:
        # 文件不存在时自动初始化空字典
        with open('points.json', 'w') as f:
            json.dump({}, f, indent=4)
        return 0
    except json.JSONDecodeError:
        # 文件已损坏时重置为空字典
        with open('points.json', 'w') as f:
            json.dump({}, f, indent=4)
        return 0

2. 优化enable命令,改用用户ID作为唯一标识

把str(ctx.author)改成str(ctx.author.id)——用户ID是永久唯一的,不会随昵称变更而改变,同时加入错误处理:

@pointSystem.command()
async def enable(ctx):
    user_id = str(ctx.author.id)
    current_points = get_points(bot, user_id)
    
    if current_points >= 0:
        await ctx.send('Already enabled for this user!')
        return

    io = await ui.prompt(ctx, 'This command enables the point system\nAre you sure you want to enable this?')
    if io.lower() == 'yes':
        await ctx.send('Ok, enabling...')
        # 写入时先读取全部内容,修改后再写入
        with open('points.json', 'r+') as f:
            try:
                points = json.load(f)
            except json.JSONDecodeError:
                points = {}
            points[user_id] = 0
            # 移动文件指针到开头,写入后截断多余内容
            f.seek(0)
            json.dump(points, f, indent=4)
            f.truncate()
        await ctx.send('Enabled for this user!')
    else:
        await ctx.send('Alright, stopping command execution...')

3. 添加文件锁彻底解决并发问题

异步环境下仅靠with还不够,需要用文件锁确保同一时间只有一个任务能操作points.json。先安装filelock库:

pip install filelock

然后修改代码,在文件操作前后加锁:

from filelock import FileLock

# 封装带锁的安全读取函数
def get_points_safe(bot, user_id: str) -> int:
    lock = FileLock("points.json.lock")
    with lock:
        return get_points(bot, user_id)

@pointSystem.command()
async def enable(ctx):
    user_id = str(ctx.author.id)
    current_points = get_points_safe(bot, user_id)
    
    if current_points >= 0:
        await ctx.send('Already enabled for this user!')
        return

    io = await ui.prompt(ctx, 'This command enables the point system\nAre you sure you want to enable this?')
    if io.lower() == 'yes':
        await ctx.send('Ok, enabling...')
        lock = FileLock("points.json.lock")
        with lock:
            with open('points.json', 'r+') as f:
                try:
                    points = json.load(f)
                except json.JSONDecodeError:
                    points = {}
                points[user_id] = 0
                f.seek(0)
                json.dump(points, f, indent=4)
                f.truncate()
        await ctx.send('Enabled for this user!')
    else:
        await ctx.send('Alright, stopping command execution...')

4. 额外建议:定期备份文件

加一个简单的备份函数,防止文件损坏后丢失数据:

import shutil

def backup_points():
    try:
        shutil.copy2('points.json', 'points_backup.json')
    except Exception as e:
        print(f"Backup failed: {e}")

你可以在每次写入文件后调用这个函数,或者用定时任务每天备份一次。

为什么之前会出现乱码?

当多个异步任务同时访问points.json时,一个任务在写入过程中(文件内容是不完整的JSON片段),另一个任务读取了这份无效内容,后续写入时就会把这些片段保存下来,最终变成看起来像随机字符串的无效JSON。加上手动管理文件流可能出现的漏关问题,进一步加剧了文件损坏的概率。

内容的提问来源于stack exchange,提问作者Connor Club

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 07:34:48