En tant que sérialiseur, il existe FlatBuffers développé par Google. La désérialisation étant explosive, il semble qu'elle puisse être utilisée dans les jeux, etc., mais il semble qu'elle ne soit pas populaire. Je pensais que l'une des raisons était qu'il était difficile de créer les données, j'ai donc créé un outil qui sérialise le contenu de la base de données SQL avec FlatBuffers tel quel. Cela vous permet de définir la structure de vos données dans une table SQL.
Dans le cas des jeux sociaux, la définition des données de base est généralement effectuée par l'ingénieur serveur, et le client semble recevoir le contenu sous forme de fichier DB de JSON ou SQLite, mais le côté client attend que le support de conversion soit terminé. Toutefois, si vous utilisez cet outil, vous pouvez fournir des données côté client lors de la création de la table de données SQL et vous pouvez répondre immédiatement aux modifications de la table.
L'outil suppose MySQL. Créez la table requise. Si vous voulez un échantillon, utilisez le MySQL officiel sakila-db.
Il existe un outil de construction appelé flatc qui le rend disponible.
Pour mac, brew install flat buffers
est OK. Super facile.
Juste au cas où, tapez flatc sur la console pour vous assurer qu'il est dans votre chemin.
Installez-le car il utilise Python populaire pour la conversion.
Ensuite, mettez les fichiers suivants.
makefbs.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import os
import os.path
import re
import shutil
import time
import sqlite3
import pymysql.cursors
_isSqliteDb = False
def GetConvName(_str):
_convName = ''
_names = re.split('[ _]', _str)
for _name in _names:
_convName = _convName + _name.capitalize()
return _convName
class SchemaInfo:
def __init__(self, _keyName, _keyType, _fbsKeyType):
self.keyName = _keyName
self.keyType = _keyType
self.fbskeyName = GetConvName(_keyName)
self.fbsKeyType = _fbsKeyType
#Obtenir le nom et le type de la colonne
def GetShemaInfo(_tableName, _conn):
_keys = []
_cur = _conn.cursor()
if _isSqliteDb == True:
# sqlite
_order = "select sql from sqlite_master where type = 'table' and name = '" + _tableName + "'"
_cur.execute(_order)
_schema = _cur.fetchone()[0]
_startPos = _schema.find(_tableName) + len(_tableName) + 1
_schema = _schema[_startPos:-5]
for _item in _schema.split(','):
_itemTmp = _item.split(' ')
if _itemTmp[1] == 'integer':
_fbsKeyType = "int"
elif _itemTmp[1] == 'real':
_fbsKeyType = "float"
elif _itemTmp[1] == 'numeric':
_fbsKeyType = "int"
else:
_fbsKeyType = "string"
_keys.append(SchemaInfo(_itemTmp[0], _itemTmp[1], _fbsKeyType))
else:
# mysql
_order = "SHOW COLUMNS FROM " + _tableName
_cur.execute(_order)
_schema = _cur.fetchall()
for _item in _schema:
_type = _item['Type']
_isUnsigned = _type.find('unsigned') >= 0
if _type.find('tinyint') >= 0:
_fbsKeyType = "ubyte" if _isUnsigned else 'byte'
elif _type.find('smallint') >= 0:
_fbsKeyType = "ushort" if _isUnsigned else 'short'
elif _type.find('bigint') >= 0:
_fbsKeyType = "ulong" if _isUnsigned else 'long'
elif _type.find('mediumint') >= 0 or _type.find('int') >= 0:
_fbsKeyType = "uint" if _isUnsigned else 'int'
elif _type.find('float') >= 0:
_fbsKeyType = "float"
elif _type.find('double') >= 0:
_fbsKeyType = "double"
else:
_fbsKeyType = "string"
_keys.append(SchemaInfo(_item['Field'], _item['Type'], _fbsKeyType))
return _keys
def GetShemaInfoFromFbsKeyName(_keys, _name):
for _item in _keys:
if _item.fbskeyName == _name:
return _item
return None
def Create_Fbs(_conn, _list, _dbMasterName):
_fd = open('param.fbs', 'w')
_fd.write("// IDL file for our master's schema.\n")
_fd.write("\n")
_fd.write("namespace param;\n")
_fd.write("\n")
for _name in _list:
Create_Master(_name, _fd, _conn)
_fd.write("table " + _dbMasterName + "\n")
_fd.write("{\n")
for _name in _list:
_fd.write(' ' + _name[1] + 'Master:' + _name[1] + "Master;\n")
_fd.write("}\n")
_fd.write("\n")
_fd.write("root_type " + _dbMasterName + ";\n")
_fd.close()
def Create_Master(_name, _fd, _conn):
print("create " + _name[0])
_keys = GetShemaInfo(_name[0], _conn)
_fd.write("table " + _name[1] + "Element\n")
_fd.write("{\n")
for _key in _keys:
_fd.write(" " + _key.fbskeyName + ":" + _key.fbsKeyType + ";\n")
_fd.write("}\n\n")
_fd.write("table " + _name[1] + "Master\n")
_fd.write("{\n")
_fd.write(" data:[" + _name[1] + "Element];\n")
_fd.write("}\n\n\n")
def Create_Conv(_conn, _list):
for _name in _list:
_elementName = _name[1] + 'Element'
_masterName = _name[1] + 'Master'
_keyInfos = GetShemaInfo(_name[0], _conn)
_startString = _elementName + "." + _elementName + "Start"
_endString = _elementName + "." + _elementName + "End"
_fd1 = open('conv' + _masterName + '.py', 'w')
_fd1.write("import utility\n")
if _isSqliteDb:
_fd1.write("import sqlite3\n")
else :
_fd1.write("import pymysql.cursors\n")
_fd1.write("from param import " + _elementName + "\n")
_fd1.write("from param import " + _masterName + "\n")
_fd1.write("\n")
_fd1.write("class conv" + _masterName + "(object):\n")
_fd1.write("\n")
_fd1.write(" @classmethod\n")
_fd1.write(" def Create(self, _conn, _builder):\n")
_fd1.write(" _cmd = '" + _name[0] + "'\n")
_fd1.write(" print(\"convert \" + _cmd)\n")
_fd1.write("\n")
_fd1.write(" _cur = _conn.cursor()\n")
_fd1.write(" _cur.execute(\"SELECT * FROM \" + _cmd)\n")
_fd1.write(" _list = _cur.fetchall()\n")
_fd1.write("\n")
_fd1.write(" _elements = []\n")
_fd1.write(" for _row in _list:\n")
_fd2 = open('param/' + _elementName + '.py', 'r')
_strline = _fd2.readline()
while _strline and _strline.find(_elementName + "Start(") < 0:
_strline = _fd2.readline()
_strline = _fd2.readline()
_params = []
while _strline and _strline.find(_elementName + "End(") < 0:
_endPos = _strline.find("):")
_strline = _elementName + '.' +_strline[4:(_endPos + 1)]
_strline = _strline.replace('(builder', '(_builder')
_params.append(_strline)
_strline = _fd2.readline()
for _param in _params:
_fbsArgName = _param[(_param.rfind(', ') + 2):-1]
_keyInfo = GetShemaInfoFromFbsKeyName(_keyInfos, _fbsArgName)
_argName = "_" + _fbsArgName
_writeText = " " + _argName + " = "
if _keyInfo.fbsKeyType == 'int' or _keyInfo.fbsKeyType == 'uint' or \
_keyInfo.fbsKeyType == 'short' or _keyInfo.fbsKeyType == 'ushort' or \
_keyInfo.fbsKeyType == 'long' or _keyInfo.fbsKeyType == 'ulong' or \
_keyInfo.fbsKeyType == 'byte' or _keyInfo.fbsKeyType == 'ubyte':
_writeText = _writeText + "utility.GetInt(_row['" + _keyInfo.keyName + "'])\n"
elif _keyInfo.fbsKeyType == 'float':
_writeText = _writeText + "utility.GetFloat(_row['" + _keyInfo.keyName + "'])\n"
elif _keyInfo.fbsKeyType == 'double':
_writeText = _writeText + "utility.GetDouble(_row['" + _keyInfo.keyName + "'])\n"
else:
_writeText = _writeText + "_builder.CreateString(utility.GetStr(_row['" + _keyInfo.keyName + "']))\n"
_fd1.write(_writeText)
_fd1.write(" " + _startString + "(_builder)\n")
for _param in _params:
_startPos = _param.rfind(', ') + 2
_param = _param[:_startPos] + '_' + _param[_startPos:]
_fd1.write(" " + _param + "\n")
_fd1.write(" _elements.append(" + _endString + "(_builder))\n")
_fd1.write("\n")
_fd1.write(" " + _masterName + "." + _masterName + "StartDataVector(_builder, len(_elements))\n")
_fd1.write(" for i in range(len(_elements)):\n")
_fd1.write(" _builder.PrependUOffsetTRelative(_elements[i])\n")
_fd1.write(" _data = _builder.EndVector(len(_elements))\n")
_fd1.write("\n")
_fd1.write(" " + _masterName + "." + _masterName + "Start(_builder)\n")
_fd1.write(" " + _masterName + "." + _masterName + "AddData(_builder, _data)\n")
_fd1.write(" return " + _masterName + "." + _masterName + "End(_builder)\n")
_fd1.close()
_fd2.close()
def Create_ConvBaseFile(_list, _dbParam, _outName, _dbMasterName):
_fd1 = open('convAll.py', 'w')
_fd1.write("#!/usr/bin/env python\n")
_fd1.write("# -*- coding: utf-8 -*-\n")
_fd1.write("import sys\n")
_fd1.write("import os\n")
_fd1.write("sys.path.append('../flatbuffers')\n")
_fd1.write("import flatbuffers\n")
if _isSqliteDb:
_fd1.write("import sqlite3\n")
else :
_fd1.write("import pymysql.cursors\n")
for _name in _list:
_masterName = _name[1] + "Master"
_fd1.write("import conv" + _masterName + "\n")
_fd1.write("from param import DbMaster\n")
_fd1.write("\n")
_fd1.write("def Convert(_conn, _outName):\n")
_fd1.write(" _builder = flatbuffers.Builder(0)\n")
_fd1.write("\n")
for _name in _list:
_masterName = _name[1] + "Master"
_fd1.write(" _" + _masterName + " = conv" + _masterName + ".conv" + _masterName + "().Create(_conn, _builder)\n")
_fd1.write("\n")
_DbMasterStr = _dbMasterName + "." + _dbMasterName
_fd1.write(" " + _DbMasterStr + "Start(_builder)\n")
for _name in _list:
_masterName = _name[1] + "Master"
_fd1.write(" " + _DbMasterStr + "Add" + _masterName + "(_builder, _" + _masterName + ")\n")
_fd1.write(" _totalData = " + _DbMasterStr + "End(_builder)\n")
_fd1.write(" _builder.Finish(_totalData)\n")
_fd1.write("\n")
_fd1.write(" _final_flatbuffer = _builder.Output()\n")
_fd1.write(" _fd = open(_outName, 'wb')\n")
_fd1.write(" _fd.write(_final_flatbuffer)\n")
_fd1.write(" _fd.close()\n")
_fd1.write("\n")
_fd1.write("if __name__ == \"__main__\":\n")
if _isSqliteDb:
_fd1.write(" _conn = sqlite3.connect('app.db')\n")
_fd1.write(" _conn.row_factory = sqlite3.Row\n")
else:
_fd1.write(" _conn = pymysql.connect(host='" + _dbParam[1] + "', user='" + _dbParam[2] + "', password='" + _dbParam[3] + "', db='" + _dbParam[4] + "', charset='utf8', cursorclass=pymysql.cursors.DictCursor)\n")
_fd1.write(" Convert(_conn, '" + _outName + "')\n")
_fd1.write(" _conn.close()\n")
_fd1.close()
def Create_UtilityFile():
_fd1 = open('utility.py', 'w')
_fd1.write(\
"def GetInt(_str):\n"\
" return 0 if _str is None or _str == 'None' else int(_str)\n"\
"\n"\
"def GetFloat(_str):\n"\
" return 0 if _str is None or _str == 'None' else float(_str)\n"\
"\n"\
"def GetDouble(_str):\n"\
" return 0 if _str is None or _str == 'None' else double(_str)\n"\
"\n"\
"def GetStr(_str):\n"\
" if isinstance(_str, unicode):\n"\
" return _str.encode('utf-8')\n"\
" if isinstance(_str, str):\n"\
" return _str\n"\
" return \"\" if _str is None else str(_str)\n"\
)
_fd1.close()
if __name__ == "__main__":
_dbParam = []
_isSqliteDb = False
_settingName = 'Setting.txt'
_inName = 'TableName.txt'
_outName = 'db_master.dat'
_dbMasterName = 'DbMaster'
_flatparam = '-c'
#for argv in sys.argv:
if os.path.isfile(_settingName):
_fd1 = open(_settingName, 'r')
_dbParam = _fd1.read().splitlines()
_fd1.close()
_isSqliteDb = _dbParam[0] == "sqlite"
_list = []
_fd1 = open(_inName, 'r')
_line = _fd1.readline().rstrip("\n")
while _line:
_list.append(_line.split(','))
_line = _fd1.readline().rstrip("\n")
if _isSqliteDb:
_conn = sqlite3.connect(_dbParam[1])
_conn.row_factory = sqlite3.Row
else:
_conn = pymysql.connect(host=_dbParam[1], user=_dbParam[2], password=_dbParam[3], db=_dbParam[4], charset='utf8', cursorclass=pymysql.cursors.DictCursor)
# Create working directory
shutil.rmtree("conv", True)
time.sleep(0.1)
os.makedirs("conv")
os.chdir('conv')
_fd = open('__init__.py', 'w')
_fd.close()
Create_UtilityFile()
Create_Fbs(_conn, _list, _dbMasterName)
os.system('flatc ' + _flatparam + ' -p param.fbs')
Create_Conv(_conn, _list)
Create_ConvBaseFile(_list, _dbParam, _outName, _dbMasterName)
_conn.close()
os.chdir('..')
os.system('python conv/convAll.py')
Créez un fichier qui décrit les informations de connexion à SQL et la définition de la table à laquelle se référer. Mettez-le dans le même dossier que makefbs.py.
Setting.txt
mysql (Sélection SQL)
localhost (Adresse de destination de la connexion)
root (Nom d'utilisateur)
Pass-123 (mot de passe)
sakila (Nom de la base de données)
TableName.txt
actor_info,SampleActorInfo (Nom de la table de base de données,Nom de la classe de sortie)
customer_list,SampleCustomerList
film_list,SampleFilmList
Créez un dossier appelé flatbuffers et copiez le fichier [Source pour python] officiellement téléchargé (https://github.com/google/flatbuffers/tree/master/python/flatbuffers).
Exécutez python. / Makefbs.py
dans la console.
Il se connecte à MySQL, analyse la structure de table spécifiée et génère automatiquement un schéma FlatBuffers.
Exécutez ensuite flatc pour créer les données binaires finales.
Un fichier de données appelé db_master.dat et un dossier de travail appelé conv sont créés.
Cet outil est conçu pour générer un fichier d'en-tête pour le langage C.
D'autres langages peuvent être pris en charge en réécrivant la variable _flatparam
dans makefbs.py.
Il y a un fichier appelé param_generated.h
dans le dossier conv, qui sera utilisé pour la désérialisation.
python
#include <iostream>
#include <fstream>
#include <sys/stat.h>
#include "param_generated.h"
int main(int argc, const char * argv[])
{
struct stat results;
if (stat(argv[1], &results) != 0)
{
std::cout << "file not found\n";
return -1;
}
char* buff = new char[results.st_size];
std::ifstream file;
file.open(argv[1], std::ios::in | std::ios::binary);
if (file)
{
file.read(buff, results.st_size);
auto data = param::GetDbMaster(buff);
auto actorMaster = data->SampleActorInfoMaster()->data();
auto num = actorMaster->size();
for (int i = 0; i < num; i++)
{
auto data = actorMaster->Get(i);
std::cout << data->ActorId() << " " << data->FirstName()->c_str() << " " << data->LastName()->c_str() << "\n";
}
}
delete[] buff;
return 0;
}
Dans l'exemple, une table appelée actor_info est regroupée dans une classe std :: vector appelée SampleActorInfoMaster. Les données de chaque ligne sont définies comme la classe SampleActorInfoElement et peuvent être obtenues avec Get (). Et il semble que param :: GetDbMaster a plusieurs informations de table.
L'acquisition elle-même est organisée par std :: vector, donc je pense que c'est facile.
Lorsque la structure de la table change, la mise à jour du fichier d'en-tête prend du temps, mais je pense qu'il serait pratique de convertir la structure de la table SQL en données telle quelle, ce qui économise beaucoup de temps et d'efforts.
Comme il est fait sur la prémisse de la coutume, toutes les colonnes de la table sont sérialisées à l'heure actuelle, mais avez-vous jouer avec makefbs.py
en fonction de chaque projet, comme décider de la règle de dénomination afin que vous puissiez créer des colonnes qui ne sont pas sérialisées. Je pense que c'est bien.
Recommended Posts