码迷,mamicode.com
首页 > 其他好文 > 详细

一个通用的通过触发器实现的,可配置的表修改日志解决方案

时间:2019-12-25 15:57:15      阅读:80      评论:0      收藏:0      [点我收藏+]

标签:img   没有   后台   哪些   substr   创建   根据   time   updating   

在MIS系统中,系统审计功能是很重要的一部分,审计的一部分就是记录数据修改日志。记录数据修改日志有很多种实现方案,有通过后台程序实现的,在修改程序中增加日志代码,也有通过数据库实现的,使用触发器来记录修改日志。本方案采用第二种方案。这种方案的优点是无论你通过什么方式修改数据,都会记录下来,极少需要修改代码;缺点是需要应用程序配合,以便能知道是哪个应用系统的账号做的修改。

本方案的核心是,通过一个配置表,配置哪些表需要记录日志、修改哪些列的时候记录日志。然后根据配置信息,为每个表生成用于记录日志的触发器。

一、日志记录表

技术图片

  日志记录表用于配置:

1、需要记录日志的表(table_name)

2、修改哪些列时,需要记录日志(log_cols),列名用逗号分隔。

3、该表的id列。用于在日志中唯一标识一行数据。

4、记录日志时需要额外记录的一些关键列,列名逗号分隔。

下面是个例子:

技术图片

  二、日志表

技术图片

 

 日志表用来记录对数据的修改。

 

三、触发器

触发器是方案的核心。

触发器里可以使用:new.列名和:old.列名来得到修改的列的旧值和新值。但是列名不能用变量,换句话说,:new不能用在动态sql中,因此就不能读取配置表,来动态取每个需要记录日志的列的旧值和新值。一句话说,你不能使用如下的语句:

vsql := select :new.name from dual;
excute immediate vssql into vName ;

只能在触发器中这样写

select :new.name into vName from dual;

在写触发器的时候就要知道要记录哪些列的日志,要写死了。

没有办法,职能采取一种折中的方式:通过配置信息,自动创建触发器。

1、创建触发器的存储过程

 1 create or replace procedure proc_create_log_trigger(
 2   p$table_name varchar2
 3 )
 4 is
 5   v$id_col varchar2(30);
 6   v$logid_exp varchar2(100);
 7   v$log_cols varchar2(1000);
 8   v$key_cols varchar2(1000);
 9   v$tri_sql varchar2(4000);
10   v$idcol_exp_ins nvarchar2(1000);
11   v$idcol_exp nvarchar2(1000);
12   v$kv1_exp nvarchar2(1000);
13   v$kv2_exp nvarchar2(1000);
14   v$oth_kv_exp nvarchar2(1000);
15   v$kv1_exp_ins nvarchar2(1000);
16   v$kv2_exp_ins nvarchar2(1000);
17   v$oth_kv_exp_ins nvarchar2(1000);
18   v$opuser_exp nvarchar2(1000);
19   v$count int ;
20 begin
21   v$id_col := null ;
22   select cfg.id_col, cfg.log_cols, cfg.key_cols into v$id_col,v$log_cols,v$key_cols
23     from phs_data_log_cfg cfg
24     where cfg.table_name = p$table_name;
25   if v$id_col is null then
26     return ;
27   end if ;
28 
29   v$tri_sql := create or replace trigger tr_ ||p$table_name || _log  ||
30                before delete or insert or update ||
31                on  || p$table_name ||  ||
32                for each row ||
33                declare ||
34                v$opuser varchar2(100);||
35                begin ;
36 
37   v$logid_exp :=seq_data_log.nextval;
38   v$idcol_exp_ins := :new.||v$id_col;
39   v$idcol_exp := :old.||v$id_col;
40   v$count := 1;
41   v$kv1_exp :=  ;
42   v$kv2_exp :=  ;
43   v$oth_kv_exp := ;
44   v$kv1_exp_ins :=  ;
45   v$kv2_exp_ins :=  ;
46   v$oth_kv_exp_ins := ;
47   v$opuser_exp := v$opuser;
48   v$tri_sql := v$tri_sql || select sys_context(‘‘hcm_context‘‘, ‘‘hcm_user‘‘) into ||v$opuser_exp|| from dual;;
49   for r in(SELECT REGEXP_SUBSTR (replace(v$key_cols, ,‘‘), [^,]+, 1,rownum) as v from dual connect by rownum<=LENGTH(v$key_cols) - LENGTH(regexp_replace(v$key_cols, ,, ‘‘))+1) loop
50     if v$count = 1 then 
51       v$kv1_exp := :old.||r.v ; 
52       v$kv1_exp_ins := :new.||r.v ; 
53     end if ;
54     if v$count = 2 then 
55       v$kv2_exp := :old.||r.v ; 
56       v$kv2_exp_ins := :new.||r.v ;
57     end if ;
58     if v$count > 2 then 
59       v$oth_kv_exp := v$oth_kv_exp ||‘‘‘‘||r.v||:‘‘||:old.||r.v||||‘‘;‘‘‘; 
60       v$oth_kv_exp_ins := v$oth_kv_exp_ins ||‘‘‘‘||r.v||:‘‘||:new.||r.v||||‘‘;‘‘‘;
61     end if ;
62     v$count := v$count + 1;
63   end loop;
64 
65   if v$kv1_exp =   then v$kv1_exp := NULL; end if;
66   if v$kv2_exp =   then v$kv2_exp := NULL; end if;
67   if v$oth_kv_exp =   then v$oth_kv_exp := NULL; end if;
68   if v$kv1_exp_ins =   then v$kv1_exp_ins := NULL; end if;
69   if v$kv2_exp_ins =   then v$kv2_exp_ins := NULL; end if;
70   if v$oth_kv_exp_ins =   then v$oth_kv_exp_ins := NULL; end if;
71 
72   v$tri_sql := v$tri_sql ||
73   if inserting then ||
74     insert into phs_data_upd_log( op_code,id,  data_id, table_name, kv1, kv2, oth_kv,op_user, op_time )||
75       values( ‘‘insert‘‘,||v$logid_exp||,||v$idcol_exp_ins||,‘‘‘||p$table_name||‘‘‘,||v$kv1_exp_ins||,||v$kv2_exp_ins ||,||v$oth_kv_exp_ins||,||v$opuser_exp||, sysdate);||
76   elsif deleting then ||
77     insert into phs_data_upd_log( op_code, id, data_id, table_name, kv1, kv2, oth_kv,op_user, op_time )||
78       values( ‘‘delete‘‘,||v$logid_exp||,||v$idcol_exp||,‘‘‘||p$table_name||‘‘‘,||v$kv1_exp||,||v$kv2_exp ||,||v$oth_kv_exp||,||v$opuser_exp||, sysdate);;
79 
80   v$tri_sql := v$tri_sql ||else ;
81   for r in(SELECT REGEXP_SUBSTR (replace(v$log_cols, ,‘‘), [^,]+, 1,rownum) as v from dual connect by rownum<=LENGTH(v$log_cols) - LENGTH(regexp_replace(v$log_cols, ,, ‘‘))+1) loop
82     v$tri_sql := v$tri_sql ||
83     if updating(‘‘‘||r.v||‘‘‘) and (:new.||r.v|| is not null or :old.||r.v|| is not null) and :new.||r.v||<>:old.||r.v|| then ||
84     insert into phs_data_upd_log( op_code, id, data_id, table_name, col, old_value, new_value, kv1, kv2, oth_kv,op_user, op_time )||
85      values( ‘‘update‘‘,||v$logid_exp||,||v$idcol_exp||,‘‘‘||p$table_name||‘‘‘,‘‘‘||r.v||‘‘‘,:old.||r.v||,:new.||r.v||,||v$kv1_exp||,||v$kv2_exp ||,||v$oth_kv_exp||,||v$opuser_exp||, sysdate);||
86     end if;;
87   end loop;
88   
89   v$tri_sql := v$tri_sql || end if;;
90   v$tri_sql := v$tri_sql || end;;
91 
92   --execute immediate v$tri_sql;
93   insert into tmp_3( script ) values(v$tri_sql) ;
94 end ;

这个存储过程的核心就是,读取某个表的日志配置数据,形成创建用于记录日志的触发器的脚本。

执行以下语句,就可以创建触发器:

1 begin
2   -- Call the procedure
3   proc_create_log_trigger(phd_pe_sjxmlr);
4 end;

下面是自动创建的一个触发器的脚本:

  1 create or replace trigger tr_phd_pe_sjxmlr_log
  2   before delete or insert or update on phd_pe_sjxmlr
  3   for each row
  4 declare
  5   v$opuser varchar2(100);
  6 begin
  7   select sys_context(hcm_context, hcm_user) into v$opuser from dual;
  8   if inserting then
  9     insert into phs_data_upd_log
 10       (op_code,
 11        id,
 12        data_id,
 13        table_name,
 14        kv1,
 15        kv2,
 16        oth_kv,
 17        op_user,
 18        op_time)
 19     values
 20       (insert,
 21        seq_data_log.nextval,
 22        :new.h_guid,
 23        phd_pe_sjxmlr,
 24        :new.col_id,
 25        :new.h_qjbh,
 26        h_khdxbh: || :new.h_khdxbh || ;,
 27        v$opuser,
 28        sysdate);
 29   elsif deleting then
 30     insert into phs_data_upd_log
 31       (op_code,
 32        id,
 33        data_id,
 34        table_name,
 35        kv1,
 36        kv2,
 37        oth_kv,
 38        op_user,
 39        op_time)
 40     values
 41       (delete,
 42        seq_data_log.nextval,
 43        :old.h_guid,
 44        phd_pe_sjxmlr,
 45        :old.col_id,
 46        :old.h_qjbh,
 47        h_khdxbh: || :old.h_khdxbh || ;,
 48        v$opuser,
 49        sysdate);
 50   else
 51     if updating(col_id) and
 52        (:new.col_id is not null or :old.col_id is not null) and
 53        :new.col_id <> :old.col_id then
 54       insert into phs_data_upd_log
 55         (op_code,
 56          id,
 57          data_id,
 58          table_name,
 59          col,
 60          old_value,
 61          new_value,
 62          kv1,
 63          kv2,
 64          oth_kv,
 65          op_user,
 66          op_time)
 67       values
 68         (update,
 69          seq_data_log.nextval,
 70          :old.h_guid,
 71          phd_pe_sjxmlr,
 72          col_id,
 73          :old.col_id,
 74          :new.col_id,
 75          :old.col_id,
 76          :old.h_qjbh,
 77          h_khdxbh: || :old.h_khdxbh || ;,
 78          v$opuser,
 79          sysdate);
 80     end if;
 81     if updating(h_value) and
 82        (:new.h_value is not null or :old.h_value is not null) and
 83        :new.h_value <> :old.h_value then
 84       insert into phs_data_upd_log
 85         (op_code,
 86          id,
 87          data_id,
 88          table_name,
 89          col,
 90          old_value,
 91          new_value,
 92          kv1,
 93          kv2,
 94          oth_kv,
 95          op_user,
 96          op_time)
 97       values
 98         (update,
 99          seq_data_log.nextval,
100          :old.h_guid,
101          phd_pe_sjxmlr,
102          h_value,
103          :old.h_value,
104          :new.h_value,
105          :old.col_id,
106          :old.h_qjbh,
107          h_khdxbh: || :old.h_khdxbh || ;,
108          v$opuser,
109          sysdate);
110     end if;
111     if updating(h_qjbh) and
112        (:new.h_qjbh is not null or :old.h_qjbh is not null) and
113        :new.h_qjbh <> :old.h_qjbh then
114       insert into phs_data_upd_log
115         (op_code,
116          id,
117          data_id,
118          table_name,
119          col,
120          old_value,
121          new_value,
122          kv1,
123          kv2,
124          oth_kv,
125          op_user,
126          op_time)
127       values
128         (update,
129          seq_data_log.nextval,
130          :old.h_guid,
131          phd_pe_sjxmlr,
132          h_qjbh,
133          :old.h_qjbh,
134          :new.h_qjbh,
135          :old.col_id,
136          :old.h_qjbh,
137          h_khdxbh: || :old.h_khdxbh || ;,
138          v$opuser,
139          sysdate);
140     end if;
141     if updating(h_khdxbh) and
142        (:new.h_khdxbh is not null or :old.h_khdxbh is not null) and
143        :new.h_khdxbh <> :old.h_khdxbh then
144       insert into phs_data_upd_log
145         (op_code,
146          id,
147          data_id,
148          table_name,
149          col,
150          old_value,
151          new_value,
152          kv1,
153          kv2,
154          oth_kv,
155          op_user,
156          op_time)
157       values
158         (update,
159          seq_data_log.nextval,
160          :old.h_guid,
161          phd_pe_sjxmlr,
162          h_khdxbh,
163          :old.h_khdxbh,
164          :new.h_khdxbh,
165          :old.col_id,
166          :old.h_qjbh,
167          h_khdxbh: || :old.h_khdxbh || ;,
168          v$opuser,
169          sysdate);
170     end if;
171   end if;
172 end;

2、在触发器中得到mis系统的账号

在日志中,需要记录操作用户,这个用户是业务系统的用户而不是数库的账号。在触发器中如何能得到应用系统的用户呢。

可以参见另一篇文章:利用oracle context 向 oracle 传值 https://www.cnblogs.com/senline/p/10345006.html

上面的触发器使用如下代码得到业务系统的用户账号:

1 select sys_context(hcm_context, hcm_user) into v$opuser from dual;

前提是,业务系统在执行修改代码时,要先执行以下语句,将账号写入到数据库session中:

1 proc_hcm_context( hcm_context, hcm_user, 9999);
proc_hcm_context 是个存储过程。

四、日志的例子
我们执行以下sql语句,然后查看日志记录的数据:
1 update phd_pe_sjxmlr set h_value=0.000001 where h_guid = 37E10088B5C0459C10EAF12F34962D17;

查看日志:

1 SELECT * FROM phs_data_upd_log ;

技术图片

 

 五、总结

以上给出了一个通过触发器记录数据修改日志的一种通用解决方案。

核心是,可配置,并且根据配置信息,自动创建触发器,通过触发器记录修改日志。业务系统的账号通过oracle session context 从业务系统传到oracle服务器,通过触发器拿到它。

这种方案有点是对原有系统改动比较小,很容易扩展,使用面很广。

一个通用的通过触发器实现的,可配置的表修改日志解决方案

标签:img   没有   后台   哪些   substr   创建   根据   time   updating   

原文地址:https://www.cnblogs.com/senline/p/configurable_datalog_by_trigger.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!