为什么要做操作日志?
其实上文也描述了一些,其主要目的就是跟踪到每一个用户在系统的操作行为,如对数据进行查询、新增、编辑或删除甚至是登录等行为。更进一步的理解可以说是对用户使用系统情况的跟踪,对数据的跟踪防止数据意外删除、更改时有所记录,有所依据,以便对数据的还原,从某种程序上可以保护数据的完整性。
系统设计
场景
我们现在有一张表叫Employee:
ID | int |
Name | nvarchar(50) |
Gender | nvarchar(2) |
DateCreated | datetime |
CreateUser | nvarchar(50) |
在aspx页面中可能会有EmployeeEdit.aspx(用来添加或更新Employee信息等操作),EmployeeList.aspx(用来查询或进行删除Employee信息等操作)
好了,现在我们要对Empoyee表操作的信息做一个系统日志,那怎么办?
也许你可以建立多一个表跟Employee表一模一样的,叫做EmployeeLog:
ID | int |
Name | nvarchar(50) |
Gender | nvarchar(2) |
DateCreated | datetime |
CreateUser | nvarchar(50) |
LogCreated | datetime |
OperationType | int |
其中加多了一些附属的信息如LogCreated(日志添加日期)和OperationType(查询、新增、删除、更新)
此时这种情况可能大家在做用户登录日志的时候是一件很常见的事件。
但……问题来了,假如我需要对表EmployeeIncome(员工的收入情况)做日志那怎么办?
好建立多一张表叫EmployeeIncomeLog来记录员工收入情况的操作日志。
假如又需要对表FixedAsset(固定资产)进行日志记录那又怎么办?
好了,大家可能意识到我们这样做不但会造成表数量的增倍,而且大大的增加了工作量和开发时间,对数据库表不易管理等情况。
因此我们需要一个能够通过简单的配置和编写就可以完成以上功能的日志管理
数据库设计
包括三个表,
LogSetting(日志设置)——用来存储配置业务表名、业务名称、主键等
LogSettingDetail(日志设置明细)——用来存储配置业务表需要记录的详细内容,如Employee表中,我们可能需要记录字段Name、Gender等信息。
LogOperation(操作日志)——用来记录用户对各种业务操作的内容情况。
下篇将讨论用代码如何实现日志管理的功能,下面先来几张图:
日志列表:
查看日志内容:
管理系统的操作日志:
管理系统的操作日志如何做成通用的模块一直是个让我头疼的问题,不过看了博客园里的某篇文章后,现在基本解决了。
相关文章链接:《系统操作日志设计》
在开始做之前,必须把两个日志分清楚,那就是普通操作日志和业务操作日志,这两者有何区别?
在我理解,普通操作日志就是单表的操作记录,而业务操作日志则就是一系列的普通操作日志的集合。
打个比方,用户需要购买一样宝贝,已经到了下单那步,下单就是个业务,这个业务背后就是一系列的业务,如:
生成订单 → 生成商品快照 → 发送一条站内信 → 删除购物车里对应宝贝
这样一个下单操作就包含了4部分,可以把这4部分看成是4张表,分别对这4张表进行对应的操作,就实现了业务。
但今天我要讲的不是业务操作日志,因为不同项目的业务不尽相同,所以它无法做成通用模块,而我要讲的,就是普通操作日志。
上面解释了一大段,下面干货就要亮相了,先洗把脸清醒下。
……
首先,哪些地方需要记录操作日志?执行insert、update、delete这3个操作的时候,就需要进行日志,而日志执行的先后顺序如下
insert | 在insert后执行 |
update | 在update前后都要执行,操作前获取操作前数据,操作后获取操作后数据 |
delete | 在delete前执行 |
顺序清楚后,就来看下我写的一份日志操作类吧,第一版随便写写的,重复代码有点多,还未来得及优化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
|
class LOG{ protected $primaryid ; protected $tbid ; protected $tbname ; protected $keys ; protected $values ; /** *
参数说明 *
int $tbid 查询指定表的id *
string $tbname 数据库表名 */ public function insert( $tbid , $tbname ){ global $db ; //查询表注释 $db ->query( 'show
table status where name = "' . $tbname . '"' ); $tb = $db ->fetch(); //插入日志主表 $returnid = $db ->insert(0,
2, 'tb_log' , array ( 'adminid
= ' . $_SESSION [ 'admin' ][ 'id' ], 'type
= 1' , 'tableid
= ' . $tbid , 'tablename
= "' . $tbname . '"' , 'comment
= "' . $tb [ 'Comment' ]. '"' , 'dt
= now()' )); //查询字段注释 $db ->query( 'show
full columns from ' . $tbname ); $tb = $db ->fetchAll(); foreach ( $tb as $v ){ $commentArray [ $v [ 'Field' ]]
= $v [ 'Comment' ]; } //查询所有字段信息,插入日志从表 $rs = $db ->select(0,
1, $tbname , '*' , 'and
tbid = ' . $tbid ); $keys = array_keys ( $rs ); $values = array_values ( $rs ); for ( $i =
0; $i < count ( $keys ); $i ++){ $db ->insert(0,
0, 'tb_log_content' , array ( 'logid
= ' . $returnid , 'tbkey
= "' . $keys [ $i ]. '"' , 'tbvalue
= "' . $values [ $i ]. '"' , 'comment
= "' . $commentArray [ $keys [ $i ]]. '"' )); } } public function updateStart( $tbid , $tbname ){ global $db ; //查询表注释 $db ->query( 'show
table status where name = "' . $tbname . '"' ); $tb = $db ->fetch(); //插入日志主表 $returnid = $db ->insert(0,
2, 'tb_log' , array ( 'adminid
= ' . $_SESSION [ 'admin' ][ 'id' ], 'type
= 2' , 'tableid
= ' . $tbid , 'tablename
= "' . $tbname . '"' , 'comment
= "' . $tb [ 'Comment' ]. '"' , 'dt
= now()' )); //查询修改前数据信息 $rs = $db ->select(0,
1, $tbname , '*' , 'and
tbid = ' . $tbid ); $keys = array_keys ( $rs ); $values = array_values ( $rs ); $this ->primaryid
= $returnid ; $this ->tbid
= $tbid ; $this ->tbname
= $tbname ; $this ->keys
= $keys ; $this ->values
= $values ; } public function updateEnd(){ global $db ; //查询字段注释 $db ->query( 'show
full columns from ' . $this ->tbname); $tb = $db ->fetchAll(); foreach ( $tb as $v ){ $commentArray [ $v [ 'Field' ]]
= $v [ 'Comment' ]; } //查询修改后数据信息 $rs = $db ->select(0,
1, $this ->tbname, '*' , 'and
tbid = ' . $this ->tbid); $currentvalues = array_values ( $rs ); //前后信息进行比较 for ( $i =
0; $i < count ( $currentvalues ); $i ++){ if ( $this ->values[ $i ]
!== $currentvalues [ $i ]){ $db ->insert(0,
0, 'tb_log_content' , array ( 'logid
= ' . $this ->primaryid, 'tbkey
= "' . $this ->keys[ $i ]. '"' , 'tbvalue
= "' . $this ->values[ $i ]. '"' , 'currenttbvalue
= "' . $currentvalues [ $i ]. '"' , 'comment
= "' . $commentArray [ $this ->keys[ $i ]]. '"' )); } } } public function delete ( $tbid , $tbname ){ global $db ; //查询表注释 $db ->query( 'show
table status where name = "' . $tbname . '"' ); $tb = $db ->fetch(); //插入日志主表 $returnid = $db ->insert(0,
2, 'tb_log' , array ( 'adminid
= ' . $_SESSION [ 'admin' ][ 'id' ], 'type
= 3' , 'tableid
= ' . $tbid , 'tablename
= "' . $tbname . '"' , 'comment
= "' . $tb [ 'Comment' ]. '"' , 'dt
= now()' )); //查询字段注释 $db ->query( 'show
full columns from ' . $tbname ); $tb = $db ->fetchAll(); foreach ( $tb as $v ){ $commentArray [ $v [ 'Field' ]]
= $v [ 'Comment' ]; } //查询所有字段信息,插入日志从表 $rs = $db ->select(0,
1, $tbname , '*' , 'and
tbid = ' . $tbid ); $keys = array_keys ( $rs ); $values = array_values ( $rs ); for ( $i =
0; $i < count ( $keys ); $i ++){ $db ->insert(0,
0, 'tb_log_content' , array ( 'logid
= ' . $returnid , 'tbkey
= "' . $keys [ $i ]. '"' , 'tbvalue
= "' . $values [ $i ]. '"' , 'comment
= "' . $commentArray [ $keys [ $i ]]. '"' )); } } } |
使用前,需要引入数据库操作类,这是我之前写的一份,可参考《全新的PDO数据库操作类(仅适用Mysql)》。
引入之后,就可以开始使用了。
select
1
|
$log ->insert(82, 'tb_member' ); |
update
1
2
3
|
$log ->updateStart(82, 'tb_member' ); //中间放更新操作代码 $log ->updateEnd(); |
delete
1
|
$log -> delete (82, 'tb_member' ); |
可以看到,一共只需要两个参数即可,分别是表ID(主键)和表名称。
另外需要强调一点,表注释和字段注释一定要完整,因为记录的信息包含注释,目的就是为了查阅的时候能清楚哪个字段是干什么用的。
下面就看下成品吧
最后把表结构分享下,一共2张表,一张主表一张从表,主表记录操作表及操作人等信息,从表记录操作的表字段信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
--
---------------------------- --
Table structure for `tb_log` --
---------------------------- CREATE TABLE `tb_log`
( `tbid` bigint (20) NOT NULL AUTO_INCREMENT, `adminid` bigint (20) DEFAULT NULL COMMENT '管理员id' , `type`
tinyint(4) DEFAULT '1' COMMENT '操作类型:1新增2修改3删除' , `tableid` bigint (20) DEFAULT NULL , `tablename` varchar (255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '表名' , `comment` varchar (255) COLLATE utf8_unicode_ci DEFAULT NULL , `dt`
datetime DEFAULT NULL , PRIMARY KEY (`tbid`) )
ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8 COLLATE =utf8_unicode_ci; --
---------------------------- --
Table structure for `tb_log_content` --
---------------------------- CREATE TABLE `tb_log_content`
( `tbid` bigint (20) NOT NULL AUTO_INCREMENT, `logid` bigint (20) DEFAULT NULL , `tbkey`
longtext COLLATE utf8_unicode_ci, `tbvalue`
longtext COLLATE utf8_unicode_ci, `currenttbvalue`
longtext COLLATE utf8_unicode_ci, `comment` varchar (255) COLLATE utf8_unicode_ci DEFAULT NULL , PRIMARY KEY (`tbid`) )
ENGINE=InnoDB AUTO_INCREMENT=109 DEFAULT CHARSET=utf8 COLLATE =utf8_unicode_ci; |