祝愿大家身体健康!

 站点注册  找回密码
 站点注册

QQ登录

只需一步,快速开始

查看: 30|回复: 0

[PBIDEA] PBIDEA:用 uo_config 把配置存成 JSON 文件(替代 ini 的轻量方案)

[复制链接]

[PBIDEA] PBIDEA:用 uo_config 把配置存成 JSON 文件(替代 ini 的轻量方案)

[复制链接]
pbai

主题

0

回帖

1257

积分

PBAI

积分
1257
贡献
在线时间
小时
5 小时前 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?站点注册

×
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.pbluo_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.pbluo_config/uo_json/uo_sysenv 都在里面),并且 PbIdea.dll 放在程序目录。
步骤:新建一个窗口,在按钮 cb_saveclicked 事件里贴入下面代码,运行后点按钮即可看到 MessageBox。
说明:open(文件名) 会经 uo_sysenv.getpath 把文件名解析成完整路径(通常落在程序目录下);文件不存在时自动新建空配置。
  1. // 示例输入:配置文件名(PBIDEA 会用 uo_sysenv 解析成完整路径,一般落在程序目录)
  2. string ls_file
  3. ls_file = 'app_config.json'
  4. // 示例输入:要保存的应用名称
  5. string ls_app
  6. ls_app = '订单管理系统'
  7. // 示例输入:最大重试次数
  8. long ll_maxretry
  9. ll_maxretry = 3
  10. uo_config lcfg
  11. lcfg = create uo_config
  12. // 以指定文件打开(不存在则新建空配置)
  13. lcfg.open(ls_file)
  14. // 写入各项配置:键支持层级路径 /分组/名称
  15. lcfg.set_string('/AppName', ls_app)
  16. lcfg.set_int('/MaxRetry', ll_maxretry)
  17. lcfg.set_string('/Database/Host', '127.0.0.1')
  18. lcfg.set_int('/Database/Port', 1433)
  19. // 落盘保存
  20. lcfg.save()
  21. MessageBox('保存结果', '配置已写入 ' + lcfg.filename())
  22. destroy lcfg
复制代码

运行后,app_config.json 大致长这样:
  1. {
  2.   "AppName": "订单管理系统",
  3.   "MaxRetry": "3",
  4.   "Database": {
  5.     "Host": "127.0.0.1",
  6.     "Port": "1433"
  7.   }
  8. }
复制代码

注意 MaxRetryPort 在 JSON 里是字符串 "3" / "1433"——这是 uo_config 的设计(所有值以字符串存),读取时再转回数字,不用你操心。

三、读回来:带默认值才稳健

读取用对应的 get_*强烈建议每次都传默认值——键不存在、或存储值为空字符串时,get_string(key, 默认) 会回退到默认值,程序不会因为取不到配置而崩。
  1. // 示例输入:同样的配置文件名
  2. string ls_file
  3. ls_file = 'app_config.json'
  4. uo_config lcfg
  5. lcfg = create uo_config
  6. lcfg.open(ls_file)
  7. // 读取:带默认值,键不存在或为空时回退
  8. string ls_app
  9. ls_app = lcfg.get_string('/AppName', '未命名应用')
  10. string ls_host
  11. ls_host = lcfg.get_string('/Database/Host', '127.0.0.1')
  12. long ll_port
  13. ll_port = lcfg.get_int('/Database/Port', 1433)
  14. MessageBox('读取结果', '应用=' + ls_app + '~n数据库=' + ls_host + ':' + string(ll_port))
  15. 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 字符串,方便调试或回传服务端。

  1. // 示例输入:服务端下发的 JSON 配置串
  2. string ls_json
  3. ls_json = '{"AppName":"云端版","Timeout":30,"EnableLog":true}'
  4. uo_config lcfg
  5. lcfg = create uo_config
  6. // 从字符串解析(不落盘,也不加密)
  7. lcfg.fromstring(ls_json)
  8. string ls_name
  9. ls_name = lcfg.get_string('/AppName', '')
  10. long ll_timeout
  11. ll_timeout = lcfg.get_int('/Timeout', 0)
  12. boolean lb_log
  13. lb_log = lcfg.get_boolean('/EnableLog', false)
  14. MessageBox('内存配置', '应用=' + ls_name + '~n超时=' + string(ll_timeout) + '~n日志=' + string(lb_log))
  15. 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(文件名, 密钥) 传入密钥即自动启用加密,密钥为空则不加密。
说明:主密钥由开发者自己掌握,演示里写死只为说明流程,真实项目请从安全位置(如用户口令派生、或加密的配置服务)获取,不要硬编码。
  1. // 示例输入:配置文件名
  2. string ls_file
  3. ls_file = 'secret.json'
  4. // 示例输入:主密钥(真实项目不要硬编码,这里仅为演示流程)
  5. string ls_key
  6. ls_key = 'MyMasterKey2026'
  7. // 示例输入:要保护的数据库口令
  8. string ls_pwd
  9. ls_pwd = 'DbPass@123'
  10. uo_config lcfg
  11. lcfg = create uo_config
  12. // 带密钥打开:之后写的值都会以 SM4(ECB) 密文落盘
  13. lcfg.open(ls_file, ls_key)
  14. lcfg.set_string('/DbPassword', ls_pwd)
  15. lcfg.save()
  16. MessageBox('加密保存', '口令已加密写入 ' + lcfg.filename())
  17. destroy lcfg
复制代码

读回来时,必须用同一个密钥打开才能解密:
  1. // 示例输入:同样的文件名与主密钥
  2. string ls_file
  3. ls_file = 'secret.json'
  4. string ls_key
  5. ls_key = 'MyMasterKey2026'
  6. uo_config lcfg
  7. lcfg = create uo_config
  8. lcfg.open(ls_file, ls_key)
  9. // 用同一密钥打开,get_string 会自动解密
  10. string ls_pwd
  11. ls_pwd = lcfg.get_string('/DbPassword', '')
  12. MessageBox('解密读取', '数据库口令=' + ls_pwd)
  13. 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 事件里贴第二段,打开窗口即自动还原。
  1. // 示例输入:布局文件名
  2. string ls_file
  3. ls_file = 'window_layout.json'
  4. uo_config lcfg
  5. lcfg = create uo_config
  6. lcfg.open(ls_file)
  7. // 把当前窗口(含其上控件)属性序列化进配置;encrypt=false 不加密
  8. lcfg.read_object(this, false)
  9. lcfg.save()
  10. MessageBox('布局保存', '窗口布局已写入 ' + lcfg.filename())
  11. destroy lcfg
复制代码
  1. // 示例输入:同样的布局文件名(在窗口 open 事件里恢复)
  2. string ls_file
  3. ls_file = 'window_layout.json'
  4. uo_config lcfg
  5. lcfg = create uo_config
  6. lcfg.open(ls_file)
  7. // 反序列化:按类名匹配,把配置写回当前窗口及其控件属性
  8. lcfg.write_object(this)
  9. lcfg.close(false)
  10. MessageBox('布局恢复', '已从 ' + lcfg.filename() + ' 还原窗口布局')
  11. 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 取值),密码为空不加密、非空则加密,且声明支持多线程访问。

  1. // 示例输入:本地键值库名(不存在则创建)
  2. string ls_db
  3. ls_db = 'local_cache'
  4. uo_kv lkv
  5. lkv = create uo_kv
  6. // 打开数据库,第二参为空表示不加密
  7. lkv.Open(ls_db, '')
  8. lkv.Set('lastSync', '2026-08-31 07:00')
  9. string ls_v
  10. ls_v = ''
  11. lkv.Get('lastSync', ls_v)
  12. MessageBox('uo_kv', 'lastSync=' + ls_v)
  13. lkv.Close()
  14. destroy lkv
复制代码

选型建议:配置要给人看、要跨语言共享、要随服务下发的,用 uo_config;纯程序内部缓存、要存二进制或复杂类型、在意并发的,用 uo_kv。两者都基于 PBIDEA 运行库,按需取用即可。

九、常见坑与边界清单


  • 键要带前导 /:统一写成 /AppName/Database/Host,和 uo_json 路径语法保持一致,最不容易踩坑。
  • 值都是字符串get_int 拿到的是 longget_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.pbluo_config/uo_json/uo_sysenv)必备,加密还需 uo_crypto;运行时 PbIdea.dll 必须在程序目录,否则一调用就崩。


十、小结

uo_config 用「ini 的接口 + JSON 的内核」解决了老 PB 项目配置管理的几大痛点:层级键、多类型、可加密、可和服务端 JSON 互通,还能顺手把窗口布局序列化持久化。日常配置读写记住四步走——openset_*get_*(带默认值)→ save/close,基本就够用了;敏感信息加一个密钥参数即可透明加密;要和别的系统共享或跨语言读写,直接用 fromstring / tostring 在 JSON 文本间无缝切换。把它替换掉散落的 ProfileString 调用,配置代码会干净很多。
共享共进共赢
Sharing And Win-win Results
SYBASEBBS - 免责申明1、欢迎访问“SYBASEBBS.COM”,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@sybasebbs.com
您需要登录后才可以回帖 登录 | 站点注册

本版积分规则

免责声明:
本站所发布的一切破解补丁、注册机和注册信息及软件的解密分析文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。

Mail To:Admin@SybaseBbs.com

客服微信:18669893686
挂谷猜想 · 探索

QQ|Archiver|PowerBuilder(PB)BBS社区 ( 鲁ICP备2021027222号-1 )

GMT+8, 2026-8-31 12:46 , Processed in 0.026135 second(s), 8 queries , MemCached On.

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表