可能是全网第一个MySQL Workbench插件编写技巧
引言
应公司要求,数据库的敏感数据在写入到数据库中要进行加密,但是在测试环境查询数据的时候要手动解密,很不方便,有的时候数据比较多,解密比较麻烦。遂研究了一下如何通过 MySQL Workbench 的插件来实现查询数据一键解密。
问题
之前已经通过 mybatis
拦截器实现了自动加解密的功能,并且可以兼容 LambdaQueryWrapper
,QueryWrapper
,Mapper
等查询方式,现在要解决的问题是通过插件自动解密数据。
插件原理
要想写 MySQL Workbench 插件,要先了解 MySQL Workbench 的原理。
插件
查看官方文档,MySQL Workbench 的核心使用 C++ 写的,但是留了大量的拓展接口,可以通过 Python 包装的方式来控制。例如可以通过 Python 创建一些界面,可以执行一些数据处理数据转换的逻辑等等,官方的用户模块文件夹中有大量的 Python 文件,感兴趣的小伙伴可以自行查看学习。
导入插件
MySQL Workbench 中的插件都是通过模块的方式导入的,一些拓展的插件都是完全通过 Python 脚本实现的。MySQL Workbench 内置了 Python 3.12,可以执行所有的 Python 脚本,所有的插件都放到一个 modules
的文件夹里面。细节文档这里不放了,可以查看文中的链接。
插件要求
插件的编写有几个要求:
- 源文件必须位于用户模块文件夹中。用户模块文件夹路径如下
Operating System | File Path |
---|---|
Windows | %AppData%\MySQL\Workbench\modules |
macOS | ~username/Library/Application Support/MySQL/Workbench/modules |
Linux | ~username/.mysql/workbench/modules |
- 模块文件名必须以
_grt.py
结尾,例如:my_module_grt.py
- 必须通过
DefineModule
为插件添加元数据。例如:
from wb import *
ModuleInfo = DefineModule(name='MyModule', author='Your Name', version='1.0')
- 插件方法需要通过进行签名,指定方法的返回值类型和参数个数,参数类型。
@ModuleInfo.export(grt.INT, grt.STRING)
def checkString(s):...
看一下官方的 demo:
from wb import *
import grtModuleInfo = DefineModule(name='MyModule', author="your name", version='1.0')@ModuleInfo.export(grt.DOUBLE, grt.STRING, (grt.LIST, grt.DOUBLE))
def printListSum(message, doubleList):sum = 0for d in doubleList:sum = sum + dprint message, sumreturn sum
Script Shell 查询
Script Shell 是一个可以执行脚本,查看 MySQL Workbench 拓展接口文档的工具,打开方式如下:
查看下面的 Classes
选项卡,本次需求使用到了 db.query.Resultset
对象,可以在这里找到 Resultset
对象的方法和属性。MySQL Workbench 提供了大量的对象,如果有其他需求的小伙伴可以研究一下其他的对象如何使用,用得好的话拓展性很高。
编写插件
前面铺垫完了,开始看本次分享的插件吧。
import base64
import binascii
from base64 import b64encode, b64decode
from binascii import hexlify, unhexlifyimport grt
from wb import DefineModule, wbinputs
from workbench.log import log_infokey = binascii.unhexlify('你的key,十六进制字符串')
# python3
PY2 = False
PY3 = True_range = range
string_types = (str,)
text_type = str
binary_type = bytesE_FMT = 'UTF8'# S盒
S_BOX = {0X00: 0XD6, 0X01: 0X90, 0X02: 0XE9, 0X03: 0XFE, 0X04: 0XCC, 0X05: 0XE1, 0X06: 0X3D, 0X07: 0XB7,0X08: 0X16, 0X09: 0XB6, 0X0A: 0X14, 0X0B: 0XC2, 0X0C: 0X28, 0X0D: 0XFB, 0X0E: 0X2C, 0X0F: 0X05,0X10: 0X2B, 0X11: 0X67, 0X12: 0X9A, 0X13: 0X76, 0X14: 0X2A, 0X15: 0XBE, 0X16: 0X04, 0X17: 0XC3,0X18: 0XAA, 0X19: 0X44, 0X1A: 0X13, 0X1B: 0X26, 0X1C: 0X49, 0X1D: 0X86, 0X1E: 0X06, 0X1F: 0X99,0X20: 0X9C, 0X21: 0X42, 0X22: 0X50, 0X23: 0XF4, 0X24: 0X91, 0X25: 0XEF, 0X26: 0X98, 0X27: 0X7A,0X28: 0X33, 0X29: 0X54, 0X2A: 0X0B, 0X2B: 0X43, 0X2C: 0XED, 0X2D: 0XCF, 0X2E: 0XAC, 0X2F: 0X62,0X30: 0XE4, 0X31: 0XB3, 0X32: 0X1C, 0X33: 0XA9, 0X34: 0XC9, 0X35: 0X08, 0X36: 0XE8, 0X37: 0X95,0X38: 0X80, 0X39: 0XDF, 0X3A: 0X94, 0X3B: 0XFA, 0X3C: 0X75, 0X3D: 0X8F, 0X3E: 0X3F, 0X3F: 0XA6,0X40: 0X47, 0X41: 0X07, 0X42: 0XA7, 0X43: 0XFC, 0X44: 0XF3, 0X45: 0X73, 0X46: 0X17, 0X47: 0XBA,0X48: 0X83, 0X49: 0X59, 0X4A: 0X3C, 0X4B: 0X19, 0X4C: 0XE6, 0X4D: 0X85, 0X4E: 0X4F, 0X4F: 0XA8,0X50: 0X68, 0X51: 0X6B, 0X52: 0X81, 0X53: 0XB2, 0X54: 0X71, 0X55: 0X64, 0X56: 0XDA, 0X57: 0X8B,0X58: 0XF8, 0X59: 0XEB, 0X5A: 0X0F, 0X5B: 0X4B, 0X5C: 0X70, 0X5D: 0X56, 0X5E: 0X9D, 0X5F: 0X35,0X60: 0X1E, 0X61: 0X24, 0X62: 0X0E, 0X63: 0X5E, 0X64: 0X63, 0X65: 0X58, 0X66: 0XD1, 0X67: 0XA2,0X68: 0X25, 0X69: 0X22, 0X6A: 0X7C, 0X6B: 0X3B, 0X6C: 0X01, 0X6D: 0X21, 0X6E: 0X78, 0X6F: 0X87,0X70: 0XD4, 0X71: 0X00, 0X72: 0X46, 0X73: 0X57, 0X74: 0X9F, 0X75: 0XD3, 0X76: 0X27, 0X77: 0X52,0X78: 0X4C, 0X79: 0X36, 0X7A: 0X02, 0X7B: 0XE7, 0X7C: 0XA0, 0X7D: 0XC4, 0X7E: 0XC8, 0X7F: 0X9E,0X80: 0XEA, 0X81: 0XBF, 0X82: 0X8A, 0X83: 0XD2, 0X84: 0X40, 0X85: 0XC7, 0X86: 0X38, 0X87: 0XB5,0X88: 0XA3, 0X89: 0XF7, 0X8A: 0XF2, 0X8B: 0XCE, 0X8C: 0XF9, 0X8D: 0X61, 0X8E: 0X15, 0X8F: 0XA1,0X90: 0XE0, 0X91: 0XAE, 0X92: 0X5D, 0X93: 0XA4, 0X94: 0X9B, 0X95: 0X34, 0X96: 0X1A, 0X97: 0X55,0X98: 0XAD, 0X99: 0X93, 0X9A: 0X32, 0X9B: 0X30, 0X9C: 0XF5, 0X9D: 0X8C, 0X9E: 0XB1, 0X9F: 0XE3,0XA0: 0X1D, 0XA1: 0XF6, 0XA2: 0XE2, 0XA3: 0X2E, 0XA4: 0X82, 0XA5: 0X66, 0XA6: 0XCA, 0XA7: 0X60,0XA8: 0XC0, 0XA9: 0X29, 0XAA: 0X23, 0XAB: 0XAB, 0XAC: 0X0D, 0XAD: 0X53, 0XAE: 0X4E, 0XAF: 0X6F,0XB0: 0XD5, 0XB1: 0XDB, 0XB2: 0X37, 0XB3: 0X45, 0XB4: 0XDE, 0XB5: 0XFD, 0XB6: 0X8E, 0XB7: 0X2F,0XB8: 0X03, 0XB9: 0XFF, 0XBA: 0X6A, 0XBB: 0X72, 0XBC: 0X6D, 0XBD: 0X6C, 0XBE: 0X5B, 0XBF: 0X51,0XC0: 0X8D, 0XC1: 0X1B, 0XC2: 0XAF, 0XC3: 0X92, 0XC4: 0XBB, 0XC5: 0XDD, 0XC6: 0XBC, 0XC7: 0X7F,0XC8: 0X11, 0XC9: 0XD9, 0XCA: 0X5C, 0XCB: 0X41, 0XCC: 0X1F, 0XCD: 0X10, 0XCE: 0X5A, 0XCF: 0XD8,0XD0: 0X0A, 0XD1: 0XC1, 0XD2: 0X31, 0XD3: 0X88, 0XD4: 0XA5, 0XD5: 0XCD, 0XD6: 0X7B, 0XD7: 0XBD,0XD8: 0X2D, 0XD9: 0X74, 0XDA: 0XD0, 0XDB: 0X12, 0XDC: 0XB8, 0XDD: 0XE5, 0XDE: 0XB4, 0XDF: 0XB0,0XE0: 0X89, 0XE1: 0X69, 0XE2: 0X97, 0XE3: 0X4A, 0XE4: 0X0C, 0XE5: 0X96, 0XE6: 0X77, 0XE7: 0X7E,0XE8: 0X65, 0XE9: 0XB9, 0XEA: 0XF1, 0XEB: 0X09, 0XEC: 0XC5, 0XED: 0X6E, 0XEE: 0XC6, 0XEF: 0X84,0XF0: 0X18, 0XF1: 0XF0, 0XF2: 0X7D, 0XF3: 0XEC, 0XF4: 0X3A, 0XF5: 0XDC, 0XF6: 0X4D, 0XF7: 0X20,0XF8: 0X79, 0XF9: 0XEE, 0XFA: 0X5F, 0XFB: 0X3E, 0XFC: 0XD7, 0XFD: 0XCB, 0XFE: 0X39, 0XFF: 0X48
}# 系统参数FK
FK = (0XA3B1BAC6, 0X56AA3350, 0X677D9197, 0XB27022DC)# 固定参数CK
CK = (0X00070E15, 0X1C232A31, 0X383F464D, 0X545B6269,0X70777E85, 0X8C939AA1, 0XA8AFB6BD, 0XC4CBD2D9,0XE0E7EEF5, 0XFC030A11, 0X181F262D, 0X343B4249,0X50575E65, 0X6C737A81, 0X888F969D, 0XA4ABB2B9,0XC0C7CED5, 0XDCE3EAF1, 0XF8FF060D, 0X141B2229,0X30373E45, 0X4C535A61, 0X686F767D, 0X848B9299,0XA0A7AEB5, 0XBCC3CAD1, 0XD8DFE6ED, 0XF4FB0209,0X10171E25, 0X2C333A41, 0X484F565D, 0X646B7279)# 轮密钥缓存
_rk_cache = {}# 加密
SM4_ENCRYPT = 1
# 解密
SM4_DECRYPT = 0
# 分组byte数
BLOCK_BYTE = 16
BLOCK_HEX = BLOCK_BYTE * 2def num2hex(num, width=1):"""整数转为指定长度的十六进制字符串,不足补0>>> num2hex(1000, width=4)'03e8':param num: 整数:param width: 16进制字符串长度, 默认为1:return str"""return '{:0>{width}}'.format(hex(num)[2:].replace('L', ''),width=width)def _byte_unpack(num, byte_n=4):# 分解后元组长度_len = 4# 步长step = (byte_n // _len) * 2hex_str = num2hex(num=num, width=byte_n * 2)split_v = list(_range(len(hex_str)))[::step] + [len(hex_str)]return tuple([int(hex_str[s:e], base=16) for s, e inzip(split_v[:-1], split_v[1:])])def _byte_pack(byte_array, byte_n=4):_len = 4# byte_array每一项16进制字符串的长度width = (byte_n // _len) * 2if len(byte_array) != _len:raise ValueError('byte_array length must be 4.')return int(''.join([num2hex(num=v, width=width)for v in byte_array]), 16)def _s_box(byte):return S_BOX.get(byte)def _non_linear_map(byte_array):"""非线性变换, 输入A=(a0, a1, a2, a3)(b0, b1, b2, b3) = (Sbox(a0), Sbox(a1), Sbox(a2), Sbox(a3))"""return (_s_box(byte_array[0]), _s_box(byte_array[1]),_s_box(byte_array[2]), _s_box(byte_array[3]))def _linear_map(byte4):"""线性变换LL(B) = B ⊕ (B <<< 2) ⊕ (B <<< 10) ⊕ (B <<< 18) ⊕ (B <<< 24)"""_left = loop_left_shiftreturn byte4 ^ _left(byte4, 2) ^ _left(byte4, 10) ^ _left(byte4, 18) ^ _left(byte4, 24)def _linear_map_s(byte4):"""线性变换L'L'(B) = B ⊕ (B <<< 13) ⊕ (B <<< 23)"""_left = loop_left_shiftreturn byte4 ^ _left(byte4, 13) ^ _left(byte4, 23)def loop_left_shift(num, offset, base=32):"""循环向左移位>>> loop_left_shift(0b11010000, 3, base=8)>>> 0b10000110"""bin_str = '{:0>{width}}'.format(bin(num)[2:], width=base)rem = offset % basereturn int(bin_str[rem:] + bin_str[:rem], 2)def _rep_t(byte4):"""合成置换T, 由非线性变换和线性变换L复合而成"""# 非线性变换b_array = _non_linear_map(_byte_unpack(byte4))# 线性变换Lreturn _linear_map(_byte_pack(b_array))def _rep_t_s(byte4):"""合成置换T', 由非线性变换和线性变换L'复合而成"""# 非线性变换b_array = _non_linear_map(_byte_unpack(byte4))# 线性变换L'return _linear_map_s(_byte_pack(b_array))def _round_keys(mk):"""轮密钥由加密密钥通过密钥扩展算法生成加密密钥MK = (MK0, MK1, MK2, MK3)轮密钥生成算法:(K0, K1, K2, K3) = (MK0 ⊕ FK0, MK1 ⊕ FK1, MK2 ⊕ FK2, MK3 ⊕ FK3)rki = Ki+4 = Ki⊕T'(Ki+1 ⊕ Ki+2 ⊕ Ki+3 ⊕ CKi) i=0, 1,...,31:param mk: 加密密钥, 16byte, 128bit:return list"""# 尝试从轮密钥缓存中获取轮密钥# 没有获取到, 根据密钥扩展算法生成_rk_keys = _rk_cache.get(mk)if _rk_keys is None:mk0, mk1, mk2, mk3 = _byte_unpack(mk, byte_n=16)keys = [mk0 ^ FK[0], mk1 ^ FK[1], mk2 ^ FK[2], mk3 ^ FK[3]]for i in _range(32):rk = keys[i] ^ _rep_t_s(keys[i + 1] ^ keys[i + 2] ^ keys[i + 3] ^ CK[i])keys.append(rk)_rk_keys = keys[4:]# 加入轮密钥缓存中_rk_cache[mk] = _rk_keysreturn _rk_keysdef _round_f(byte4_array, rk):"""轮函数, F(X0, X1, X2, X3, rk) = X0 ⊕ T(X1 ⊕ X2 ⊕ X3 ⊕ rk):param byte4_array: (X0, X1, X2, X3), 每一项4byte, 32bit:param rk: 轮密钥, 4byte, 32bit"""x0, x1, x2, x3 = byte4_arrayreturn x0 ^ _rep_t(x1 ^ x2 ^ x3 ^ rk)def _crypt(num, mk, mode=SM4_ENCRYPT):"""SM4加密和解密:param num: 密文或明文 16byte:param mk: 密钥 16byte:param mode: 轮密钥顺序"""x_keys = list(_byte_unpack(num, byte_n=16))round_keys = _round_keys(mk)if mode == SM4_DECRYPT:round_keys = round_keys[::-1]for i in _range(32):x_keys.append(_round_f(x_keys[i:i + 4], round_keys[i]))return _byte_pack(x_keys[-4:][::-1], byte_n=16)def encrypt(clear_num, mk):"""SM4加密算法由32次迭代运算和1次反序变换R组成.明文输入为(X0, X1, X2, X3), 每一项4byte, 密文输出为(Y0, Y1, Y2, Y3), 每一项4byte轮密钥为rki, i=0,1,...,32, 4byte, 运算过程如下:1). 32次迭代运算: Xi+4 = F(Xi, Xi+1, Xi+2, Xi+3, rki), i=0,1,...,322). 反序变换: (Y0, Y1, Y2, Y3) = (X35, X34, X33, X32):param clear_num: 明文, 16byte:param mk: 密钥, 16byte"""return _crypt(num=clear_num, mk=mk)def decrypt(cipher_num, mk):"""SM4解密算法, 解密变换与加密变换结构相同, 不同的仅是轮密钥的使用顺序.解密时轮密钥使用顺序为(rk31,rk30,...,rk0):param cipher_num: 密文, 16byte:param mk: 密钥, 16byte"""return _crypt(num=cipher_num, mk=mk, mode=SM4_DECRYPT)def _padding(text, mode=SM4_ENCRYPT):"""加密填充和解密去填充"""# python2 is (basestring, )# python3 is (str, bytes)_str_or_bytes = string_types if PY2 else (string_types + (binary_type,))if text is None or not isinstance(text, _str_or_bytes):return# unicodeif isinstance(text, text_type):text = text.encode(encoding=E_FMT)if mode == SM4_ENCRYPT:# 填充p_num = BLOCK_BYTE - (len(text) % BLOCK_BYTE)space = '' if PY2 else b''pad_s = (chr(p_num) * p_num) if PY2 else (chr(p_num).encode(E_FMT) * p_num)res = space.join([text, pad_s])else:# 去填充p_num = ord(text[-1]) if PY2 else text[-1]res = text[:-p_num]return resdef _key_iv_check(key_iv):"""密钥或初始化向量检测"""# 密钥if key_iv is None or not isinstance(key_iv, (string_types, binary_type)):raise TypeError('Parameter key or iv:{} not string_types or binary_type'.format(key_iv))if isinstance(key_iv, text_type):key_iv = key_iv.encode(encoding=E_FMT)if len(key_iv) > BLOCK_BYTE:raise ValueError('Parameter key or iv:{} byte greater than {}'.format(key_iv.decode(E_FMT),BLOCK_BYTE))return key_ivdef _hex(str_or_bytes):# PY2: _hex('北京') --> 'e58c97e4baac'# PY3: _hex('北京') --> b'e58c97e4baac'if PY2:hex_str = hexlify(str_or_bytes)else:# python3if isinstance(str_or_bytes, text_type):byte = str_or_bytes.encode(encoding=E_FMT)elif isinstance(str_or_bytes, binary_type):byte = str_or_byteselse:byte = b''hex_str = hexlify(byte)return hex_strdef _unhex(hex_str):# PY2: _unhex('e58c97e4baac') --> '\xe5\x8c\x97\xe4\xba\xac'# PY3: _unhex('e58c97e4baac') --> b'\xe5\x8c\x97\xe4\xba\xac'return unhexlify(hex_str)# 电子密码本(ECB)
def encrypt_ecb(plain_text, key):"""SM4(ECB)加密:param plain_text: 明文:param key: 密钥, 小于等于16字节"""plain_text = _padding(plain_text, mode=SM4_ENCRYPT)if plain_text is None:return# 密钥检验key = _key_iv_check(key_iv=key)plain_hex = _hex(plain_text)cipher_hex_list = []for i in _range(len(plain_text) // BLOCK_BYTE):sub_hex = plain_hex[i * BLOCK_HEX:(i + 1) * BLOCK_HEX]cipher = encrypt(clear_num=int(sub_hex, 16),mk=int(_hex(key), 16))cipher_hex_list.append(num2hex(num=cipher, width=BLOCK_HEX))cipher_text = b64encode(_unhex(''.join(cipher_hex_list)))return cipher_text if PY2 else cipher_text.decode(E_FMT)def decrypt_ecb(cipher_text, key):"""SM4(ECB)解密:param cipher_text: 密文:param key: 密钥, 小于等于16字节"""cipher_text = b64decode(cipher_text)cipher_hex = _hex(cipher_text)# 密码检验key = _key_iv_check(key_iv=key)plain_hex_list = []for i in _range(len(cipher_text) // BLOCK_BYTE):sub_hex = cipher_hex[i * BLOCK_HEX:(i + 1) * BLOCK_HEX]plain = decrypt(cipher_num=int(sub_hex, 16),mk=int(_hex(key), 16))plain_hex_list.append(num2hex(num=plain, width=BLOCK_HEX))plain_text = _padding(_unhex(''.join(plain_hex_list)),mode=SM4_DECRYPT)return plain_text if PY2 else plain_text.decode(E_FMT)# 密码块链接(CBC)
def encrypt_cbc(plain_text, key, iv):"""SM4(CBC)加密:param plain_text: 明文:param key: 密钥, 小于等于16字节:param iv: 初始化向量, 小于等于16字节"""plain_text = _padding(plain_text, mode=SM4_ENCRYPT)if plain_text is None:return# 密钥检验key = _key_iv_check(key_iv=key)# 初始化向量监测iv = _key_iv_check(key_iv=iv)plain_hex = _hex(plain_text)ivs = [int(_hex(iv), 16)]for i in _range(len(plain_text) // BLOCK_BYTE):sub_hex = plain_hex[i * BLOCK_HEX:(i + 1) * BLOCK_HEX]cipher = encrypt(clear_num=(int(sub_hex, 16) ^ ivs[i]),mk=int(_hex(key), 16))ivs.append(cipher)cipher_text = b64encode(_unhex(''.join([num2hex(num=c, width=BLOCK_HEX)for c in ivs[1:]])))return cipher_text if PY2 else cipher_text.decode(E_FMT)def decrypt_cbc(cipher_text, key, iv):"""SM4(CBC)解密:param cipher_text: 密文:param key: 密钥 小于等于16字节:param iv: 初始化向量 小于等于16字节"""cipher_text = b64decode(cipher_text)cipher_hex = _hex(cipher_text)# 密钥检测key = _key_iv_check(key_iv=key)# 初始化向量检测iv = _key_iv_check(key_iv=iv)ivs = [int(_hex(iv), 16)]plain_hex_list = []for i in _range(len(cipher_text) // BLOCK_BYTE):sub_hex = cipher_hex[i * BLOCK_HEX:(i + 1) * BLOCK_HEX]cipher = int(sub_hex, 16)plain = (ivs[i] ^ decrypt(cipher_num=cipher,mk=int(_hex(key), 16)))ivs.append(cipher)plain_hex_list.append(num2hex(num=plain, width=BLOCK_HEX))plain_text = _padding(_unhex(''.join(plain_hex_list)),mode=SM4_DECRYPT)return plain_text if PY2 else plain_text.decode(E_FMT)ModuleInfo = DefineModule(name="DataUtils", author="b", version="1.0")@ModuleInfo.plugin("wb.sqlide.decryptData", caption="decrypt data from resultSet",input=[wbinputs.currentResultset(), wbinputs.currentEditableResultset()], pluginMenu="SQL/Utilities",accessibilityName="decryptData from resultSet")
@ModuleInfo.export(grt.INT, grt.classes.db_query_Resultset, grt.classes.db_query_EditableResultset)
def decryptData(result_set, edit_result_set):"""decryptData from resultSet"""try:row_count = result_set.rowCountcolumn_count = len(result_set.columns)log_info(str(row_count) + '\n')log_info(str(column_count) + '\n')for a in range(row_count - 1):result_set.goToRow(a)edit_column(result_set, edit_result_set, column_count)result_set.nextRow()except Exception as e:log_info(str(e) + '\n')return 0def edit_column(result_set, edit_result_set, column_count):for a in range(column_count - 1):value = result_set.stringFieldValue(a)log_info(value + '\n')if value.startswith('enc:'):b = binascii.unhexlify(value[4:])data = base64.b64encode(b).decode()edit_result_set.setStringFieldValue(a, decrypt_ecb(data, key))
插件效果
上面的插件实现的功能是,在通过 SQL 查询完数据之后,点击 decryptData from resultSet
即可自动解密查询结果中被加密的数据。由于用到了 SM4 来解密,所以这里为了不引入外部依赖,将 pysm4
这个库的源码放到同一个插件脚本里面了,所以上面的一大堆方法都是 pysm4
里面的,可以先忽略。
执行插件前:
执行插件后,enc
前缀已经没有了,自动解密出来原始的数据了。
插件编写
首先,需要使用 DefineModule
来实例化一个对象,用这个对象来注册插件,通过装饰器的方式,DefineModule
中会定义名字,作者,版本等信息。
ModuleInfo = DefineModule(name="DataUtils", author="b", version="1.0")@ModuleInfo.plugin("wb.sqlide.decryptData", caption="decrypt data from resultSet",input=[wbinputs.currentResultset(), wbinputs.currentEditableResultset()], pluginMenu="SQL/Utilities",accessibilityName="decryptData from resultSet")
@ModuleInfo.export(grt.INT, grt.classes.db_query_Resultset, grt.classes.db_query_EditableResultset)
这里要注意几点:
@ModuleInfo.plugin
中的参数,第一个"wb.sqlide.decryptData"是可以自定义的caption
是插件的说明,也可以自定义- input 参数是根据你的需求,例如要操作
Resultset
对象,就去wbinputs
中去找返回Resultset
类型的方法,这里我额外用到了EditableResultset
对象,因为我还要对Resultset
中的某行数据进行更改。 pluginMenu
就是在菜单中的位置,可以更改。@ModuleInfo.export
中的参数,第一个参数是返回值的类型,后面的所有参数都是要注册的插件的参数,和刚才的inputs
参数的类型和数量要一致。
def decryptData(result_set, edit_result_set):"""decryptData from resultSet"""try:row_count = result_set.rowCountcolumn_count = len(result_set.columns)log_info(str(row_count) + '\n')log_info(str(column_count) + '\n')for a in range(row_count - 1):result_set.goToRow(a)edit_column(result_set, edit_result_set, column_count)result_set.nextRow()except Exception as e:log_info(str(e) + '\n')return 0
自定义的插件方法,参数的数量和类型要和刚才定义的一致,返回值的类型也要和刚才定义的一致。
完成以上内容后就可以开始编写插件的逻辑了,插件的逻辑比较简单,这里我就不详细分析了。主要的功能就是实现一个解密函数,对 Resultset
中某行的加密数据进行解密。
注意事项
插件写完了,说几点注意事项。
- 插件的加载是通过 Python 进行的,所以每次插件的更改都要重启 MySQL Workbench 才能生效。
- 需要输出日志的话可以自己在 Python 中写文件,也可以通过
from workbench.log import log_info
的方式把日志打印到软件的日志里面。 - MySQL Workbench 的插件其实是可以调用外部依赖的,不过我没有在官方的文档中找到教程,自己摸索了一下,可以把外部依赖文件夹手动复制到 C:\Program Files\MySQL\MySQL Workbench 8.0 CE\python\site-packages 这个路径中,即可在插件中直接
import
调用了。 Resultset
类型和EditableResultset
类型应该有继承关系,EditableResultset
也可以用Resultset
的属性和方法。
总结
MySQL Workbench 的文档写的太差了,好多用法都没有明确的文档,还得靠自己猜和看官方的脚本才能知道怎么用,不过最终花了几个小时还是搞定了这个功能。
相关文章:

可能是全网第一个MySQL Workbench插件编写技巧
引言 应公司要求,数据库的敏感数据在写入到数据库中要进行加密,但是在测试环境查询数据的时候要手动解密,很不方便,有的时候数据比较多,解密比较麻烦。遂研究了一下如何通过 MySQL Workbench 的插件来实现查询数据一键…...

D62【python 接口自动化学习】- python基础之数据库
day62 SQL 基础 学习日期:20241108 学习目标:MySQL数据库-- 131 SQL基础和DDL 学习笔记: SQL的概述 SQL语言的分类 SQL的语法特征 DDL - 库管理 DDL - 表管理 总结 SQL是结构化查询语言,用于操作数据库,通用于绝大…...

探索美赛:从准备到挑战的详细指南
前言 美国大学生数学建模竞赛(MCM/ICM),简称“美赛”,是全球规模最大的数学建模竞赛之一。它鼓励参赛者通过数学建模来解决现实世界中的复杂问题,广受世界各地大学生的欢迎。本文将详细介绍美赛的全过程,从…...

IP地址查询——IP归属地离线库
自从网络监管部门将现实IP地址列入监管条例,IP地址的离线库变成网络企业发展业务的不可或缺的一部分,那么IP地址离线库是什么,又能够给我们带来什么呢? 什么是IP地址离线库? IP地址离线库是IP地址服务商将通过各种合…...

“倒时差”用英语怎么说?生活英语口语学习柯桥外语培训
“倒时差”用英语怎么说? “倒时差”,这个让无数旅人闻之色变的词汇,在英语中对应的正是“Jet Lag”。"Jet" 指的是喷气式飞机,而 "lag" 指的是落后或延迟。这个短语形象地描述了当人们乘坐喷气式飞机快速穿…...

Linux入门攻坚——37、Linux防火墙-iptables-3
私网地址访问公网地址的问题,请求时,目标地址是公网地址,可以在公网路由器中进行路由,但是响应报文的目的地址是私网地址,此时在公网路由器上就会出现问题。公网地址访问私网地址的问题,需要先访问一个公网…...

微服务架构面试内容整理-安全性-Spring Security
Spring Security 是 Spring 框架中用于实现认证和授权的安全模块,它提供了全面的安全解决方案,可以帮助开发者保护 Web 应用、微服务和 API 免受常见的安全攻击。以下是 Spring Security 的主要特点、工作原理和使用场景: 主要特点 1. 身份认证与授权: 提供多种认证方式,…...

新的服务器Centos7.6 安装基础的环境配置(新服务器可直接粘贴使用配置)
常见的基础服务器配置之Centos命令 正常来说都是安装一个docker基本上很多问题都可以解决了,我基本上都是通过docker去管理一些容器如:mysql、redis、mongoDB等之类的镜像,还有一些中间件如kafka。下面就安装一个 docker 和 nginx 的相关配置…...

深度学习:广播机制
广播机制(Broadcasting)是 PyTorch(以及其他深度学习框架如 NumPy)中的一种强大功能,它允许不同形状的张量进行逐元素操作,而不需要显式地扩展张量的维度。广播机制通过自动扩展较小的张量来匹配较大张量的…...

音视频入门基础:FLV专题(25)——通过FFprobe显示FLV文件每个packet的信息
音视频入门基础:FLV专题系列文章: 音视频入门基础:FLV专题(1)——FLV官方文档下载 音视频入门基础:FLV专题(2)——使用FFmpeg命令生成flv文件 音视频入门基础:FLV专题…...

Openstack7--安装消息队列服务RabbitMQ
只需要在控制节点安装 安装RabbitMQ yum -y install rabbitmq-server 启动RabbitMQ并设置开机自启 systemctl start rabbitmq-server;systemctl enable rabbitmq-server 创建 rabbitmq 用户 并设置密码为 000000 rabbitmqctl add_user rabbitmq 000000 如果你不慎创错了…...

day55 图论章节刷题Part07([53.寻宝]prim算法、kruskal算法)
前言:使用最小生成树的方法解决将所有节点连接起来所需的最小路径问题。 prim算法 Prim算法是一种贪心算法,从任意一个顶点开始构建最小生成树。每次选择当前已加入生成树的顶点中,距离最近的尚未加入生成树的顶点,直到所有顶点…...

LeetCode 93-复制 IP地址
题目链接:LeetCode93 欢迎留言交流,每天都会回消息。 class Solution {//定义结果集,返回最终结果List<String> rs new ArrayList<>();public List<String> restoreIpAddresses(String s) {//将字符串包装为可变长度的字…...

海底捞点单
单点锅底推荐: 番茄锅底通31 牛油麻辣通44 清汤麻辣备44 菌汤锅底通31 小吃&主食: 捞派捞面一黄金小馒头一茴香小油条 红糖枇杷一小酥肉 DIY锅底推荐: 1.寿喜锅:海鲜味酱4勺陈醋1勺蚝油2勺盐适量白糖7勺 芹菜1勺 2.麻辣锅底…...

It’s All About Your Sketch: Democratising Sketch Control in Diffusion Models
翻译: 摘要 本文揭示了草图在扩散模型中的潜力,解决了生成式人工智能中直接草图控制的虚假承诺。我们重要的是使这个过程更加普及,让业余的草图也能生成精确的图像,真正实现“你画的就是你得到的”。一项初步研究强调了这一研究的…...

Java基础-组件及事件处理(下)
(创作不易,感谢有你,你的支持,就是我前行的最大动力,如果看完对你有帮助,请留下您的足迹) 目录 面板组件 说明 常见组件 JScrollPane常用构造方法 JScrollPane设置面板滚动策略的方法 JScrollPane滚…...

npm list -g --depth=0(用来列出全局安装的所有 npm 软件包而不显示它们的依赖项)
您提供的命令 npm list -g --depth0 是在 Node Package Manager (npm) 的上下文中使用的,用来列出全局安装的所有 npm 软件包而不显示它们的依赖项。 这是它的运作方式: npm list -g --depth0-g: 指定列表应包括全局安装的软件包。--depth0: 限制树形结…...

深度学习:nn.Linear
nn.Linear 是 PyTorch 中的一个线性层(全连接层),用于将输入张量从一个维度空间映射到另一个维度空间。具体来说,nn.Linear 执行以下操作: outputinputweightTbias 其中: input 是输入张量。 weight 是权重…...

大数据新视界 -- 大数据大厂之 Impala 性能提升:高级执行计划优化实战案例(下)(18/30)
💖💖💖亲爱的朋友们,热烈欢迎你们来到 青云交的博客!能与你们在此邂逅,我满心欢喜,深感无比荣幸。在这个瞬息万变的时代,我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...

常用的Anaconda Prompt命令行指令
一、环境管理 查看已安装的环境 conda env list 或 conda info --envs:列出所有已安装的Anaconda环境。 创建新环境 conda create -n env_name pythonx.x:创建一个名为env_name的新环境,并指定Python版本为x.x。 激活环境 conda activate env…...

如何低成本、零代码开发、5分钟内打造一个企业AI智能客服?
传统客服因员工效率低、时段需求波动大、数据管理费时费力等管理难题,导致难以满足用户需求,无法深入挖掘客服数据价值,造成客源流失。而智能体搭建的“智能客服”能借助大模型和知识库知识,助力实现数字化运营,破解企…...

全网最全最新最细的MYSQL5.7下载安装图文教程
一、MYSQL两种安装包格式 MySQL安装文件分为两种,一种是msi格式的,一种是zip格式的。zip格式相当于绿色版,不需要安装,只需解压缩之后就可以使用了,但是要进行配置。msi格式是安装版。 二、MYSQL官网下载 1.官网地址…...

NoSQL数据库与关系型数据库的主要区别
NoSQL数据库与关系型数据库在多个方面存在显著区别,以下是对这些主要区别的详细描述: 一、数据存储模型 关系型数据库:使用表格形式存储数据,每个表格由行和列组成,行表示记录,列表示字段。数据之间的关系…...

ubuntu24.04安装matlab失败
又是摸鱼摆烂的一天,好难过~ 官方教程:https://ww2.mathworks.cn/help/install/ug/install-products-with-internet-connection.html 问题描述:https://ww2.mathworks.cn/matlabcentral/answers/2158925-cannot-install-matlab-r2…...

Oracle 11g rac 集群节点的修复过程
Oracle 11g rac 集群节点的修复过程 目录 Oracle 11g rac 集群节点的修复过程一、问题的产生二、修复过程1、执行 roothas.pl 命令2、执行 root.sh 命令3、查看集群信息4、查看节点2的IP地址5、查看节点2的监听信息 一、问题的产生 用户的双节点 Oracle 11g rac 集群ÿ…...

c++:string(一)
文章目录 一string类1C语言中的字符串2C中的string二遍历1[ ]2迭代器3const迭代器4范围for5auto6总结三String的尾插1size和length2max_size,capacity和clear3访问接口4尾插字符和字符串5 append的重载三string的扩容问题(1)怎么扩容(2&#…...

github和Visual Studio
1、代码下载和提交 GitHubDesktopSetup-x64.exe 使用很简单,自己稍微琢磨下就明白了。 2、Visual Studio 2022 2.1 安装组件及学习内容 Visual Studio 中的 CMake 项目 | Microsoft Learn 2.2 打开 CMakeLists.txt 文件 定位并选择 CMakeLists.txt 文件 …...

django框架-settings.py文件的配置说明
以下是一些Django的核心配置和其默认值. 下面列出了contrib应用提供的配置, 后面是核心配置的专题索引. 关于介绍性资料, 详见 settings指南. ABSOLUTE_URL_OVERRIDES 默认值: {} (空字典) 它是一个将 “app_label.model_name” 字符串映射到接受模型对象并返回其URL的函数的…...

【C语言】缺陷管理流程
请解释一下缺陷管理流程,包括缺陷的发现、跟踪、验证和关闭等环节。 缺陷管理流程是一种软件质量保证过程,其目的是识别、记录、分析、解决并最终消除程序中的错误或问题。以下是这个流程的主要步骤: 缺陷发现 (Bug Discovery): 这通常是通过…...

基于深度学习的猫狗识别
基于深度学习的猫狗识别是计算机视觉领域中的一个经典问题,它主要利用深度学习技术来训练和构建模型,以便能够自动区分和识别图像中的猫和狗。以下是一个基于深度学习的猫狗识别的简要介绍: 一、数据集准备 要实现猫狗识别,首先需…...