基础数据修改的日志很重要,常常有重要的基础数据被改了导致问题,然后还无据可查。所以需要所有基础数据修改有日志记录。
检验的C#ORM层已经实现了对基础表的增、删、改自动记录日志。增加就记录增加的JSON日志,修改就把修改前面表JSON记录,删除就把删除的JSON记住。
如下图:可以做表日志查询
由于我们是M库,有些写的基础维护页面有M对象保存、修改和删除的。M层就需要一个存日志的方法,目标就是让表修改可查询。新增需要记录新增的数据是什么。修改需要记录从什么数据修改成什么。删除需要知道删除了什么数据。
最常规的方式就是提供一个日志方法,让业务实现自己传要记录的日志。这样的毛病是记录的日志串格式不统一,难以做通用查询。然后是业务实现M还得在逻辑里时刻考虑嵌入记录日志逻辑,日志代码侵入性太强了。
如下图方式(因为为了日志影响了正常逻辑的结构,分步容易漏):
为此需要实现两个目标:
1.业务不需要关注日志组串细节。
2.业务不需要因为修改这种操作在改之前先准备好修改前日志串,改之后在准备改之后日志串,删除也同理。这种行为破坏了业务的思路。
设计的日志数据格式:
{
"Sql": "",
"OldData": "[{\"RowID\":113,\"Code\":\"dhcc\",\"CName\":\"dhcc\",\"Password\":\"\",\"HospitalDR\":\"\",\"LanguageDR\":13,\"SecurityType\":\"0\",\"PIN\":\"\",\"Remark\":\"我是超级用户66253,42806\",\"Sequence\":0,\"Active\":1,\"PositionTitleDR\":\"\",\"UserType\":\"LIS\",\"UKeyNo\":\"\",\"SignImage\":\"\",\"Theme\":\"\",\"MenuModel\":\"2\",\"PermissionDR\":8}]",
"NewData": "[{\"RowID\":\"113\",\"Code\":\"dhcc\",\"CName\":\"dhcc\",\"Password\":\"\",\"HospitalDR\":\"\",\"LanguageDR\":13,\"SecurityType\":\"0\",\"PIN\":\"\",\"Remark\":\"我是超级用户66253,42864\",\"Sequence\":0,\"Active\":1,\"PositionTitleDR\":\"\",\"UserType\":\"LIS\",\"UKeyNo\":\"\",\"SignImage\":\"\",\"Theme\":\"\",\"MenuModel\":\"2\",\"PermissionDR\":8}]"
}
首先M修改表的%New和%OpenId的对象可以通过$CLASSNAME()得到表名。那么就可以改造表对象调%Save()的保存方式。把表对象调用用过LogSave方法,方法内部解析表名,对于插入表的操作,就把对象值组装好JSON。对于更新表的操作就在%Save()之前查询表里的这条数据组装好老数据JOSN,然后把当前对象组装好新数据JSON。对于删除的数据提供LogDelete方法删除,该方法组在删除前装好删除老数据JOSN。
实现:
/// 带日志的删除
/// TableClassName:表的类名
/// RowID:要删除的RowID
/// Session:会话串,可以不给
/// ClientIPAddress:IP,不给会自动获取
/// w ##Class(LIS.WS.DHCLISServiceBase).LogDelete()
ClassMethod LogDelete(TableClassName, RowID, Session, ClientIPAddress)
{
s TableClassName=$g(TableClassName)
s Session=$g(Session)
s ClientIPAddress=$g(ClientIPAddress)
//得到IP
i '$l(ClientIPAddress) s ClientIPAddress=##Class(LIS.Util.Common).GetClientIP()
s LogMain =##class(%DynamicObject).%New()
s LogMain.Sql=""
s Action="D"
s OldJson=..GetTableRowLogJson(TableClassName,RowID,"")
s LogMain.OldData=OldJson.%ToJSON()
s LogMain.NewData=""
//删除数据
s @("ret=##Class("_TableClassName_").%DeleteId("_RowID_")")
s retDo=@"ret"
i $SYSTEM.Status.IsOK(retDo) d
.//得到表主键
.s TableCodeI=##Class(LIS.Util.Common).IndexData($p(TableClassName,".",2))
.s TableDR=$o(^dbo.SYSTableI("IndexCode",TableCodeI,""))
.//没有表就插入
.i '$l(TableDR) d
..s objTabel=##Class(dbo.SYSTable).%New()
..s objTabel.CName=$p(TableClassName,".",2)
..s objTabel.Code=$p(TableClassName,".",2)
..s objTabel.Description=$p(TableClassName,".",2)
..s objTabel.ParentName=""
..s objTabel.Sequence=999
..s retTable=objTabel.%Save()
..s TableDR=objTabel.RowID
.//保存日志
.s objLog=##Class(dbo.SYSTableLog).%New()
.s objLog.ActData=LogMain.%ToJSON()
.s objLog.Action=Action
.s objLog.AddDate=$zd($h,8)
.s objLog.AddTime=$p($h,",",2)
.s objLog.AddUserDR=$p(Session,"^",1)
.s objLog.ClientIPAddress=ClientIPAddress
.s objLog.RecordID=RowID
.s objLog.Remark="M"
.s objLog.TableDR=TableDR
.s retLog=objLog.%Save()
q retDo
}
/// 带日志的保存
/// TableObj:通过%OpenId或%New的表对象
/// Session:会话串,可以不给
/// ClientIPAddress:IP,不给会自动获取
/// w ##Class(LIS.WS.DHCLISServiceBase).LogSave()
ClassMethod LogSave(TableObj, Session, ClientIPAddress)
{
s TableObj=$g(TableObj)
s Session=$g(Session)
s ClientIPAddress=$g(ClientIPAddress)
//得到IP
i '$l(ClientIPAddress) s ClientIPAddress=##Class(LIS.Util.Common).GetClientIP()
s LogMain =##class(%DynamicObject).%New()
s LogMain.Sql=""
s Action=""
s ObjName=""
s RowID=""
//更新表数据
i $l(TableObj.RowID) d
.s Action="U"
.s ObjName=$CLASSNAME(TableObj)
.s OldJson=..GetTableRowLogJson(ObjName,TableObj.RowID,"")
.s NewJson=..GetTableRowLogJson(ObjName,"",TableObj)
.s LogMain.OldData=OldJson.%ToJSON()
.s LogMain.NewData=NewJson.%ToJSON()
//插入表数据
e d
.s Action="I"
.s ObjName=$CLASSNAME(TableObj)
.s NewJson=..GetTableRowLogJson(ObjName,"",TableObj)
.s LogMain.OldData=""
.s LogMain.NewData=NewJson.%ToJSON()
//执行数据保存
s ret=TableObj.%Save()
s RowID=TableObj.RowID
i $SYSTEM.Status.IsOK(ret) d
.//得到表主键
.s TableCodeI=##Class(LIS.Util.Common).IndexData($p(ObjName,".",2))
.s TableDR=$o(^dbo.SYSTableI("IndexCode",TableCodeI,""))
.//没有表就插入
.i '$l(TableDR) d
..s objTabel=##Class(dbo.SYSTable).%New()
..s objTabel.CName=$p(ObjName,".",2)
..s objTabel.Code=$p(ObjName,".",2)
..s objTabel.Description=$p(ObjName,".",2)
..s objTabel.ParentName=""
..s objTabel.Sequence=999
..s retTable=objTabel.%Save()
..s TableDR=objTabel.RowID
.//保存日志
.s objLog=##Class(dbo.SYSTableLog).%New()
.s objLog.ActData=LogMain.%ToJSON()
.s objLog.Action=Action
.s objLog.AddDate=$zd($h,8)
.s objLog.AddTime=$p($h,",",2)
.s objLog.AddUserDR=$p(Session,"^",1)
.s objLog.ClientIPAddress=ClientIPAddress
.s objLog.RecordID=RowID
.s objLog.Remark="M"
.s objLog.TableDR=TableDR
.s retLog=objLog.%Save()
q ret
}
/// 得到一行数据的JSON对象
/// w ##Class(LIS.WS.DHCLISServiceBase).GetTableRowJson("dbo.SYSUser","113").%ToJSON()
/// TableName:表类名
/// RowID:数据主键,和TableObj二传1
/// TableObj:数据对象,和RowID二传1
ClassMethod GetTableRowLogJson(TableName, RowID, TableObj)
{
s TableName=$g(TableName)
s RowID=$g(RowID)
s TableObj=$g(TableObj)
s retArr=[]
s @"RetJsonObj =##class(%DynamicObject).%New()"
i $l(RowID) d
.//更新的这里不能用OpenId,用他取的数据是改了之后的对象
.//s @("Obj=##class("_TableName_").%OpenId("_RowID_")")
.s @("Obj=$g(^"_TableName_"D("_RowID_"))")
e d
.s @("Obj")=TableObj
s rset = ##class(%ResultSet).%New()
d rset.Prepare("select COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='dbo' AND REPLACE(TABLE_NAME,'_','')='"_$REPLACE(TableName,"dbo.","")_"' Order by ORDINAL_POSITION")
s exeret=rset.Execute()
s colCount=rset.GetColumnCount()
s dealNum=0
s PropertyMap=""
s Index=0
While(rset.Next())
{
s colField=rset.GetColumnName(1)
s ColValue=rset.GetDataByName(colField)
s Index=Index+1
i $l(RowID) d
.i Index=1 d
..s @("RetJsonObj."_ColValue_"="_RowID)
.e d
..s @("RetJsonObj."_ColValue_"=$lg(Obj,"_Index_")")
e d
.s @("RetJsonObj."_ColValue_"=Obj."_ColValue)
}
d retArr.%Push(@"RetJsonObj")
q retArr
}
使用示例:
/// 此类写带日志增删改的写法
Class Standard.StdLogSave Extends %RegisteredObject
{
/// 测试带日志保存方法
/// w ##Class(Standard.StdLogSave).LogSaveTestMTHD("","","","","","","","","","","","","","","")
ClassMethod LogSaveTestMTHD(P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, Sessions, Output RowCount As %String) As %String
{
//托管事务保存鉴定结果
q ##Class(LIS.WS.DHCLISServiceBase).DeclarativeTrans("Standard.StdLogSave","LogSaveTestDo",P0,P0,P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, Sessions)
}
/// 测试带日志保存方法
/// w ##Class(Standard.StdLogSave).LogSaveTest()
ClassMethod LogSaveTestDo(P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, Sessions, Output RowCount As %String) As %String
{
//更新dhcc
s objLog=##Class(dbo.SYSUser).%OpenId(113,0)
s objLog.Remark="我是超级用户"_$h
//调用带日志的保存
s sc=##Class(LIS.WS.DHCLISServiceBase).LogSave(objLog)
i ('$SYSTEM.Status.IsOK(sc)) d
.THROW ##class(%Exception.SystemException).%New("事务委托","D",,"-1^保存失败:"_$SYSTEM.Status.GetErrorText(sc))
h 1
//插入新用户
s objNew=##Class(dbo.SYSUser).%New()
s objNew.Active=1
s objNew.CName="ZLZ"
s objNew.Code="ZLZTT"
s objNew.HospitalDR=1
s objNew.Sequence=1
//调用带日志的保存
s sc=##Class(LIS.WS.DHCLISServiceBase).LogSave(objNew)
i ('$SYSTEM.Status.IsOK(sc)) d
.THROW ##class(%Exception.SystemException).%New("事务委托","D",,"-1^保存失败:"_$SYSTEM.Status.GetErrorText(sc))
h 1
//删除插入的用户
s sc=##Class(LIS.WS.DHCLISServiceBase).LogDelete("dbo.SYSUser",objNew.RowID)
i ('$SYSTEM.Status.IsOK(sc)) d
.THROW ##class(%Exception.SystemException).%New("事务委托","D",,"-1^保存失败:"_$SYSTEM.Status.GetErrorText(sc))
q ""
}
}
遵循代理者模式和低耦合原则,分析给M要做表日志的伙伴。