前言: 本文是对 Godot(4.X): 外接Python处理Excel数据: 账号管理系统实现-CSDN博客 中Python部分代码的完整实现,本文将继续解释Python的子脚本部分,其中主脚本部分在主文章中已虽已完整给出,这里 仍然会加入文本开头 方便阅读

这里给出Python文件结构(  主管理器  Json管理器  Excel管理器  ),主要使用 Openpyxl

主管理器只负责对接Godot,负责返回对应处理结果,这里重新说明:

规定: "0" : 验证通过"1" : 校验信息问题(比如账号不存在,注册密码长度不够)"2" : 账号密码匹配问题(密码错误,已被注册)

主管理器代码

#=================openpyxl库================#
#       本文用来处理Excel的python的库       #
#===========================================#
from openpyxl.worksheet.worksheet import Worksheet

# 以下为子Python脚本引入的处理函数(这里简要解释)
from ExcelProcessing import read_from_excel             # 返回指定的Excel工作表
from ExcelProcessing import write_new_account           # 向Excel写入新账号信息,返回 tuple[bool, int] 元组
from JsonProcessing import convert_excel_to_account     # 将Excel中读取的数据转化为固定字典格式 {"ID": [password, name]}
from JsonProcessing import detect_login_information     # 校验登录信息,返回 tuple[bool, int] 元组
# Optional 为类型注解标记,可以用于规定函数传参传限定类型内的参数如 Optional[int | str] 指参数可以传入 int,str,None类型的  
from typing import Optional
# 这个是Python用来接收系统传参的类,其中 sys.argv 为收到的参数
import sys
import os
#=============默认存档名============#
default_save_exc_name       = "GameDataExcel.xlsx"      # 手动调用脚本时的Excel路径
default_exc_data_player     = "PlayerData"              # 默认取打开Excel中的工作表的名字为 PlayerData
default_json_out            = "PlayerGameData.json"     # 存档转化出的Json位置
#=============默认存档名============#

#=============指定参数==============#         # 这里所有的位置都是Godot中传来参数中对应参数的索引
default_order_index:            int = 2       # 这里是默认从godot获得的参数中取第2位为指令参数
default_excel_path_index:       int = 1       # 默认Excel位置
default_order_name_index:       int = 3       # 默认名字位置
default_order_pwd_index:        int = 4       # 默认密码位置
default_godot_argv_len:         int = 5       # 默认传参长度
#=============指定参数==============#

#=============指定命令==============#
godot_login:        str = "0"                 # 指定Godot登录命令为 "0"
godot_register:     str = "1"                 # 指定Godot注册命令为 "1"
#=============指定命令==============#

"""
以下所有函数面向Godot返回
此时Python中print的内容就是Godot接收到的返回值+-
全部使用print
"""

"""
返回值说明:
- 元组第1位:是否校验通过(True=通过,False=不通过)
- 元组第2位:状态码
    - 0 : 账号验证通过
    - 1 : 账户信息不存在
    - 2 : 账号信息验证错误(信息不全/密码错误)
"""
# 登录验证器
def login_verify(input_login_info: Optional[list[str]]) -> None:
    if input_login_info is None:
        return None
    
    try:
        # 得到账户表格
        read_sheet = read_from_excel(default_save_exc_name, default_exc_data_player)
        stored_info_dict = convert_excel_to_account(read_sheet)
        # 验证登录信息
        verify_result: tuple =  detect_login_information(stored_info_dict, input_login_info)
        # 通过验证
        print(str(verify_result[1]))
    except Exception as e:
        # 登录程序运行失败
        print("-1")

"""
账户注册处理
返回-1: 程序运行出错
返回0 : 注册成功
返回1 : 账户注册出现问题(非法字符/账户密码问题)
返回2 : 账户已被注册
"""
# 注册验证器
def register_verify(input_register_info: Optional[list[str]]) -> None:
    if input_register_info is None:
        return None
    
    try:
        # 验证注册信息并写入
        verify_result: tuple = write_new_account(default_exc_data_player, 
                                                 default_save_exc_name, 
                                                 input_register_info)
        print(str(verify_result[1]))

    except Exception as e:
        # 注册程序运行失败
        print("-1")
    
"""
返回 -1 为程序运行出错
返回 -2 为程序输入变量不足
"""
# 主函数进入
if __name__ == "__main__":
    if len(sys.argv) >= default_godot_argv_len:
        # 将Godot传参转至本地
        receive_params: list[str] = sys.argv
        godot_order = receive_params[default_order_index]
        # 格式化传参内容
        default_save_exc_name   = receive_params[default_excel_path_index]      # 获得收到的Excel地址
        input_name              = receive_params[default_order_name_index]      # 获得收到的账号ID
        input_pwd               = receive_params[default_order_pwd_index]       # 获得收到的密码
        input_info: list[str] = [input_name, input_pwd]

        # 登录注册验证请求
        if (godot_order == godot_login):
            login_verify(input_info)
        if (godot_order == godot_register):
            register_verify(input_info)

    # 参数不够传 -2
    else:
        print("-2")

1. 对子管理器的简述

        在主文章的主管理器代码中,使用了子管理器的函数

# 以下为子Python脚本引入的处理函数(这里简要解释)
from ExcelProcessing import read_from_excel             # 返回指定的Excel工作表
from ExcelProcessing import write_new_account           # 向Excel写入新账号信息,返回 tuple[bool, int] 元组
from JsonProcessing import convert_excel_to_account     # 将Excel中读取的数据转化为固定字典格式 {"ID": [password, name]}
from JsonProcessing import detect_login_information     # 校验登录信息,返回 tuple[bool, int] 元组

虽然并非所有子管理器的代码均被使用,但是文章仍然会解释说明

子管理器要分析获得的Godot参数,排除错误的账号信息并返回给主管理器,由主管理器返回给Godot

子管理器处理Excel账号数据流程:

1. 首先要规定Excel存入的账号的格式

2. 然后Python要打开Excel,将Excel格式转化为特定格式,这里需要的的格式为

{ "ID": [password, name ]  }

3. Python寻找到对应ID,检查密码,返回规定的检查结果

2. 子管理器实现

这里先给出Excel的格式

        1. Excel管理器 (ExcelProcessing)

主要用于处理Excel数据,以下为代码部分

#====引入的 Openpyxl 库中的工作簿,工作表,以及加载函数====#
from openpyxl.worksheet.worksheet import Worksheet        # 工作表类
from openpyxl.workbook.workbook import Workbook           # 工作簿类
from openpyxl import load_workbook                        # 加载Excel函数
from typing import Optional                               # 多类型注释,用于函数返回多类型

#====允许调试信息(防止 print() 输出到Godot捕获参数列表)====#
DEBUG = False
# 调试信息输出
def log(msg):
    """只有 DEBUG=True 时才打印,否则不输出任何东西"""
    if DEBUG:
        print(msg)
#====允许调试信息(防止 print() 输出到Godot捕获参数列表)====#

"""
Excel全局Excel文件管理器
可以替换所有函数内打开Excel的操作
减少频繁打开关闭Excel的性能消耗
这里可以忽略,本文并未使用
"""
#===============全局文件管理器Excel===============#
"""
统一管理全局被打开的Excel
防止手动释放出现问题
"""
class Excel_Manager:

    #==默认空表未打开Excel==#
    def __init__(self):
        self.inner_excel: Workbook      = None

    #==尝试打开Excel==#
    def open_excel(self, file_path: str) -> bool:     
        #==检查本对象是否已经有Excel指向==#
        if self.inner_excel:
            log(f"打开对象已存在: {self.inner_excel}: 请手动释放")
            return False

        #==尝试读取Excel==#
        try:
            #==获取Excel==#
            read_excel: Workbook = load_workbook(file_path, read_only=True)
            self.inner_excel = read_excel
            return True
    
        #==异常处理==#
        except Exception as e:
            log(f"打开Excel发生异常: {e}")
            return False

    #==获得类中Excel==#
    def get_excel(self) -> Optional[Workbook]:
        #==检查本对象Excel是否已经指向==#
        if self.inner_excel:
            log(f"成功返回Excel: {self.inner_excel}")
            return self.inner_excel
        
        #=不存在=#
        log("返回excel失败,excel并未读取")
        return None

    #==获得Excel中对应表单==#   
    def get_sheet(self, sheet_name: str) -> Optional[Worksheet]:
        #==检查Excel指向==#
        if not self.inner_excel:
            log("不存在原表")
            return None

        #==返回特定表==#     
        try:
            if sheet_name not in self.inner_excel.sheetnames:
                log(f"工作表'{sheet_name}' 不存在")
                return None
            #==返回表==#
            return self.inner_excel[sheet_name]
        #==返回异常==#
        except Exception as e:
            log(f"表单返回发生异常: {e}")
            return None

    #==关闭Excel表格以及停止指向==#
    def close_workbook(self) -> bool:
        #==检查Excel指向==#
        if not self.inner_excel:
            log("不存在原表,已经关闭")
            return True
        try:
            self.inner_excel.close()
            return True
        
        #==表单关闭失败==#
        except Exception as e:
            log(f"关闭excel出现异常: {e}")
            return False   
        
"""
以下为当前文章使用的原版表格函数
因为没有Excel统一管理类
所以需要注意表格的状态
开启或者关闭
"""
#============从excel中以固定格式读取工作表============#
#===========返回值为 Worksheet 或 None ===============#
#==参数(Excel路径,工作表名字,是否只读(默认不只读))===#
def read_from_excel(file_read_path: str,
                    read_data_name: str,
                    read_only_out: bool = False) -> Optional[Worksheet]:
    
    read_excel: Workbook
    #==返回指定工作表==#
    try:
        #==获取Excel==#
        read_excel = load_workbook(file_read_path, read_only=read_only_out)
        #==Excel中不存在指定工作表==#
        if read_data_name not in read_excel.sheetnames:
            log(f"工作表'{read_data_name}' 不存在")
            read_excel.close()
            return None
        
        #==读取返回指定工作表==#
        read_excel_sheet = read_excel[read_data_name]
        read_excel.close()
        return read_excel_sheet
    
    # 读取失败
    except Exception as e:
        log(f"read failed{e}")
        # 打开了就关闭Excel
        if read_excel:
            read_excel.close()
        return None
    
"""
账户注册处理
返回-1: 程序运行出错
返回0 : 注册成功
返回1 : 账户注册出现问题(非法字符/账户密码问题)
返回2 : 账户已被注册
"""    
#=============写入新玩家数据==============#
#===返回值为元组 tuple[bool, int] ========#
#====第一个为是否成功,第二个为结果参数===#

#参数(Excel工作表名,Excel路径,新的账户信息)#
def write_new_account(read_data_name: str,
                      file_read_path: str,
                      new_account_information: Optional[list[str]]) -> tuple[bool, int]:

    #===传入空列表处理===#
    if new_account_information is None:
        return False, -1

    #=======这里调用了下面的验证账户输入合法性质(检验非法字符)检查函数=========#
    #==================返回同样为元组 tuple[bool, str] =======================#
    #==================== 第一个为是否合法,第二个为调试信息===================#
    get_account_verify: tuple[bool, str] = verify_account_data(new_account_information)

    #===非法返回 1===#
    if not get_account_verify[0]:
        log("账号登录或注册出现问题: " + get_account_verify[1])
        return False, 1

    read_excel: Workbook
    #=====合法尝试修改表格=====#
    try:
        #=====这里调用了下面验证Excel中玩家是否存在的函数=====#
        #==================返回值为bool类型==================#
        #=== 参数 (Excel工作表名字,Excel路径,查找账户ID) ===#
        if detect_id_excel_exist(read_data_name, file_read_path, new_account_information[0]):
            log(f"玩家ID已经存在: {new_account_information[0]}")
            return False, 2
    
        #===修改Excel表格,则Excel读取不能启用 read_only=True ===#
        read_excel = load_workbook(file_read_path, read_only=False)

        # 检查工作表存在
        if read_data_name not in read_excel.sheetnames:
            log(f"工作表'{read_data_name}' 不存在")
            read_excel.close()
            return False, -1
        
        # 选中工作表,获得新数据行号
        read_excel_sheet = read_excel[read_data_name]
        new_row_index = read_excel_sheet.max_row + 1        # 获得最大行的下一行,就是插入行
        col_num = read_excel_sheet.max_column               # 获得Excel当前工作表最大列数

        #====输入数据不全全部补"None"=====#
        need_add_num = col_num - len(new_account_information)       # 要补充的 "None" 的数量
        if need_add_num > 0:
            new_account_information += [None] * need_add_num

        #=======遍历新行所有单元格写入=======#
        # col_index 从 start 开始,每增加一列增加 1,用来计数
        # .cell()的第三个参数用来设置Excel单个格子的值
        for col_index, input_value in enumerate(new_account_information, start=1):
            read_excel_sheet.cell(row=new_row_index, 
                                  column=col_index, 
                                  value=input_value)

        #====保存修改数据====#
        read_excel.save(file_read_path)
        read_excel.close()
        log(f"数据写入成功: {new_account_information} -> {file_read_path}")
        return True, 0


    # 写入失败处理
    except Exception as e:
        log(f"write failed: {e}")
        if read_excel:
            read_excel.close()
        return False, -1

#=====验证Excel中玩家是否存在=====#
#=========返回值为bool类型========#
#=== 参数 (Excel工作表名字,Excel路径,查找账户ID) ===#
def detect_id_excel_exist(read_data_name: str, file_read_path: str, detected_id: str) -> bool:

    read_excel: Workbook
    #====尝试寻找ID====#
    try:
        # read_only=False,这样才可以使用列迭代器,否则报错
        read_excel = load_workbook(file_read_path, read_only=False)
        # 检查工作表是否存在于当前 Excel 表格
        if read_data_name not in read_excel.sheetnames:
            log(f"工作表'{read_data_name}' 不存在")
            read_excel.close()
            return False
        
        #====选中工作表,获取最大行数====#
        read_excel_sheet = read_excel[read_data_name]
        read_sheet_first = read_excel_sheet["A"]
        row_max_len = read_excel_sheet.max_row

        #====遍历确认玩家ID不存在====#
        exist_dir: dict[str, int] = {}
        for current_row, cell in enumerate(read_sheet_first, start=1):
            # 略过第一个属性名
            if current_row == 1:
                continue
            # 将所有玩家ID放入字典,用于后续检测
            if cell.value:
                exist_dir[str(cell.value)] = 1

        # 对比检测是否存在
        if detected_id in exist_dir:
            read_excel.close()
            return True
        read_excel.close()
        return False

    #=====检测出现异常,用于调试======#
    except Exception as e:
        log(f"查找Excel出现问题: {e}")
        if read_excel:
            read_excel.close()
        return False
    
#=======验证账户输入合法性质(检验非法字符)检查函数=========#
#===========返回为元组 tuple[bool, str] ==================#
#========== 第一个为是否合法,第二个为调试信息=============#
def verify_account_data(acc_info: list[str]) -> tuple[bool, str]:
    """
    账号合法性质校验、
    校验至少[账号ID, 密码, ......]
    """
    if not acc_info or len(acc_info) < 2:
        return False, "字段不足: 至少需要[账号ID, 密码]"
    
    acc_id          = acc_info[0]
    acc_password    = acc_info[1]

    #=====账号基本信息校验=====#
    # 返回 bool 类型
    if not acc_id:
        return False, "账号ID不能为空或全空格"
    if len(acc_id) < 3:
        return False, "账号长度不能小于3"
    # 这里右边 per_char 会遍历组成一个表格,只有非法字符会成为元素
    # any() 只要其中检测的元素不为空则返回true,也就是有非法字符返回true
    if any(per_char in r'\/:*?"<>| ' for per_char in acc_id):
        return False, "账号ID包含非法特殊字符"
    
    #====密码校验====#
    if not acc_password:
        return False, "密码不能为空"
    if len(acc_password) < 4:
        return False, "密码长度不能小于4"
    
    return True, "校验通过"

        2. Json管理器 (JsonProcessing)

Json子管理器主要用于转化Excel玩家存档数据等为Json文件提供Godot读取,这里也提供输入总账户字典,根据Godot输入信息,验证登录信息

以下为代码部分

from openpyxl.worksheet.worksheet import Worksheet
from typing import Optional
import json             # 引入 Json 库

#====允许调试信息(防止 print() 输出到Godot捕获参数列表)====#
DEBUG = False
# 调试信息输出
def log(msg):
    """只有 DEBUG=True 时才打印,否则不输出任何东西"""
    if DEBUG:
        print(msg)
#====允许调试信息(防止 print() 输出到Godot捕获参数列表)====#

# 转换工作表为字典(默认字典格式,所有标题散装)
# 这里是转化玩家存档数据的不是账户数据的
# 输入为 Excel工作表
# 输出为字典类型 { "属性": 值 }
def convert_excel_to_dir(out_excel_worksheet: Optional[Worksheet]) -> dict:
    #===Excel工作表传值判断===#
    if out_excel_worksheet is None:
        log("默认字典转换: 传入的是空表")
        return {}
    
    try:
        """
        这里的列表转化循环为
        str(cell.value) if cell.value is not None 满足添加单元格的值
        else "None" 不满足补齐 "None"
        cell 来源于工作表的第一列,可以用常量换,表示名字列或者数据列
        """
        # 转换属性列表
        excel_title_name: list[str | None]  = [str(cell.value) if cell.value is not None 
                                               else "None"
                                               for cell in out_excel_worksheet[1]]
        
        # 转化属性值列表
        excel_value: list[str | int | None] = [cell.value if cell.value is not None
                                               else "None"
                                               for cell in out_excel_worksheet[2]]
        
        # 用 zip 将两组数据对应拼成字元组
        excel_zip_result = zip(excel_title_name, excel_value)
        # 元组转换字典
        excel_zip_to_dir = dict(excel_zip_result)
        # 返回玩家存档信息字典
        return excel_zip_to_dir
    # 转换失败
    except Exception as e:
        log(f"Excel转换字典失败: {str(e)}")
        return {}

# ===============================================================================#
# 转换工作表为账号格式 { "名字": ["密码", "昵称"] }
# 适配 Godot本地登录,查字典完成账号校验
# 传参 (Excel工作表),返回上述字典
# ===============================================================================#
def convert_excel_to_account(out_excel_worksheet: Optional[Worksheet]) -> dict:
    #===Excel工作表传值判断===#
    if out_excel_worksheet is None:
        log("账号字典转换: 传入的是空表")
        return {}

    try:
        # 转换账户信息ID,需要的list容器
        excel_id_list:          list[str | None]        = []
        excel_password_list:    list[str | None]        = []
        excel_name_list:        list[str | None]        = []
        excel_account_dir:      dict[str , list]         = {}

        # 添加数据内置的用于转化特定列的数据到存储列表的函数
        # 思路同上转化玩家存档数据
        def append_value(cell: tuple, excel_list: list):
            # 遍历得到的当前列
            for cell_value in cell:
                    append_case: str = ""
                    # 没有数据添加 "None"
                    if cell_value.value is None:
                        append_case = "None"
                    else:
                        append_case = str(cell_value.value)
                    # 添加玩家信息
                    excel_list.append(append_case)

        #==注意传进来的工作表不要 read_only=True 否则无法使用列迭代器 ==#
        # 这里遍历 1 到 3 列 ,同样可以换成常数,分别指 ID PASSWORD NAME
        for col_num, cell in enumerate(out_excel_worksheet.iter_cols(min_col=1, 
                                                                     max_col=3, 
                                                                     min_row=2, 
                                                                     max_row=None), 
                                       start=1):

            # 添加玩家信息到对应列表
            # 用match匹配当前列
            match col_num:
                # 玩家账户ID信息
                case 1:
                    append_value(cell, excel_id_list)
                # 玩家密码信息
                case 2: 
                    append_value(cell, excel_password_list)
                # 玩家名字信息
                case 3:
                    append_value(cell, excel_name_list)
                
        # 获得当前最小的数据列表长度
        min_valid_length = min(len(excel_password_list), len(excel_name_list), len(excel_id_list))

        # 截断列表, 保证每一个数据所存的数据长度一致
        # 方便后续 zip 转化
        excel_id_list           = excel_id_list[:min_valid_length]
        excel_name_list         = excel_name_list[:min_valid_length]
        excel_password_list     = excel_password_list[:min_valid_length]
        
        # 这里的思路是先将后面的所有数据整合成列表
        # 然后再和 ID 匹配转化成字典
        # 中间数组
        result_info_list: list[list[str]] = []
        # 整合结果字典 [[password, name]]
        result_info_list = [
            [password, name]
            for password, name in zip(excel_password_list, 
                                      excel_name_list)
        ]

        # 转换字典
        excel_account_dir = dict(zip(excel_id_list, result_info_list))
        return excel_account_dir
        
    # 账户字典转换失败          
    except Exception as e:
        log(f"账户字典创建失败: {str(e)}")
        return {}
#===============================================================================#

# 字典创建JSON文件
# 这里是将字典的数据转化为Json的
# 传参为 (字典,Json输出路径)
def convert_dir_to_json(out_dir: dict, json_out_path: str) -> None:
    # 尝试创建JSON文件
    try:
        convert_dir: str = json.dumps(
            out_dir, 
            ensure_ascii=False, 
            indent=4,
            sort_keys=False
            )
        # 写入JSON
        with open(json_out_path, "w", encoding="utf-8") as file:
            file.write(convert_dir)
        log(f"JSON文件创建成功: {json_out_path}")
    # 创建失败
    except Exception as e:
        log(f"字典创建JSON失败: {str(e)}")

"""
校验玩家登录信息(账号 + 密码)

返回值说明:
- 元组第1位:是否校验通过(True=通过,False=不通过)
- 元组第2位:状态码
    - 0 : 账号验证通过
    - 1 : 账户信息不存在
    - 2 : 账号信息验证错误(信息不全/密码错误)
"""
#=====使用玩家信息字典校验登录信息=====#
# 传值为 (玩家信息字典,登录信息)
# 返回 tuple[bool, int] 元组
# 第一个bool为校验是否通过,int 为校验得到的结果码
def detect_login_information(input_info_dict: dict, 
                             login_info: Optional[list[str]]
                             ) -> tuple[bool, int]:

    # 空信息校验
    if login_info is None:
        log("登录验证: 传入的是空信息")
        return False, 1

    # 校验输入长度
    if len(login_info) < 2:
        log("登录信息不全")
        return False, 2
    
    # 校验玩家ID存在
    if not (login_info[0] in input_info_dict):
        log("账号不存在!")
        return False, 1

    # 校验密码
    input_password = login_info[1]
    stored_password = input_info_dict[login_info[0]][0]
    if input_password != stored_password:
        log("账号密码错误!")
        return False, 2
        
    # 校验通过
    log("校验通过")
    return True, 0

3. 补充

        本代码演示部分在主文章中已经演示,这里不再做演示,代码开源地址同样在主文章中给出。这里返回主文章

Godot(4.X): 外接Python处理Excel数据: 账号管理系统实现-CSDN博客

更多推荐