[游戏开发]Python版Excel数据配表导出工具系列 [第二篇] 导表流程详述

[上一篇链接]

[游戏开发]Python打表工具系列 [第一篇][IDE开发环境部署] VSCode Python环境调试_Little丶Seven的博客-CSDN博客

[前言]

第二篇文章是对流程的概述,从第三篇开始详述打表过程每一步

[正文]

策划配表习惯使用excel,我们打表目标也是xlsm和xlsx文件

开始打表前需要确认好打表工具目录在哪,可以在所有Excel配表同层级的文件夹内新建个文件夹命名TableCreater

TableCreater里有几个目录要区分清楚,首先是python脚本文件夹叫Scripts,还有在运行工具期间生成的各种文件,用Temp文件夹存储,Temp里有PB_Python、PB_lua、Proto、Bytes等文件夹,打表流程启动后生成的各种文件要放到对应文件夹中

Scripts文件夹内要有start.py、main.py、excelToCSV.py、export_proto.py、export_pb_lua.py、export_pb_python.py、export_bytes.py、export_bytesTables.py

Scripts文件夹内的文件一看就是用来打表的各个流程代码。

虽然项目是从start.py开始启动的,但具体的打表流程我们放到main.py中运行,start.py的职责是监听用户输入信息并收集信息塞给main.py进入打表流程

如下图所示,很显然这是一个cmd文件启动了python工程,补全、提示、等都是在python代码中实现的。start.py的功能有tab键补齐名字,分号多个输入,只是输入all表示全部excel打表,以及clear清空log等操作。

如图所示,我输入了一个115_资源总表,在start.py里监听输入并自动把全路径拼齐,当按下回车时,start.py把【115_资源总表】这个名字传给main.py开始了打表流程,由于我们输入了一个表名而不是"all",因此到了main函数中自然不是全打表

import os
#读行模块
import readline
#自动补全模块
import rlcompleter
#监听用户输入模块
import userInput
import sys
#开发者自定义的系统参数
import env_debug

def getAllExportExcelNames():
    inputStr = ""
    isAll = False
    if env_debug.isRuning:
        if len(sys.argv) > 0: 
            inputStr,isAll = userInput.getAllExportExcelNames(sys.argv[13])
        else:
            print("env_debug error")
    else:
        #readline模块与colorama模块有冲突,无法一起使用
        py = os.path.abspath("TableCreater/Python27/python")
        tips = os.path.abspath("TableCreater/Script/tips.py")
        os.system("{0} {1} 1".format(py,tips))

        while True:
            inputStr,isAll = userInput.getInput()
            if inputStr == "exit":
                os.system("exit")
                break
            elif inputStr == "clear":
                os.system("cls")
                py = os.path.abspath("TableCreater/Python27/python")
                tips = os.path.abspath("TableCreater/Script/tips.py")
                os.system("{0} {1} 1".format(py,tips))
            elif inputStr == "error":
                print("command error")
            else:
                break
    return inputStr,isAll



#excelNames:要导出的所有Excel表格名称
#isAll:是否是全部导出
#isVersion:是否是出版本
def export(excelNames,isAll,isVersion=False):
    import main
    main.run(excelNames,isAll,isVersion)
    if not isVersion:
        os.system("pause")

def isVersion():
    return len(sys.argv) > 0 and sys.argv[len(sys.argv) - 1] == "version"

if __name__ == '__main__':
    env_debug.switch("common")
    caller = sys.argv[1]
    if caller == "1": #常规导表
        if not isVersion():#显示用户输入
            excelNames,isAll = getAllExportExcelNames()
            if excelNames != "exit":
                export(excelNames,isAll)
        else: #直接导出所有
            excelNames,isAll = userInput.getAllExportExcelNames("all")
            export(excelNames,isAll,True)
            
    elif caller == "2": #C#导表
        excelNames,isAll = userInput.getAllExportExcelNames(sys.argv[10])
        export(excelNames,True,isVersion())

    elif caller == "3": #本地战斗服务器专属导表
        excelNames,isAll = userInput.getAllExportExcelNames(sys.argv[8])
        export(excelNames,True,isVersion())

最上面有个 import env_debug模块,这是我们自己写的系统变量定义文件 env_debug.py文件里,这里有必要解释一下为何存在一个系统变量文件。我们的打表工具将来是要暴露给所有开发人员使用的,因此需要有一个快捷入口,而一个功能健全的打表工具很显然不能只有单一的打表功能,还要支持定制化内容,下面是我项目打表工具的启动入口 run.cmd,该文件就是python工程的入口,并向python工程传入各种参数参数,看一下该cmd文件的callType,很明显该工程支持生成多类型。可以生成lua读表、C#读表功能以及对战斗服务做了支持。启动python工程的代码后面带了茫茫多的参数, env_debug.py文件里就是存了这些参数,方便开发者调试用的。

@echo off 

REM ***isVersion代表是否为出版本<做为最后一个参数传入,默认:空,不是出版本,如果该值为version代表出版本>***

call export_setting.bat

set callType=1

REM 重载
set callType=%1

if "%callType%"=="1" goto export_common

if "%callType%"=="2" goto export_csharp

if "%callType%"=="3" goto export_battle


REM 常规导出
:export_common

set isVersion=%2
"./TableCreater/Python27/python" "./TableCreater/Script/start.py" 1 %import_excel_path% %import_enum_path% %export_public_path% %export_client_lua_pb% %export_client_lua_script% %export_client_byte% %export_server_proto% %export_server_byte% %export_client_project_lua_pb% %export_client_project_lua_script% %export_client_project_byte% %isVersion%

goto end

REM 导出C#版
:export_csharp

set isVersion=%4
"./TableCreater/Python27/python" "./TableCreater/Script/start.py" 2 %import_excel_path% %import_enum_path% %export_public_path% %export_client_cs_pb% %export_client_cs_script% %export_client_project_cs_pb% %export_client_project_cs_script% %2 %3 %isVersion%

goto end

REM 导出本地战斗版
:export_battle

set isVersion=%6
"./TableCreater/Python27/python" "./TableCreater/Script/start.py" 3 %import_excel_path% %import_enum_path% %export_public_path% %2 %3 %4 %5 %isVersion%

:end



接下来是env_debug.py的代码,14个参数很明显,就是上面这个run.cmd文件传入的各个参数。env_debug.py存在的意义就是工具开发人员不需要用run.cmd文件传入参数,而是本地已经模拟好了这些参数。直接启动python工程调试即可。

#客户端测试环境
def enter_debug_env_common():
    sys.argv = [0]*14
    sys.argv[0] = "./TableCreater/Script/start.py"
    sys.argv[1] = "1"
    sys.argv[2] = "./"
    sys.argv[3] = "./../EnumData"
    sys.argv[4] = "./../AllData"
    sys.argv[5] = "./../AllData/client/lua_pb"
    sys.argv[6] = "./../AllData/client/lua_script"
    sys.argv[7] = "./../AllData/client/bytes"
    sys.argv[8] = "./../AllData/server/proto"
    sys.argv[9] = "./../AllData/server/bytes"
    sys.argv[10] = "./../../Client-Full/Assets/Lua/Logic/Table/AutoGen/TablePb"
    sys.argv[11] = "./../../Client-Full/Assets/Lua/Logic/Table/AutoGen"
    sys.argv[12] = "./../../Client-Full/Assets/Res/Data/Tables"
    sys.argv[13] = "152_掉落配置表.xlsx"
    to_gbk()
    print(">>>>进入测试环境:[common]<<<<".decode('utf-8').encode('gbk'))

#选择环境
#default_env默认环境 common
def switch(default_env = "common"):
    if (len(sys.argv)>2):#外部调用
        return
    global isRuning
    isRuning = True
    if default_env == "common":
        enter_debug_env_common()

    elif default_env == "csharp":
        enter_debug_env_csharp()

    elif default_env == "battle":
        enter_debug_env_battle()

[main.py代码]

下面介绍一下main.py具体代码与负责内容,先上代码

#! python2
# coding=utf-8

import traceback
import os
import time
import sys
import tools
import setting
import debug
import Parser
import env
import math

import tips
import export_csv
import export_merge
import export_proto
import export_code
import export_pb_lua
import export_pb_python
import export_pb_csharp
import export_pb_csharp_enums
import export_byte
import export_clientcode
import export_clientscript_csharp
import checkAll

from tableData import tableManager
# import chardet


def execute_csv(excelNames):
    for i in range(len(excelNames)):
        debug.log("\n开始分析{}信息...\n".format(debug.toUTF8(excelNames[i])))
        # 生成csv
        export_csv.execute(excelNames[i])


def async_execute_csv(excelNames):
    tools.async_process(excelNames, execute_csv)

    merge_csvs = tableManager.getNeedMergeTable()
    if(len(merge_csvs) > 0):
        # 合并csv
        debug.log("\n开始合并csv=>")
        export_merge.execute(merge_csvs)
        debug.log("\n合并完毕!")


# 常规导表
def export_common(excelNames, isVersion):
    Parser.curToolUser = "1"
    #生成csv
    async_execute_csv(excelNames)

    # 清空临时文件
    tools.clearDir(setting.dirPath_temp)

    csvs_client, filts_server = tableManager.getTableNames(["common", "client"])
    csvs_server, filts_client = tableManager.getTableNames(["common", "server"])

    if len(csvs_server) == 0:
        debug.warning("\n分析结果:输入源为客户端专属表格!")
    if len(csvs_client) == 0:
        debug.warning("\n分析结果:输入源为服务器专属表格!")

    if len(csvs_client) > 0:
        Parser.curToolUser = "2"
        debug.log("\n开始生成客户端配置文件=>")
        debug.log(csvs_client)
        if len(filts_server) > 0:
            debug.log("\n过滤服务器专属表格=>")
            debug.warning(filts_server)

        # 生成proto文件
        debug.log("\n开始生成客户端proto...\n")
        export_proto.execute(csvs_client)

        # 生成proto对应的python解析脚本
        debug.log("开始生成客户端proto对应的python版pb脚本...\n")
        export_pb_python.execute(csvs_client)

        # 生成proto对应的lua解析脚本
        debug.log("开始生成客户端proto对应的lua版pb脚本...\n")
        export_pb_lua.execute(csvs_client)

        # 生成导出bytes代码
        debug.log("开始生成客户端导出bytes代码...\n")
        export_code.execute(csvs_client)

        # 生成bytes
        debug.log("开始生成客户端bytes...")
        export_byte.execute(csvs_client)

        # 生成客户端使用代码
        debug.log("\n开始生成客户端相关代码...\n")
        export_clientcode.execute()

        # 拷贝pb文件与bytes到项目
        debug.log("拷贝pb文件、bytes、相关代码 到AllData...\n")
        tools.copyFiles(os.path.abspath(setting.dirPath_pb_lua), env.export_client_lua_pb)
        tools.copyFiles(os.path.abspath(setting.protocPath_clientScript), env.export_client_lua_script)
        tools.copyFiles(os.path.abspath(setting.dirPath_byte), env.export_client_byte)
        tools.copyFiles(os.path.abspath(setting.dirPath_byte_clientOnly), env.export_client_byte+"/clientOnly/")
        
        if not isVersion: # 出版本不拷贝到项目
            debug.log("拷贝pb文件、bytes、相关代码 到项目...\n")
            tools.copyFiles(os.path.abspath(setting.dirPath_pb_lua), env.export_client_project_lua_pb)
            tools.copyFiles(os.path.abspath(setting.protocPath_clientScript), env.export_client_project_lua_script)
            tools.copyFiles(os.path.abspath(setting.dirPath_byte), env.export_client_project_byte)
            tools.copyFiles(os.path.abspath(setting.dirPath_byte_clientOnly), env.export_client_project_byte+"/ClientOnly/")
            
        debug.log("生成客户端配置完毕!\n")

    if len(csvs_server) > 0:
        # 清空临时文件
        tools.clearDir(setting.dirPath_temp)
        Parser.curToolUser = "3"
        debug.log("开始生成服务器配置文件=>")
        debug.log(csvs_server)
        if len(filts_client) > 0:
            debug.log("\n过滤客户端专属表格=>")
            debug.warning(filts_client)

        #生成proto文件
        debug.log("\n开始生成服务器proto...\n")
        export_proto.execute(csvs_server)

        #生成proto对应的python解析脚本
        debug.log("开始生成服务器proto对应的python版pb脚本...\n")
        export_pb_python.execute(csvs_server)

        #生成导出bytes代码
        debug.log("开始生成服务器导出bytes代码...\n")
        export_code.execute(csvs_server)

        #生成bytes
        debug.log("开始生成服务器bytes...")
        export_byte.execute(csvs_server)
    
        #拷贝pb文件与bytes到AllData
        debug.log("\n拷贝服务器proto文件、bytes、到AllData...\n")
        tools.copyFiles(os.path.abspath(setting.dirPath_proto),env.export_server_proto)
        tools.copyFiles(os.path.abspath(setting.dirPath_byte),env.export_server_byte)

        debug.log("服务器配置生成完毕!")



#客户端C#导表
def export_csharp(excelNames,isVersion):
    Parser.curToolUser = "1"
    async_execute_csv(excelNames)

    # 清空临时文件
    tools.clearDir(setting.dirPath_temp)

    # debug.log("\n开始生成客户端C#版枚举代码...")
    # enumProtoPath = env.import_enum_path + "/proto" 
    # enumProtos = sys.argv[9] #传入的枚举
    # #生成C#版枚举
    # export_pb_csharp_enums.execute(enumProtoPath,enumProtos)

    debug.log("\n开始生成客户端C#版配表相关代码...")
    Parser.curToolUser = "2"

    csvs_client, filts_server = tableManager.getTableNames(["common", "client"])

    if len(filts_server) > 0:
        debug.log("\n过滤服务器专属表格=>")
        debug.warning(filts_server)

    if len(csvs_client) == 0:
        debug.warning("\n分析结果:输入源为服务器专属表格!")
    else:
        debug.log("\n开始生成客户端C#版proto...")
        export_proto.execute(csvs_client)

        debug.log("\n开始生成客户端C#版proto对应的pb脚本...")
        export_pb_csharp.execute(csvs_client)
        
        allTables = tableManager.getAllTableNames()    
        debug.log("\n开始生成成客户端C#版AllTable.cs...")
        export_clientscript_csharp.execute(allTables)


        #拷贝pb文件与bytes到AllData
        debug.log("\n拷贝客户端C#版pb文件、相关代码 到AllData...")
        tools.copyFiles(os.path.abspath(setting.dirPath_pb_csharp),env.export_client_cs_pb)
        tools.copyFiles(os.path.abspath(setting.protocPath_clientScript_csharp),env.export_client_cs_script)

        if not isVersion:
            #拷贝pb文件与bytes到项目
            debug.log("\n拷贝客户端C#版pb文件、相关代码 到项目...")
            tools.copyFiles(os.path.abspath(setting.dirPath_pb_csharp),env.export_client_project_cs_pb)
            tools.copyFiles(os.path.abspath(setting.protocPath_clientScript_csharp),env.export_client_project_cs_script)    

        debug.log("\n生成完毕...")



#战斗导表
def export_battle(excelNames,isVersion):
    Parser.curToolUser = "1"
    async_execute_csv(excelNames)

    #清空临时文件
    tools.clearDir(setting.dirPath_temp)

    csvs_battle = []
    #战斗相关的配置必须是common,因为战斗服务器在客户端跑
    allTables = tableManager.getAllTableData()
    for key, table in allTables.items():
        if table.tableUser != "common":
            debug.throwError("配表{}归属者不是common!".format(table.name))
        csvs_battle.append(table.name)


    if len(csvs_battle) > 0:
        Parser.curToolUser = "2"
        debug.log("\n开始生成战斗相关配置文件=>")
        debug.log(csvs_battle)

        #生成proto文件
        debug.log("\n开始生成战斗相关配置proto...\n")
        export_proto.execute(csvs_battle)

        #生成proto对应的python解析脚本
        debug.log("开始生成战斗相关配置proto对应的python版pb脚本...\n")
        export_pb_python.execute(csvs_battle)

        #生成proto对应的lua解析脚本
        debug.log("开始生成战斗相关配置proto对应的lua版pb脚本...\n")
        export_pb_lua.execute(csvs_battle)

        #生成导出bytes代码
        debug.log("开始生成战斗相关配置的bytes导出代码...\n")
        export_code.execute(csvs_battle)

        #生成bytes
        debug.log("开始生成战斗相关配置bytes...\n")
        export_byte.execute(csvs_battle)


        #拷贝pb文件与bytes到项目
        debug.log("开始拷贝战斗相关配置 proto文件、pb文件、bytes 到 指定路径...\n")
        tools.copyFiles(os.path.abspath(setting.dirPath_proto),env.export_battle_proto)
        tools.copyFiles(os.path.abspath(setting.dirPath_pb_lua),env.export_battle_lua_pb)
        tools.copyFiles(os.path.abspath(setting.dirPath_byte),env.export_battle_byte)
        
        debug.log("生成战斗相关配置完毕!\n")


#检查重复
def export_check(excelNames):
    Parser.curToolUser = "1"
    for i in range(len(excelNames)):
        debug.log("\n开始分析{}信息...\n".format(debug.toUTF8(excelNames[i])))
        # 生成csv
        checkAll.checkExcel(excelNames[i])




# 入口函数
def run(excelNames, isAll, isVersion):
    try:
        # 初始化环境变量
        env.init()
        beginTime = time.time()
            
        caller = sys.argv[1]

        # 准备导出环境
        env.prepare(isAll, isVersion)

        if caller == "1": #常规导表
            export_common(excelNames,isVersion)
        elif caller == "2": #导客户端C#表
            export_csharp(excelNames,isVersion)
        elif caller == "3": #导战斗表
            export_battle(excelNames,isVersion)


        #清空临时文件
        tools.clearDir(setting.dirPath_temp)
        
        #显示耗时
        userTime = int(time.time() - beginTime + 0.5)

        time_min = userTime//60
        if time_min > 0:
            debug.greenlog("\n操作完成,总耗时{}分{}秒\n".format(time_min,userTime%60))
        else:
            debug.greenlog("\n操作完成,总耗时{}秒\n".format(userTime))
        
    except Exception,err:
        debug.touchError(err)



#只做数据检查,并不导出任何文件
def runCheckOnly(excelNames, isAll):
    try:
        beginTime = time.time()
        caller = sys.argv[1]
        # 准备全部ID数据
        env.checkPrepare()
        #开始检查
        export_check(excelNames)

        #显示耗时
        userTime = int(time.time() - beginTime + 0.5)
        time_min = userTime//60
        if time_min > 0:
            debug.greenlog("\n操作完成,总耗时{}分{}秒\n".format(time_min,userTime%60))
        else:
            debug.greenlog("\n操作完成,总耗时{}秒\n".format(userTime))
        
    except Exception,err:
        debug.touchError(err)

main.py代码分为两部分,第一部分是run函数,start.py调用该函数启动打表流程,run函数也很简单,就是区分打表类型。具体的打表流程在export_common函数中。

下面重点介绍一下export_common函数,该函数内调用的各个步骤接口,就是本系列教程的核心代码,在导表的过过程中还加入了客户端表和服务器表的数据过滤,可以看到服务器、客户端判断的相关代码。


# 常规导表
def export_common(excelNames, isVersion):
    Parser.curToolUser = "1"
    #生成csv
    async_execute_csv(excelNames)

    # 清空临时文件
    tools.clearDir(setting.dirPath_temp)

    csvs_client, filts_server = tableManager.getTableNames(["common", "client"])
    csvs_server, filts_client = tableManager.getTableNames(["common", "server"])

    if len(csvs_server) == 0:
        debug.warning("\n分析结果:输入源为客户端专属表格!")
    if len(csvs_client) == 0:
        debug.warning("\n分析结果:输入源为服务器专属表格!")

    if len(csvs_client) > 0:
        Parser.curToolUser = "2"
        debug.log("\n开始生成客户端配置文件=>")
        debug.log(csvs_client)
        if len(filts_server) > 0:
            debug.log("\n过滤服务器专属表格=>")
            debug.warning(filts_server)

        # 生成proto文件
        debug.log("\n开始生成客户端proto...\n")
        export_proto.execute(csvs_client)

        # 生成proto对应的python解析脚本
        debug.log("开始生成客户端proto对应的python版pb脚本...\n")
        export_pb_python.execute(csvs_client)

        # 生成proto对应的lua解析脚本
        debug.log("开始生成客户端proto对应的lua版pb脚本...\n")
        export_pb_lua.execute(csvs_client)

        # 生成导出bytes代码
        debug.log("开始生成客户端导出bytes代码...\n")
        export_code.execute(csvs_client)

        # 生成bytes
        debug.log("开始生成客户端bytes...")
        export_byte.execute(csvs_client)

        # 生成客户端使用代码
        debug.log("\n开始生成客户端相关代码...\n")
        export_clientcode.execute()

        # 拷贝pb文件与bytes到项目
        debug.log("拷贝pb文件、bytes、相关代码 到AllData...\n")
        tools.copyFiles(os.path.abspath(setting.dirPath_pb_lua), env.export_client_lua_pb)
        tools.copyFiles(os.path.abspath(setting.protocPath_clientScript), env.export_client_lua_script)
        tools.copyFiles(os.path.abspath(setting.dirPath_byte), env.export_client_byte)
        tools.copyFiles(os.path.abspath(setting.dirPath_byte_clientOnly), env.export_client_byte+"/clientOnly/")
        
        if not isVersion: # 出版本不拷贝到项目
            debug.log("拷贝pb文件、bytes、相关代码 到项目...\n")
            tools.copyFiles(os.path.abspath(setting.dirPath_pb_lua), env.export_client_project_lua_pb)
            tools.copyFiles(os.path.abspath(setting.protocPath_clientScript), env.export_client_project_lua_script)
            tools.copyFiles(os.path.abspath(setting.dirPath_byte), env.export_client_project_byte)
            tools.copyFiles(os.path.abspath(setting.dirPath_byte_clientOnly), env.export_client_project_byte+"/ClientOnly/")
            
        debug.log("生成客户端配置完毕!\n")

    if len(csvs_server) > 0:
        # 清空临时文件
        tools.clearDir(setting.dirPath_temp)
        Parser.curToolUser = "3"
        debug.log("开始生成服务器配置文件=>")
        debug.log(csvs_server)
        if len(filts_client) > 0:
            debug.log("\n过滤客户端专属表格=>")
            debug.warning(filts_client)

        #生成proto文件
        debug.log("\n开始生成服务器proto...\n")
        export_proto.execute(csvs_server)

        #生成proto对应的python解析脚本
        debug.log("开始生成服务器proto对应的python版pb脚本...\n")
        export_pb_python.execute(csvs_server)

        #生成导出bytes代码
        debug.log("开始生成服务器导出bytes代码...\n")
        export_code.execute(csvs_server)

        #生成bytes
        debug.log("开始生成服务器bytes...")
        export_byte.execute(csvs_server)
    
        #拷贝pb文件与bytes到AllData
        debug.log("\n拷贝服务器proto文件、bytes、到AllData...\n")
        tools.copyFiles(os.path.abspath(setting.dirPath_proto),env.export_server_proto)
        tools.copyFiles(os.path.abspath(setting.dirPath_byte),env.export_server_byte)

        debug.log("服务器配置生成完毕!")

[开始简述打表过程啦!!!]

[第一步]将excel文件转CSV并输出到CSV目录

https://blog.csdn.net/liuyongjie1992/article/details/117324693

[第二步]读取csv的数据类型行和数据名称行,使用这两行数据生成proto

https://blog.csdn.net/liuyongjie1992/article/details/117324749

[第三步]调用protoc-gen-lua.bat生成lua版pb文件

https://blog.csdn.net/liuyongjie1992/article/details/117324829

[第四步]调用protoc.exe生成python版pb文件

https://blog.csdn.net/liuyongjie1992/article/details/117325176

[第五步]csv生成bytes文件导入Unity工程等待读数据

https://blog.csdn.net/liuyongjie1992/article/details/117325209

[第六步]生成Lua读表脚本,业务开发调用该脚本读数据

https://blog.csdn.net/liuyongjie1992/article/details/117325259

[第七步]生成统一的读表接口

https://blog.csdn.net/liuyongjie1992/article/details/117325385

[第八步]业务层使用读表功能

https://blog.csdn.net/liuyongjie1992/article/details/117571169

额外小知识与完结语

https://blog.csdn.net/liuyongjie1992/article/details/117325408

[下一篇链接]

https://blog.csdn.net/liuyongjie1992/article/details/117324693

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Little丶Seven

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值