当前位置: 首页 > news >正文

# 最野AOP实现:他连AOP这个词都没听过

最野AOP实现:他连AOP这个词都没听过

非科班野生程序员,深耕政务信息化20年。2008年,接了个某市社保三级审核的需求。不知道AOP是什么概念,但用PowerBuilder干了一件现在回头看就是AOP的事——不改老业务代码一行,在DataWindow的Update环节把数据"切走",审核完了再写回来。这篇文章讲的就是这个故事。最后感谢豆包、智谱、OpenCode,决策是我做的,代码是我搓的,文字是他们总结的。


背景:不改代码加审核

2008年,某市社保系统要上三级审核。

老系统几百个窗口、上千个DataWindow,业务逻辑都在里面。正常做法是一个一个窗口改——每个窗口加审核状态字段、加审核流程、加审核页面。这样干不仅成本高得吓人,更致命的是质量差——几百个窗口逐个改,改漏一个就是线上事故。

翻代码?翻不起。改业务表?改不起。更关键的是,逐个改质量没法保证

唯一的活路:不改老业务系统的一行代码,从外面"切"进去。

PowerBuilder的DataWindow

先交代一下PB的DataWindow。

PB的DataWindow是整个框架的核心——一个可视控件,绑定一条SQL,自动渲染成表格/表单,用户编辑完后调dw_1.Update(),PB自动根据行的状态生成INSERT/UPDATE/DELETE语句,直接提交到数据库。

用户编辑 → DataWindow → dw_1.Update() → 数据库

行状态是PB自动维护的:

  • New!— 新插入的行
  • DataModified!— 修改过的行
  • 删除缓冲区— 用户删除的行

Update()时,PB自动根据状态生成对应的SQL。

我的做法:劫持Update

关键一步:重写uo_datawindowupdate事件,不调用父类的Update(),而是自己遍历行状态,拼SQL。

用户编辑 → DataWindow → update_user()(我重写的)→ 拼SQL → 写到审核中间表

核心代码逻辑

public function integer update_user(string as_exsql); // 获取表名 ls_Table = this.Describe("DataWindow.Table.UpdateTable") // 获取列信息 ll_ColumnsCount = long(this.Describe("datawindow.Column.Count")) for ll_i = 1 to ll_ColumnsCount // 只处理标记为update的列 ls_temp = this.Describe("#"+String(ll_i)+".update") if lower(ls_temp) = "yes" then ls_ColumnName[ll_m] = this.Describe("#"+String(ll_i)+".name") ls_ColumnType[ll_m] = this.Describe(ls_ColumnName[ll_m]+".coltype") ll_ColumnRow[ll_m] = ll_i end if // 记录主键列 ls_temp = this.Describe("#"+String(ll_i)+".key") if lower(ls_temp) = "yes" then ls_Key[ll_n] = this.Describe("#"+String(ll_i)+".name") ls_KeyType[ll_n] = this.Describe(ls_Key[ll_n]+".coltype") ll_KeyRow[ll_n] = ll_i end if next

然后按状态拼SQL:

// 遍历每一行,根据状态拼不同的SQL for ll_i = 1 to ll_RowCount l_status = this.GetItemStatus(ll_i, 0, Primary!) if l_status = New! or l_status = NewModified! then // 新增 → 拼 INSERT INTO ... VALUES (...) ls_sql = ls_sql + " Insert into " + ls_Table + " (" // ...遍历列,拼列名和值 // 按类型处理:string加引号,number直接写,date用to_date() else if l_status = DataModified! then // 修改 → 拼 UPDATE ... SET ... WHERE ... // 只更新变更过的列 for ll_j = 1 to UpperBound(ll_ColumnRow) l_status_Modified[ll_j] = this.GetItemStatus(ll_i, ls_ColumnName[ll_j], Primary!) next // 只拼状态为DataModified!的列 end if next // 删除区的行 → 拼 DELETE FROM ... WHERE ... for ll_i = 1 to ll_deleteRowCount ls_sql = ls_sql + "Delete from " + ls_Table + " where " // 用主键拼WHERE条件 next

最后用PL/SQL块包起来批量执行:

// 每 il_pageCount 条拼一个 begin ... end; 块 for ll_i = 1 to ll_times lsa_sql[ll_i] = "begin ~r~n" for ll_j = 1 to ll_a lsa_sql[ll_i] = lsa_sql[ll_i] + ids1.object.a[(ll_i-1)*il_Count+ll_j] next lsa_sql[ll_i] = lsa_sql[ll_i] + " end ;~r~n" next this.resetupdate() // 重置行状态 return 1

切到哪里去

数据不写到原来的业务表,而是写到审核中间表

审核中间表的结构和业务表一模一样,外加几个审核字段:

  • 审核状态(待审核 / 已通过 / 已退回)
  • 审核级别(一级 / 二级 / 三级)
  • 审核人、审核时间、审核意见

业务表叫kc22,审核表叫kc22_auditupdate_user()把SQL里的表名从kc22替换成kc22_audit,数据就"切"到了审核表。

审核通过后,再从审核表写回业务表。老业务代码一行没改。

现在回头看:这就是AOP

AOP概念我的实现
切面(Aspect)uo_datawindow重写的update事件
切入点(Join Point)dw_1.Update()这个调用
通知(Advice)拦截后拼SQL写到审核表,而不是原表
织入(Weave)继承uo_datawindow,所有窗口用子类代替父类

不碰业务逻辑,在"保存"这个动作的前后插入自己的逻辑。这就是AOP。

但2008年,我不知道AOP是什么。我就是觉得:改老代码太贵了,我付不起那个成本,只能从外面切。

后来的影响

这个决定影响了我后面十几年的框架设计。

Row的状态机

PB版用GetItemStatus()检查行状态,是PB内置的。后来做Java版,PB不在了,我就自己实现状态机

// Java版 Row.java — 2008年PB思路的延续publicvoidsetItemValue(Objectkey,Objectvalue){if(map.get(key)==null){if(_t==0)_t=1;// 新增}else{if(_t==0){_t=3;// 修改_o.put(key,map.get(key));// 保存原始值}}map.put(key.toString(),value);}

_t=0无变化、_t=1新增、_t=3修改——和PB的NotModified!New!DataModified!一一对应。_o存原始值,对应PB里 DataModified 时能拿到旧值的能力。

RowSet的三区

List<Row>primary=newArrayList<>();// 主缓冲区(正常数据)List<Row>delete=newArrayList<>();// 删除缓冲区List<Row>filter=newArrayList<>();// 过滤缓冲区

PB的DataWindow有三个缓冲区:Primary、Delete、Filter。Java版用三个List模拟。完全对应。

DataStore的 save/update/insert/delete

Java版的DBUtil.updateWithDataStore()insertWithDataStore()deleteWithDataStore(),遍历RowSet,根据每行的状态自动生成INSERT/UPDATE/DELETE——和2008年PB版update_user()的逻辑一模一样。

算不算原创

"在Update环节拦截数据变更"这个想法,PB社区里有人做过类似的。但把行状态遍历、SQL自动拼接、审核中间表切走、审核通过后写回这一整套做下来,而且不改老业务代码一行——至少在我当时的圈子里,没人这么干过。

更重要的是,这个思路后来成了我整个框架的基因。从PB到Java到C#到JavaScript,DataStore/RowSet/Row这套东西一脉相承,根都在2008年那个含着泪干的项目里。

小结

有时候最好的架构决策,不是因为学了什么理论,而是因为付不起改代码的成本

几百个窗口不能动。

逼出了"不改代码加审核"的方案,逼出了自研状态机,逼出了DataStore体系,逼出了后来十几年一脉相承的框架设计。

2014年接某海关C#项目,把这套东西从Java搬到C#,看起来是"狂想"。其实根在2008年就已经埋下了。


你见过最野的AOP实现是什么?评论区聊聊。

标签:#AOP #PowerBuilder #DataWindow #政务信息化 #状态机 #自研框架 #社保 #三级审核 #拦截器

http://www.jsqmd.com/news/647802/

相关文章:

  • FinBERT金融情感分析:揭秘专业AI如何读懂财经新闻背后的情绪密码
  • 多模态教育不是加摄像头+AI语音!2026奇点大会闭门议程首曝:教育认知神经建模的5层技术穿透路径
  • 文生图技术选型实战指南:2025年工业级应用全景解析
  • 2026年电子商务论文降AI工具推荐:用户行为分析和商业模式部分
  • LVGL9 RLE图片压缩实战:从Flash加载.bin文件到屏幕显示的完整避坑指南
  • 从SVM到凸优化:对偶问题的数学之美
  • 2026年4月北京 GEO 优化服务商榜单:京城五强实力亮相,赋能华北全域增长
  • 【国家级多模态项目避坑指南】:直击长尾场景下跨模态对齐断裂、标签噪声放大、推理延迟飙升三大致命缺陷
  • AI时代工程师的超级进化论
  • 别再一层层传props了!useContext高效状态管理实战
  • uni-app怎么动态生成二维码 uni-app利用插件生成分享码方法【技巧】
  • UART与USART的区别
  • AI时代工程师Superpowers的进化论
  • Python asyncio 异步文件下载实现
  • 如何高效使用Cursor Free VIP:突破AI编程助手限制的完整指南
  • 2025-2026年访客机品牌推荐:五大口碑产品评测对比顶尖访客信息登记混乱 - 品牌推荐
  • # 事务提交时原子写审计日志:commit里调存储过程,业务和日志同生共死
  • C语言实战:两种算法解析行列式计算
  • 被90%团队忽略的模态间语义鸿沟:SITS2026首次公布跨模态对抗样本库(含17类高危攻击向量)
  • 慧源流GEO——EEAT原则在B2B制造行业的实战落地
  • π3:当视觉几何遇见置换等变,如何重塑三维重建的底层逻辑?
  • TVBoxOSC终极指南:如何快速打造全能电视盒子媒体中心
  • Python Flask路由怎么限制方法_methods列表配置仅允许GET或POST限制接口非法请求
  • 2026年TCT亚洲展海外观众增长50% 正在成为全球“走进中国”的第一站——上海
  • 2025-2026年访客机品牌推荐:五大口碑产品评测对比顶尖工厂安全准入繁琐案例 - 品牌推荐
  • Ubuntu 22.04 下,从零构建 Isaac Sim 与 Isaac Lab 一体化机器人开发环境
  • 从单体到微服务:飞控仿真台架构演进之路
  • 如何永久保存微信聊天记录?终极免费工具使用指南
  • 多模态大模型容灾备份策略(NASA级冗余设计白皮书首次公开)
  • 2025-2026年访客机品牌推荐:五大口碑产品评测对比顶尖工厂访客登记繁琐耗时注意事项 - 品牌推荐