一、背景
1.1 我遇到了几个项目,他们的接口服务器崩溃了。 项目上希望尽快恢复服务器。他们的服务器在局域网上运行,他们不能使用git,服务器中有多个命名空间运行不同的服务,而且通常只有一台平台服务器。
1.2 如果消息中有字符流类型的属性,消息搜索页面不支持使用字符流属性进行过滤,因此很难找到想要的消息。
1.3 其他同事可能会更新服务器上的代码,代码中可能有些错误。
2.挑战
2.1 如何快速恢复?
2.2 如何支持字符流属性过滤消息?
2.3 如何在编译类时自动备份?
3.解决方案
1.编译时自动导出为备份文件
首先,我们定义一个名为“SYS.base”的类,它只有一个名为“ CLSBAKPATH”的参数,并设置它的值
Class SYS.Base Extends %RegisteredObject
{
Parameter CLSBAKPATH = "D:\IRIS\CLSBAK" ;
}
ObjectScriptObjectScript
然后,定义一个名为“SYS.Projection”的类,它继承了 base和%Projection.AbstractProjection,添加“Projection Reference As SYS.Projection”,重写类方法“CreateProjection”;
代码如下:
Class SYS.Projection Extends ( %Projection.AbstractProjection , Base)
{
Projection Reference As SYS.Projection ;
ClassMethod CreateProjection(classname As %String , ByRef parameters As %String , modified As %String , qstruct) As %Status
{ w ! s changetime=^oddDEF(classname,63)
s changetime=$zdt(changetime,3)
w "class"_classname_" is modified at "_changetime_" !",!
s CLSBAKPATH=..#CLSBAKPATH
i CLSBAKPATH[ "/" d
.s PL= "/"
e d
.s PL= "\"
s:$e(CLSBAKPATH,*)'=PL CLSBAKPATH=CLSBAKPATH_PL
s sc= $SYSTEM.OBJ.Export(classname_".cls",CLSBAKPATH_classname_$tr(changetime,":-","")_ ".xml" )
q $$$OK
}
}
ObjectScriptObjectScript
执行该方法后,被编译的类将直接导出到目标路径“CLSBAKPATH”,它的名称将类似于“类名20230217130218.xml”
最后,如何使用它?
选择一个要自动备份的类,让它继承“SYS.Projection”。当类被编译时,它会被导出。如果你不修改重新编译它,它会覆盖旧的备份文件。
顺便说一句,我认为关键点是 %Projection.AbstractProjection,有了“CreateProjection”,我们可以做更多,如果你想在生产环境中更新一些代码,并且又不想让人再手动去执行一些目标方法代码,这是个好方法。他需要做的是将xml文件导入studio,并编译它,如果勾选“Compile Imported Items”框,那就不再需要单独编译。也就是导入的时候就会自动编译,编译的时候就自动执行,相当于就是导入后自动执行!
到目前为止,它只备份了一个类文件。如果我们想要备份更多怎么办?
方法来了!简单来说,挂任务!
通常用于备份接口代码和数据。
在本教程中,我们在本地驱动器中备份文件。
首先,我们定义一个名为“Common.SYS”的类
然后添加一个名为“ Export ”的类方法,它是主要方法,它将调用其他功能方法。 A导出类,B导出凭证,C导出GLB。
类看起来像:
Class Common.SYS Extends %RegisteredObject
{
/// Exportclass
ClassMethod ExportAllClassesIndividual(NS As %String = "" )
{
s Drive=..#Drive s currentns= $ZUTIL ( 67 , 6 , $j )
i NS'= "" d
.w:'$d(^["%sys"]CONFIG("Namespaces",NS)) "namespace "_NS_" is not existed,please check it!" ,!
.q:'$d(^[ "%sys"]CONFIG("Namespaces",NS))
.zn NS
.s FilePath=Drive_":/BakFile/"_NS_"/"
.i '##class(%File).DirectoryExists(FilePath) d
..d ##class(%File).CreateDirectoryChain(FilePath)
.s sc= $system .OBJ.ExportAllClassesIndividual(FilePath,"b",.el,"","CDYZone,Sample,User" )
.i sc'=1 d
..w " namespace "_NS_" get an error when export class! "_$System.Status.GetErrorText(sc),!
e d
.s alns=""
.f s alns=$o(^["%sys"]CONFIG("Namespaces",alns)) q:alns="" d
..q:$e(alns,1)="%"
..q:(alns="ENSDEMO")||(alns="ENSEMBLE")||(alns["-")
..q:'$d(^[alns]CacheMsg("Confirm"))
..zn alns
..s FilePath=Drive_":/BakFile/"_alns_"/"
..i '##class(%File).DirectoryExists(FilePath) d
...d ##class(%File).CreateDirectoryChain(FilePath)
..s sc=$system.OBJ.ExportAllClassesIndividual(FilePath,"b",.el,"","Common,SYS" )
..i sc'=1 d
...w " namespace "_alns_" get an error when export class! "_ $System.Status.GetErrorText(sc),!
zn currentns
q $$$OK
}
/// Ens.Config.Credentials
ClassMethod ExportAllCredentials(NS As %String = "" )
{
s Drive=..#Drive
s currentns= $ZUTIL ( 67 , 6 , $j )
i NS'= "" d
. w :' $d (^[ "%sys" ]CONFIG( "Namespaces" ,NS)) "namespace " _NS_ " is not existed,please check it!" ,!
. q :' $d (^[ "%sys" ]CONFIG( "Namespaces" ,NS))
. q :' $d (^[NS]Ens.Conf.CredentialsD)
. zn NS
. s Data=[]
. s FilePath=Drive_ ":/BakFile/" _NS_ "/Ens_Config_Credentials"
.i ' ##class ( %File ).DirectoryExists(FilePath) d
..d ##class ( %File ).CreateDirectoryChain(FilePath)
. s Data=[]
. s CID= ""
.f s CID= $o ( ^Ens .Conf.CredentialsD(CID)) q :CID= "" d
..s obj= ##class (Ens.Config.Credentials). %OpenId (CID)
..q :' $IsObject (obj)
..s data={}
..s data.SystemName=obj.SystemName
..s data.Username=obj.Username
..s data.Password=obj.Password
..d Data. %Push (data)
. d :Data. %Size ()> 0 ..SaveFile (FilePath,Data. %ToJSON ())
e d
. ;w currentns,!
. s alns= ""
.f s alns= $o (^[ "%sys" ]CONFIG( "Namespaces" ,alns)) q :alns= "" d
..q : $e (alns, 1 )= "%"
..q :(alns= "ENSDEMO" )||(alns= "ENSEMBLE" )||(alns[ "-" )
..q :' $d (^[alns]CacheMsg( "Confirm" ))
..q :' $d (^[alns]Ens.Conf.CredentialsD)
..zn alns
..s FilePath=Drive_ ":/BakFile/" _alns_ "/Ens_Config_Credentials"
..i ' ##class ( %File ).DirectoryExists(FilePath) d
. ..d ##class ( %File ).CreateDirectoryChain(FilePath)
..s Data=[]
..s CID= ""
..f s CID= $o ( ^Ens .Conf.CredentialsD(CID)) q :CID= "" d
. ..s obj= ##class (Ens.Config.Credentials). %OpenId (CID)
. ..q :' $IsObject (obj)
. ..s data={}
. ..s data.SystemName=obj.SystemName
. ..s data.Username=obj.Username
. ..s data.Password=obj.Password
. ..d Data. %Push (data)
..d :Data. %Size ()> 0 ..SaveFile (FilePath,Data. %ToJSON ())
zn currentns
q $$$OK
}
ClassMethod SaveFile(FilePath, Data)
{
s file= ##class ( %FileCharacterStream ). %New ()
s file.Filename=FilePath_ "/Credentials.JSON"
d file. Write (Data)
s sc=file. %Save ()
i sc'= 1 d
. w FilePath_ " save Credential failed! " _ $System .Status.GetErrorText(sc),!
e d . w FilePath_ " save Credential success! " ,!
q $$$OK
}
ClassMethod SaveGLB(NS)
{
s Drive=..#Drive
w :' $d (^[ "%sys" ]CONFIG( "Namespaces" ,NS)) "namespace " _NS_ " is not existed,please check it!" ,!
q :' $d (^[ "%sys" ]CONFIG( "Namespaces" ,NS)) 1
s rset = ##class ( %ResultSet ). %New ( "%SYS.GlobalQuery:NameSpaceList" )
q :'rset.QueryIsValid() "invalid Query:"
d rset.Execute(NS, "CDY*" )
s Flag= 0
while (rset.Next()){
s HasData=rset.GetDataByName( "HasData" )
i HasData= 1 d
. s Name=rset.GetDataByName( "Name" )
. q :Name[ "PushConfigListD" ;some needed globals
. s Data(Name)= 1
. s Flag= 1
}
w :Flag= 0 NS_ " namespace doesn't have global to be exported" ,!
q :Flag= 0 NS_ " namespace doesn't have global to be exported"
s FilePath=Drive_ ":/BakFile/" _NS_ "/GLB/"
i ' ##class ( %File ).DirectoryExists(FilePath) d
. d ##class ( %File ).CreateDirectoryChain(FilePath)
q ##class ( %Library.Global ).Export(NS,.Data,FilePath_ "SYS.gof" )
}
/// ExportAll CDY* Globals
ClassMethod ExportAllCDYGLB(NS As %String = "" )
{
s currentns= $ZUTIL ( 67 , 6 , $j )
i NS'= "" d
. w :' $d (^[ "%sys" ]CONFIG( "Namespaces" ,NS)) "namespace " _NS_ " is not existed,please check it!" ,!
. q :' $d (^[ "%sys" ]CONFIG( "Namespaces" ,NS))
. s sc= ..SaveGLB (NS)
.i sc'= 1 d
..w " namespace " _NS_ " get an error when export global! " _ $System .Status.GetErrorText(sc),!
e d
. s alns= ""
.f s alns= $o (^[ "%sys" ]CONFIG( "Namespaces" ,alns)) q :alns= "" d
..q : $e (alns, 1 )= "%"
..q :(alns= "ENSDEMO" )||(alns= "ENSEMBLE" )||(alns[ "-" )
..q :' $d (^[alns]CacheMsg( "Confirm" ))
..s sc= ..SaveGLB (alns)
..i sc'= 1 d
. ..w " namespace" _alns_ " get an error when export global! " _ $System .Status.GetErrorText(sc),!
zn currentns
q $$$OK
}
/// location to store backuped file
Parameter Drive = "d" ;
ClassMethod Export()
{
s NS= "" ///namespace
d ..ExportAllClassesIndividual (NS) ///class file(include package)
d ..ExportAllCredentials (NS) ///Credential information
d ..ExportAllCDYGLB (NS) ///Global file, or data
q $$$OK
}
}
ObjectScriptObjectScript
在 iris 中添加一个自定义任务,你就能保留最新的代码和数据。
优化消息搜索
当我得到使消息搜索页面支持使用字符流类型的属性进行过滤的解决方案时(为了解决这个问题,我问 intersystems 的工程师,她告诉我,使用“EnsPortal.MsgFilter.Assistant”,覆盖类方法“GetSQLCondition”。所以我做了一个类扩展 EnsPortal.MsgFilter.Assistant.it 似乎已经解决了问题。在编译类之后然后 s ^EnsPortal.Settings("MessageViewer","AssistantClass")=class
).
我写的代码如下:
Class ENSLIB.MsgFilterAssistant Extends EnsPortal.MsgFilter.Assistant
{
ClassMethod GetSQLCondition(pOperator As %String , pProp As %String , pValue As %String , pDisplay As %Boolean = 0 ) As %String
{
if (pValue = "" ) || ((pOperator '= "Like" ) && (pOperator '= "NotLike" )) quit ##super (pOperator, pProp, pValue, pDisplay)
if ( "%%" = $extract (pValue, *- 2 , *- 1 ))
{
set pValue = "'" _ $extract (pValue, 1 , *- 3 ) _ "' ESCAPE '" _ $extract (pValue, *) _ "'"
} else {
set pValue = "'" _ $replace (pValue, "'" , "''" ) _ "'"
}
quit "substring(" _ pProp _ ", 1, 3000000) " _ $case (pOperator, "Like" : "LIKE" , "NotLike" : "NOT LIKE" ) _ " " _ pValue
}
/// w ##class(ENSLIB.MsgFilterAssistant).SetMV()
ClassMethod SetMV()
{
s ^EnsPortal .Settings( "MessageViewer" , "AssistantClass" )= "ENSLIB.MsgFilterAssistant"
q $$$OK
}
}
ObjectScriptObjectScript
导入代码后,必须有人执行'##class(ENSLIB.MsgFilterAssistant).SetMV()',然后它才能工作!所以我想如果有一种方法可以在导入或编译后自动运行 SetMV,我寻找解决方案,从论坛到开放交换,最后我想到可以用 Projection,代码更改为:
Class ENSLIB.MsgFilterAssistant Extends (EnsPortal.MsgFilter.Assistant, %Projection.AbstractProjection )
{
Projection Reference As MsgFilterAssistant ;
ClassMethod GetSQLCondition(pOperator As %String , pProp As %String , pValue As %String , pDisplay As %Boolean = 0 ) As %String
{
if (pValue = "" ) || ((pOperator '= "Like" ) && (pOperator '= "NotLike" )) quit ##super (pOperator, pProp, pValue, pDisplay)
if ( "%%" = $extract (pValue, *- 2 , *- 1 ))
{
set pValue = "'" _ $extract (pValue, 1 , *- 3 ) _ "' ESCAPE '" _ $extract (pValue, *) _ "'"
} else {
set pValue = "'" _ $replace (pValue, "'" , "''" ) _ "'"
}
quit "substring(" _ pProp _ ", 1, 3000000) " _ $case (pOperator, "Like" : "LIKE" , "NotLike" : "NOT LIKE" ) _ " " _ pValue }
/// w ##class(ENSLIB.MsgFilterAssistant).SetMV()
ClassMethod SetMV()
{
s ^EnsPortal .Settings( "MessageViewer" , "AssistantClass" )= $this
q $$$OK
}
ClassMethod CreateProjection(cls As %String , ByRef params) As %Status
{
w ! w "开始执行" ,!
d ..SetMV ()
w "执行完成" ,!
q $$$OK
}
}
ObjectScriptObjectScript
类编译完成后会自动执行SetMV!是否有更好的实现方式呢?
如果你有更好的方法,请分享!
4.更多
我将继续寻找更好的方法来复制源类和配置数据。
为黄老师点赞!