我有一个需要更改审核的数据库应用程序。我希望在数据库级别实现这一点,这样我就不必解析查询以查看正在更改的字段或将日志记录例程添加到现有代码中。相反,我想在DB类级别添加必要的审计代码。
我希望能够发出UPDATE
查询,然后在此之后发出另一个查询以查看更改了哪些数据。
如果运行查询UPDATE customers SET cus_tel = '45678', cus_name = 'Mary', cus_city = 'Cape Town' WHERE cus_id = 123;
,则更改检测查询将返回如下内容:
------------------------------------------
| Field | PK | Old Value | New Value |
==========================================
| cus_tel | 123 | 12345 | 45678 |
| cus_name | 123 | John | Mary |
------------------------------------------
在这种情况下,我假设cus_city
字段已经 Cape Town
,因此不需要更新。如果查询一次更新多行,PK字段非常有用。
使用此数据,我可以根据需要将更改记录到审核表中。
我正在使用PHP和MySQL / PDO。
修改
我发现this SO question解决了将更改记录到表中的触发器问题 - 几乎完全符合我的要求:
DELIMITER $$
DROP TRIGGER `update_data `$$
CREATE TRIGGER `update_data` AFTER UPDATE on `data_table`
FOR EACH ROW
BEGIN
IF (NEW.field1 != OLD.field1) THEN
INSERT INTO data_tracking
(`data_id` , `field` , `old_value` , `new_value` , `modified` )
VALUES
(NEW.data_id, "field1", OLD.field1, NEW.field1, NOW());
END IF;
IF (NEW.field2 != OLD.field2) THEN
INSERT INTO data_tracking
(`data_id` , `field` , `old_value` , `new_value` , `modified` )
VALUES
(NEW.data_id, "field2", OLD.field2, NEW.field2, NOW());
END IF;
IF (NEW.field3 != OLD.field3) THEN
INSERT INTO data_tracking
(`data_id` , `field` , `old_value` , `new_value` , `modified` )
VALUES
(NEW.data_id, "field3", OLD.field3, NEW.field3, NOW());
END IF;
END$$
DELIMITER ;
但很明显,这只记录了一个具有已定义字段的表。有没有办法“概括”这个触发器,以便它可以应用于任何没有(或最小)修改的任意字段的任意表?
答案 0 :(得分:0)
行。我的解决方案是PHP和MySQL的结合,使其尽可能“透明”地工作。
这些方法存在于Data
包装器类中,该类使用PDO和预处理语句。
使用其他方法的一些解释:
Data::prepareAndExecute ($query, $tokens);
是一种快捷方法,用于准备查询,执行查询,如果有结果,则返回这些结果的关联数组。Data::isSafeDatabaseEntity ($table)
只是检查表名是否符合preg_match ("/^([a-zA-Z0-9_]+)$/", $check);
命令以防止SQL注入。这是因为我不能为字段和表名使用预准备语句。Data::tableInfo ($table);
根据从PDOStatement::getColumnMeta ();
收到的信息返回表格中列的关联数组。Data::getTablePrimaryKey ($table);
使用SHOW INDEX FROM...
查询的结果。应该说这只能用于单场PK。我的Data
课程摘录:
public static function addTableLogging ($table, $ignorecolumns = array ())
{
if (Data::isSafeDatabaseEntity ($table))
{
$update_trigger = "CREATE TRIGGER `{$table}_after_update` AFTER UPDATE ON `{$table}` FOR EACH ROW BEGIN\n";
$insert_trigger = "CREATE TRIGGER `{$table}_after_insert` AFTER INSERT ON `{$table}` FOR EACH ROW BEGIN\n";
$columns = Data::tableInfo ($table);
$pk = Data::getTablePrimaryKey ($table);
foreach ($columns as $column)
{
if (!in_array ($column ['name'], $ignorecolumns))
{
$update_trigger .= " IF (NEW.{$column ['name']} != OLD.{$column ['name']}) THEN
CALL changelog_store ('{$table}', OLD.{$pk}, '{$column ['name']}', OLD.{$column ['name']}, NEW.{$column ['name']});
END IF;\n";
$insert_trigger .= " CALL changelog_store ('{$table}', NEW.{$pk}, '{$column ['name']}', '', NEW.{$column ['name']});\n";
}
}
$update_trigger .= "END";
$insert_trigger .= "END";
self::removeTableLogging ($table);
self::prepareAndExecute ($update_trigger);
self::prepareAndExecute ($insert_trigger);
}
}
public static function removeTableLogging ($table)
{
if (self::isSafeDatabaseEntity ($table))
{
Data::prepareAndExecute ("DROP TRIGGER IF EXISTS `{$table}_after_update`;");
Data::prepareAndExecute ("DROP TRIGGER IF EXISTS `{$table}_after_insert`;");
}
}
public static function refreshLoggingProcedure ()
{
/* -- for logging into MySQL Table:
CREATE TABLE `changelog` (
`change_id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`change_table` VARCHAR(50) NULL DEFAULT NULL,
`change_table_id` VARCHAR(25) NULL DEFAULT NULL,
`change_field` VARCHAR(50) NULL DEFAULT NULL,
`change_old` VARCHAR(255) NULL DEFAULT NULL,
`change_new` VARCHAR(255) NULL DEFAULT NULL,
`change_user` INT(10) UNSIGNED NOT NULL DEFAULT '0',
`change_date` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`change_id`),
INDEX `change_table_id` (`change_table_id`),
INDEX `change_table` (`change_table`, `change_field`)
);
*/
$logquery = "CREATE PROCEDURE `changelog_store`(IN `tab` VARCHAR(50), IN `pkval` INT, IN `fieldn` VARCHAR(50), IN `oldv` TEXT, IN `newv` TEXT)
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
IF ISNULL(@STAFFID) THEN
SET @STAFFID = 0;
END IF;
INSERT INTO `changelog` (change_table, change_table_id, change_field, change_old, change_new, change_date, change_user)
VALUES (tab, pkval, fieldn, oldv, newv, NOW(), @STAFFID);
END";
Data::prepareAndExecute ("DROP PROCEDURE IF EXISTS `changelog_store`;");
Data::prepareAndExecute ($logquery);
}