马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?站点注册
×
PBIDEA:用 uo_config 把配置存成 JSON 文件(替代 ini 的轻量方案)
阅读说明
1. 适用版本:PB 10 / 12.5 及以上;uo_config 是 PBIDEA 组件(位于 websuite.pbl),经典 PowerBuilder 没有这个对象,需要安装 PBIDEA 运行库。
2. 支持数据库:本文不涉及数据库,配置保存在本地 JSON 文件或内存中(与外部库共用时可对接任意 DBMS)。
3. 操作系统与环境要求:Windows 7+;需安装 PBIDEA 运行库 PbIdea.dll;工程库列表中加入 websuite.pbl(uo_config/uo_json/uo_sysenv 在此 PBL,启用加密时还需 uo_crypto)。
4. 难度系数:★★☆☆☆(核心读写是入门级,文末对象序列化与加密属进阶扩展)。
5. 其它阅读说明:建议先读 Day 2《uo_json 读写 JSON》——uo_config 内部就是建立在 uo_json 之上的;示例代码可直接复制到按钮 clicked 事件里运行,无需界面交互。
一、为什么用 JSON 替代 ini
老 PowerBuilder 项目里,配置大多写成 ini 文件,靠 ProfileString / SetProfileString 读写。ini 有几个绕不开的短板:
- 只支持「段/键/值」两段式,没法表达层级(比如「数据库.主库.主机」这种嵌套);
- 值全是字符串,数字、布尔、小数都要自己转;
- 没有标准的结构化格式,想和别的语言(Java/前端)共享配置很别扭;
- 明文存储,敏感信息(口令、令牌)无处安放。
PBIDEA 的 uo_config 就是为这个场景准备的:它用 JSON 文件代替 ini,对外提供和 ini 一样简单的 set_* / get_* 接口,但底层是 uo_json,所以天然支持层级键、多类型、可加密,还能直接把一段 JSON 字符串或整个窗口的属性读进读出。一句话:ini 的写法,JSON 的能力。
uo_config 的关键事实先说清楚:
- 它继承自 nonvisualobject,内部持有一个 uo_json _json 作为配置容器;
- 所有配置值最终都以字符串形式存在 JSON 里,set_int/set_double/set_boolean 只是在写入时 string() 转一下、读取时再 long()/double()/boolean 转回来;
- 键沿用 uo_json 的路径语法,用 /分组/名称 表示层级(例如 /Database/Host);
- 存盘时调用 uo_json.Writejsonfile,生成的是标准、可读的 JSON 文本。
二、最小可用:写入并保存一个配置文件
最常用的链路就是「打开文件 → set_* 写值 → save 落盘」。下面这段演示把应用名、重试次数、数据库连接信息写进 app_config.json。
前置:工程的库列表里已经加入 websuite.pbl(uo_config/uo_json/uo_sysenv 都在里面),并且 PbIdea.dll 放在程序目录。
步骤:新建一个窗口,在按钮 cb_save 的 clicked 事件里贴入下面代码,运行后点按钮即可看到 MessageBox。
说明:open(文件名) 会经 uo_sysenv.getpath 把文件名解析成完整路径(通常落在程序目录下);文件不存在时自动新建空配置。 - // 示例输入:配置文件名(PBIDEA 会用 uo_sysenv 解析成完整路径,一般落在程序目录)
- string ls_file
- ls_file = 'app_config.json'
- // 示例输入:要保存的应用名称
- string ls_app
- ls_app = '订单管理系统'
- // 示例输入:最大重试次数
- long ll_maxretry
- ll_maxretry = 3
- uo_config lcfg
- lcfg = create uo_config
- // 以指定文件打开(不存在则新建空配置)
- lcfg.open(ls_file)
- // 写入各项配置:键支持层级路径 /分组/名称
- lcfg.set_string('/AppName', ls_app)
- lcfg.set_int('/MaxRetry', ll_maxretry)
- lcfg.set_string('/Database/Host', '127.0.0.1')
- lcfg.set_int('/Database/Port', 1433)
- // 落盘保存
- lcfg.save()
- MessageBox('保存结果', '配置已写入 ' + lcfg.filename())
- destroy lcfg
复制代码
运行后,app_config.json 大致长这样:
- {
- "AppName": "订单管理系统",
- "MaxRetry": "3",
- "Database": {
- "Host": "127.0.0.1",
- "Port": "1433"
- }
- }
复制代码
注意 MaxRetry 和 Port 在 JSON 里是字符串 "3" / "1433"——这是 uo_config 的设计(所有值以字符串存),读取时再转回数字,不用你操心。
三、读回来:带默认值才稳健
读取用对应的 get_*。强烈建议每次都传默认值——键不存在、或存储值为空字符串时,get_string(key, 默认) 会回退到默认值,程序不会因为取不到配置而崩。
- // 示例输入:同样的配置文件名
- string ls_file
- ls_file = 'app_config.json'
- uo_config lcfg
- lcfg = create uo_config
- lcfg.open(ls_file)
- // 读取:带默认值,键不存在或为空时回退
- string ls_app
- ls_app = lcfg.get_string('/AppName', '未命名应用')
- string ls_host
- ls_host = lcfg.get_string('/Database/Host', '127.0.0.1')
- long ll_port
- ll_port = lcfg.get_int('/Database/Port', 1433)
- MessageBox('读取结果', '应用=' + ls_app + '~n数据库=' + ls_host + ':' + string(ll_port))
- destroy lcfg
复制代码
要点:get_string(key, default) 内部会先 trim(default),所以默认值前后不要带空格;而且当存储值是空串时也会返回默认值(源码里 if len(ls_text) = 0 then ls_text = default)。这意味着「空配置」和「没配置」在读取侧是被同等对待的,设计上是有意如此,写代码时心里要有数。
四、支持的类型:字符串 / 整数 / 小数 / 布尔
uo_config 提供四组读写,覆盖绝大多数配置场景:
| 类型 | 写入 | 读取 | | 字符串 | set_string(key, value) | get_string(key) / get_string(key, 默认) | | 整数(long) | set_int(key, value) | get_int(key) / get_int(key, 默认) | | 小数(double) | set_double(key, value) | get_double(key) / get_double(key, 默认) | | 布尔 | set_boolean(key, value) | get_boolean(key, 默认) |
布尔在 JSON 里存成 "true" / "false" 文本,get_boolean 判定规则是:s = "true" 为真,其余(false/yes/1 之外)为假。所以不要自己存 "1" 当真,老老实实用 set_boolean 写,读取才稳。
五、不落盘:从内存 JSON 字符串加载
很多时候配置不是来自本地文件,而是登录后由服务端下发一段 JSON。uo_config 不用文件也能用:
- fromstring(json):把一段 JSON 文本解析进内存配置(不加密、不落盘);
- load(json串, 密钥):同样是内存加载,但第二参给了密钥就会启用解密(和文件加密同一套机制);
- tostring():把当前配置导出成格式化 JSON 字符串,方便调试或回传服务端。
- // 示例输入:服务端下发的 JSON 配置串
- string ls_json
- ls_json = '{"AppName":"云端版","Timeout":30,"EnableLog":true}'
- uo_config lcfg
- lcfg = create uo_config
- // 从字符串解析(不落盘,也不加密)
- lcfg.fromstring(ls_json)
- string ls_name
- ls_name = lcfg.get_string('/AppName', '')
- long ll_timeout
- ll_timeout = lcfg.get_int('/Timeout', 0)
- boolean lb_log
- lb_log = lcfg.get_boolean('/EnableLog', false)
- MessageBox('内存配置', '应用=' + ls_name + '~n超时=' + string(ll_timeout) + '~n日志=' + string(lb_log))
- destroy lcfg
复制代码
注意这里键也带前导 /(/AppName、/Timeout),和文件用法完全一致——uo_config 的键始终是 uo_json 路径语法,统一用 /xxx 最省心。
六、敏感信息:自动加密存储
把数据库口令、接口令牌直接写明文 JSON 是灾难。uo_config 内置了 SM4 加密:只要 open 时多传一个密钥,之后所有 set_string 写入的值都会以 SM4(ECB) 密文落盘,读取时再用同一密钥自动解密。底层用的是 uo_crypto,但对你完全透明。
前置:加密依赖 uo_crypto(同在 websuite.pbl)。open(文件名, 密钥) 传入密钥即自动启用加密,密钥为空则不加密。
说明:主密钥由开发者自己掌握,演示里写死只为说明流程,真实项目请从安全位置(如用户口令派生、或加密的配置服务)获取,不要硬编码。 - // 示例输入:配置文件名
- string ls_file
- ls_file = 'secret.json'
- // 示例输入:主密钥(真实项目不要硬编码,这里仅为演示流程)
- string ls_key
- ls_key = 'MyMasterKey2026'
- // 示例输入:要保护的数据库口令
- string ls_pwd
- ls_pwd = 'DbPass@123'
- uo_config lcfg
- lcfg = create uo_config
- // 带密钥打开:之后写的值都会以 SM4(ECB) 密文落盘
- lcfg.open(ls_file, ls_key)
- lcfg.set_string('/DbPassword', ls_pwd)
- lcfg.save()
- MessageBox('加密保存', '口令已加密写入 ' + lcfg.filename())
- destroy lcfg
复制代码
读回来时,必须用同一个密钥打开才能解密:
- // 示例输入:同样的文件名与主密钥
- string ls_file
- ls_file = 'secret.json'
- string ls_key
- ls_key = 'MyMasterKey2026'
- uo_config lcfg
- lcfg = create uo_config
- lcfg.open(ls_file, ls_key)
- // 用同一密钥打开,get_string 会自动解密
- string ls_pwd
- ls_pwd = lcfg.get_string('/DbPassword', '')
- MessageBox('解密读取', '数据库口令=' + ls_pwd)
- destroy lcfg
复制代码
加密机制的内部细节(源码已核实):密钥会先 uo_crypto.md5(密钥) 做一次散列作为 SM4 密钥,固定 IV 为 "0123456701234567",算法 "sm4_ecb"。换句话说——密钥丢了就解不回来,且 md5 散列 + ECB 属于够用但不算强的安全等级,真要扛专业攻击还得上更强的方案。它解决的是「配置文件明文躺盘」这个最普遍的痛点,不是高安全场景的银弹。
七、进阶:把窗口/控件属性序列化进配置
uo_config 还有一组很能打的能力——对象序列化。它可以把整个窗口(及其上的控件)的属性写成 JSON,下次打开时原样还原。典型用途:记住用户拖好的窗口大小、分栏位置、Tab 选中页、各输入控件的最后内容。
- read_object(powerobject obj, boolean encrypt):把对象(窗口/用户对象/Tab)及其子控件的属性序列化进配置,encrypt=true 时加密;
- write_object(powerobject obj):按类名匹配,把配置写回对象及其控件属性,并触发 on_data_change 事件刷新。
前置:下面两段代码写在窗口(例如 w_main)的脚本里,this 就是当前窗口实例。序列化会递归遍历窗口上的 control[](窗口/用户对象/Tab 都支持),OLE 控件会被跳过。
步骤(保存布局):在「保存布局」按钮 clicked 里贴第一段;步骤(恢复布局):在窗口 open 事件里贴第二段,打开窗口即自动还原。 - // 示例输入:布局文件名
- string ls_file
- ls_file = 'window_layout.json'
- uo_config lcfg
- lcfg = create uo_config
- lcfg.open(ls_file)
- // 把当前窗口(含其上控件)属性序列化进配置;encrypt=false 不加密
- lcfg.read_object(this, false)
- lcfg.save()
- MessageBox('布局保存', '窗口布局已写入 ' + lcfg.filename())
- destroy lcfg
复制代码- // 示例输入:同样的布局文件名(在窗口 open 事件里恢复)
- string ls_file
- ls_file = 'window_layout.json'
- uo_config lcfg
- lcfg = create uo_config
- lcfg.open(ls_file)
- // 反序列化:按类名匹配,把配置写回当前窗口及其控件属性
- lcfg.write_object(this)
- lcfg.close(false)
- MessageBox('布局恢复', '已从 ' + lcfg.filename() + ' 还原窗口布局')
- destroy lcfg
复制代码
几个边界要心里有数:
- 序列化键以对象类名(如 w_main)为根节点,所以类名变了就匹配不上,重命名窗口会丢失旧布局;
- 可以通过 read_object(obj, encrypt, exclude, include) 的第三、四参控制「排除哪些字段 / 只含哪些字段」(逗号分隔,include 支持 * 前缀做前缀匹配),避免把不该持久化的属性(如临时状态)写进去;
- 恢复后 uo_config 会触发你在实例变量 EventName(默认 "on_data_change")指定的事件,想做额外刷新逻辑就在窗口里写这个事件。
八、另一种选择:uo_kv 嵌入式键值库
如果觉得「一个 JSON 文件」不够用,比如想要带类型的多值、可加密、支持多线程的本地键值库,PBIDEA 还提供了 uo_kv(同在 websuite.pbl,底层是 PbIdea.dll 的 kv* 系列函数)。它和 uo_config 的定位不同:
- uo_config:面向「人能直接看懂、能和服务端 JSON 互通」的配置文件,文本可读;
- uo_kv:面向「程序内部高频读写的小型键值库」,类型更丰富(支持 blob/数组/日期时间等 any 取值),密码为空不加密、非空则加密,且声明支持多线程访问。
- // 示例输入:本地键值库名(不存在则创建)
- string ls_db
- ls_db = 'local_cache'
- uo_kv lkv
- lkv = create uo_kv
- // 打开数据库,第二参为空表示不加密
- lkv.Open(ls_db, '')
- lkv.Set('lastSync', '2026-08-31 07:00')
- string ls_v
- ls_v = ''
- lkv.Get('lastSync', ls_v)
- MessageBox('uo_kv', 'lastSync=' + ls_v)
- lkv.Close()
- destroy lkv
复制代码
选型建议:配置要给人看、要跨语言共享、要随服务下发的,用 uo_config;纯程序内部缓存、要存二进制或复杂类型、在意并发的,用 uo_kv。两者都基于 PBIDEA 运行库,按需取用即可。
九、常见坑与边界清单
- 键要带前导 /:统一写成 /AppName、/Database/Host,和 uo_json 路径语法保持一致,最不容易踩坑。
- 值都是字符串:get_int 拿到的是 long、get_double 拿到的是 double,但磁盘上全是字符串;别指望 JSON 里是数字类型。
- 默认值语义:get_string(key, 默认) 在「键不存在」和「值为空串」两种情况下都返回默认值,二者不分。
- 加密密钥即一切:用 open(文件名, 密钥) 写的密文,必须用同一个密钥读;密钥丢失无法恢复,且算法是 md5 散列 + SM4_ECB,只防明文泄露,不防专业破解。
- save 与 close 的区别:save() 只是落盘、对象还在;close(true) 落盘并清空内存、close(false) 丢弃改动不保存。忘记 save()/close(true) 配置不会写入文件。
- 对象序列化认类名:read_object/write_object 以类名匹配,重命名窗口会导致旧布局失效;用 exclude/include 控制持久化范围。
- 依赖要带齐:websuite.pbl(uo_config/uo_json/uo_sysenv)必备,加密还需 uo_crypto;运行时 PbIdea.dll 必须在程序目录,否则一调用就崩。
十、小结
uo_config 用「ini 的接口 + JSON 的内核」解决了老 PB 项目配置管理的几大痛点:层级键、多类型、可加密、可和服务端 JSON 互通,还能顺手把窗口布局序列化持久化。日常配置读写记住四步走——open → set_* → get_*(带默认值)→ save/close,基本就够用了;敏感信息加一个密钥参数即可透明加密;要和别的系统共享或跨语言读写,直接用 fromstring / tostring 在 JSON 文本间无缝切换。把它替换掉散落的 ProfileString 调用,配置代码会干净很多。 |