10个方法,嵌套最深的方法一旦有个业务错误代码,那么后续的程序都要在返回值进行判断。$ze的信息。示例模拟登录后支付处理库存简单的业务操作流程。
Login->支付Pay->库存Stock。先看一下大多数返回值的几种写法:
^分割来处理错误代码。ClassMethod LoginLogic(num)
{// todo 模拟登录逻辑q:(num = 1) "-1^输入密码错误"q:(num = 2) "-2^账户不存在"q:(num = 3) "-3^用户没有启用"q 0
}
这是我们最常见的错误返回值代码。这种返回值有以下问题:
^分隔符来分割,当分隔符不存在的时候,会出现错误。SQLCODE。USER>w ##class(M.OldHandleError).LoginLogic($random(4))
-3^用户没有启用
USER>w ##class(M.OldHandleError).LoginLogic($random(4))
-2^账户不存在
USER>w ##class(M.OldHandleError).LoginLogic($random(4))
-1^输入密码错误
USER>w ##class(M.OldHandleError).LoginLogic($random(4))
0
0,即可判断是否成功。ClassMethod PayLogic(num)
{// todo 模拟支付逻辑q:(num = 4) "余额不足"q:(num = 5) "单笔支付过高"q:(num = 6) "重复支付"q 0
}
这种返回值有以下问题:
ID时,就得需要把判断改成+字符串等于0,也比较繁琐。USER>w ##class(M.OldHandleError).PayLogic($random(7))
重复支付
USER>w ##class(M.OldHandleError).PayLogic($random(7))
余额不足
USER>w ##class(M.OldHandleError).PayLogic($random(7))
单笔支付过高
USER>w ##class(M.OldHandleError).PayLogic($random(7))
0
JSON处理错误描述。JSON规避了分隔符问题,也规避了判断不一直的方式。ClassMethod StockLogic(num)
{// todo 模拟库存逻辑q:(num = 7) {"code":-7,"msg":"库存不足"}q:(num = 8) {"code":-8,"msg":"批次不存在"}q:(num = 9) {"code":-9,"msg":"订单不存在"}q {"code":0,"msg":"订单不存在"}
}
这种返回值有以下问题:
JSON作为返回,也会发生异常。USER>w ##class(M.OldHandleError).StockLogic($random(9)).%ToJSON()
{"code":-9,"msg":"订单不存在"}
USER>w ##class(M.OldHandleError).StockLogic($random(9)).%ToJSON()
{"code":-7,"msg":"库存不足"}
USER>w ##class(M.OldHandleError).StockLogic($random(9)).%ToJSON()
{"code":-8,"msg":"批次不存在"}
USER>w ##class(M.OldHandleError).StockLogic($random(9)).%ToJSON()
{"code":0,"msg":"成功"}
SQLCODE,sc等。ClassMethod MainLogic(num = {$random(11)})
{s $zt = "Error"s ret = ..LoginLogic(num)#; 需要判断分割字符串s code = $p(ret, "^", 1)s msg = $p(ret, "^", 2)q:(code < 0) msgs ret = ..PayLogic(num)#; 需要判断是否为0q:(ret '= 0) rets ret = ..StockLogic(num)#; 需要判断是为JSON对象q:('$isobject(ret)) "异常错误"q:(ret.code <0 ) ret.msgq $$$OK
Errors $zt = ""q $ze
}
USER> w ##class(M.OldHandleError).MainLogic()
输入密码错误
USER> w ##class(M.OldHandleError).MainLogic()
1
USER> w ##class(M.OldHandleError).MainLogic()
批次不存在
USER> w ##class(M.OldHandleError).MainLogic()
单笔支付过高
USER> w ##class(M.OldHandleError).MainLogic()
库存不足
USER> w ##class(M.OldHandleError).MainLogic()
余额不足
基于以上问题我们来设计一套通用的解决方案:
通过
Throw命令抛出自定义异常,由统一入口(门面)拦截抛出的信息,进行处理,根据不同的异常信息,可定制成功消息,失败消息,提示消息。
统一入口(门面模式)的异常处理程序,进行记录系统日志与堆栈信息。
M.BaseException异常基类。M.BaseException继承%Exception.AbstractException。AsSystemError()方法,该方法把异常信息处理为$ze格式。Class M.BaseException Extends %Exception.AbstractException
{Method AsSystemError() As %String [ CodeMode = expression ]
{
i%Name_i%Location_$select(i%Data'="":$select($extract(i%Data)="^":" ",1:" *")_i%Data,1:"")
}}
M.BaseException。M.WarningException - 用于错误提示信息,并非错误。Class M.WarningException Extends M.BaseException
{}
M.LoginException - 用于处理登录错误。Class M.LoginException Extends M.BaseException
{}
M.PayException - 用于处理支付错误。Class M.PayException Extends M.BaseException
{}
M.StockException- 用于处理库存错误。Class M.StockException Extends M.BaseException
{}
M.Base.inc文件,定义一些处理异常的宏。#define StockException(%str) ##class(M.StockException).%New("处理库存失败", -102, ,%str)#define PayException(%str) ##class(M.PayException).%New("支付失败", -101, ,%str)#define LoginException(%str) ##class(M.LoginException).%New("登录失败", -100, ,%str)#define WarningException(%msg) ##class(M.WarningException).%New("提示", -100, ,%msg)#define SystemException(%name, %msg) ##class(%Exception.SystemException).%New(%name, -100, ,%msg)
这里模拟了提示信息与错误信息,提示信息用$$$WarningException返回,错误信息用对应的业务异常类来处理。
通过num变量的随机数模拟真实业务场景可能发生的错误。
LoginLogic
ClassMethod LoginLogic(num)
{// todo 模拟登录逻辑throw:(num = 1) $$$LoginException("输入密码错误")throw:(num = 2) $$$WarningException("账户不存在")throw:(num = 3) $$$LoginException("用户没有启用")q $$$OK
}
PayLogicClassMethod PayLogic(num)
{// todo 模拟支付逻辑throw:(num = 4) $$$PayException("余额不足")throw:(num = 5) $$$PayException("单笔支付过高")throw:(num = 6) $$$WarningException("重复支付")q $$$OK
}
LoginLogicClassMethod StockLogic(num)
{// todo 模拟库存逻辑throw:(num = 7) $$$WarningException("库存不足")throw:(num = 8) $$$StockException("批次不存在")throw:(num = 9) $$$StockException("订单不存在")q $$$OK
}
ClassMethod MainLogic(num = {$random(11)})
{s ret = ..LoginLogic(num)s ret = ..PayLogic(num)s ret = ..StockLogic(num)q ret
}
JSON处理方法,作为处理返回值。/// 返回消息Json
ClassMethod Msg(code, msg = "", data = "") As %DynamicObject
{s ret = {}s ret.code = codes ret.msg = msgs ret.data = dataq ret
}/// 返回前台的成功消息
ClassMethod Success(data) As %DynamicObject
{q ..Msg(200, "", data)
}/// 返回前台的成功消息转Json
ClassMethod Success2Json(data) As %String
{q ..Success(data).%ToJSON()
}/// 返回前台的失败消息
ClassMethod Failure(msg, data = "") As %DynamicObject
{q ..Msg(-1, msg, data)
}/// 返回前台的失败消息转Json
ClassMethod Failure2Json(msg, data = "") As %String
{q ..Failure(msg, data).%ToJSON()
}/// 返回前台的警告消息
ClassMethod Warning(msg, data = "") As %DynamicObject
{q ..Msg(0, msg, data)
}/// 返回前台的警告消息转Json
ClassMethod Warning2Json(msg, data = "") As %String
{q ..Warning(msg, data).%ToJSON()
}
txt文件。txt文件的好处是:当服务器库发生崩溃打不开时,txt文件可以不受干扰。Filename保存的txt文件名,将获取的类、方法、参数值和根据$Stack来获取的堆栈信息,保存追加到txt文件。Parameter Filename = "E:\m\error.txt";ClassMethod LogErrors(msg, className, methodName, params...)
{s stream=##class(%FileCharacterStream).%New()s stream.Filename = ..#Filenamed stream.MoveToEnd()d stream.WriteLine("----------------------------------") s str = "日期:" _ $zdt($h, 3)d stream.WriteLine(str) s str = "类:" _ classNamed stream.WriteLine(str) s str = "方法:" _ methodNamed stream.WriteLine(str) s str = "参数:" _ ..GetMethodParams(className, methodName, params...)d stream.WriteLine(str) s str = "错误信息:" _ msgd stream.WriteLine(str) s str = "堆栈信息:"d stream.WriteLine(str) for loop = 0 : 1 : $stack(-1) { s place = $stack(loop, "PLACE")s source = $stack(loop, "MCODE")s ecode = $stack(loop, "ECODE")s ecode = $s(ecode '= "" : ", ecode: " _ ecode,1:"")s info = " [place: " _ place _ ", source: "_ ..Trim(source) _ ecode _"]"d stream.WriteLine(info)}d stream.WriteLine("----------------------------------") d stream.WriteLine("")d stream.WriteLine("")d stream.SaveStream()d stream.%Close()
}
txt文件。注:使用Try-Catch与$Ztrap双保险机制,在Try-Catch里处理复杂的异常日志保存程序时可能会发生错误,例如插入日志表时字段长度不够,使用$Ztrap对Try-Catch复杂的异常处理程序进行保险,保证程序捕捉所有异常。
ClassMethod GateWay(className, methodName, params...)
{#; 清除错误信息s $ec = ""s $ze = ""#; 设置陷阱s $zt = "Error"try {#; 调用门面s ret = $classmethod(className, methodName, params...)} catch e {#; 解锁当前进程加的异常锁lock#; 回滚异常事务tro:($tl > 0)#; 提示信息,按需做日志记录,此处没有保存错误日志与堆栈if (e.%IsA("M.WarningException")){s msg = e.AsSystemError()ret ..Warning2Json(msg)}#; 记录系统日志s data = e.Datas:$lv(data) data = $$Format^%qcr(data, 1)s msg = e.Name _ e.Location _ " *" _ datas ret = $$LOG^%ETN(msg)s id = $list(ret, 2)#; 记录txt日志以及堆栈d ..LogErrors(msg, className, methodName, params...)#; 将业务错误信息返回if (e.%IsA("M.BaseException")){s msg = e.AsSystemError()ret ..Failure2Json(msg _ " 错误日志id:" _ id)}#; 其他错误if (msg = "") {s msg = $ze}ret ..Failure2Json(msg _ " 错误日志id:" _ id)}#; 成功消息ret ..Success2Json(ret)
Errors $zt = ""q ..Failure2Json("意外错误:" _ $ze)
}
USER>w ##class(M.GateWay).GateWay("M.Main","MainLogic",$random(11))
{"code":-1,"msg":"登录失败 *输入密码错误 错误日志id:49","data":""}
txt文件。




按照上述方式即可达到:
$Ztrap方式捕捉异常。ClassMethod GateWayZtrap(className, methodName, params...)
{s $ec = ""s $ze = ""s $zt = "Error"s ret = $classmethod(className, methodName, params...)ret ..Success2Json(ret)q $$$OK
Errors $zt = ""locktro:($tl > 0)s e = $throwobj#; 提示信息,按需做日志记录,此处没有保存错误日志与堆栈if (e.%IsA("M.WarningException")){s msg = e.AsSystemError()ret ..Warning2Json(msg)}#; 记录系统日志s data = e.Datas:$lv(data) data = $$Format^%qcr(data, 1)s msg = e.Name _ e.Location _ " *" _ datas ret = $$LOG^%ETN(msg)s id = $list(ret, 2)#; 记录txt日志以及堆栈d ..LogErrors(msg, className, methodName, params...)if (e.%IsA("M.BaseException")){s msg = e.AsSystemError()ret ..Failure2Json(msg _ " 错误日志id:" _ id)}if (msg = "") {s msg = $ze}ret ..Failure2Json(msg _ " 错误日志id:" _ id)
}
USER>w ##class(M.GateWay).GateWayZtrap("M.Main","MainLogic",$random(11))
{"code":0,"msg":"提示 *库存不足","data":""}
USER>w ##class(M.GateWay).GateWayZtrap("M.Main","MainLogic",$random(11))
{"code":200,"msg":"","data":1}
USER>w ##class(M.GateWay).GateWayZtrap("M.Main","MainLogic",$random(11))
{"code":-1,"msg":"登录失败 *用户没有启用 错误日志id:51","data":""}
USER>w ##class(M.GateWay).GateWayZtrap("M.Main","MainLogic",$random(11))
{"code":-1,"msg":"支付失败 *单笔支付过高 错误日志id:52","data":""}


M.GateWayClass M.GateWay Extends %RegisteredObject
{
/// 二者取其一 - 双保险方式
/// w ##class(M.GateWay).GateWay("M.Main","MainLogic",$random(11))
ClassMethod GateWay(className, methodName, params...)
{#; 清除错误信息s $ec = ""s $ze = ""#; 设置陷阱s $zt = "Error"try {#; 调用门面s ret = $classmethod(className, methodName, params...)} catch e {#; 通过throw抛出的异常,$stack捕获不到,是因为$ec与$ze为空#; 解锁当前进程加的异常缩lock#; 回滚异常事务tro:($tl > 0)#; 提示信息,按需做日志记录,此处没有保存错误日志与堆栈if (e.%IsA("M.WarningException")){s msg = e.AsSystemError()ret ..Warning2Json(msg)}#; 记录系统日志s data = e.Datas:$lv(data) data = $$Format^%qcr(data, 1)s msg = e.Name _ e.Location _ " *" _ datas ret = $$LOG^%ETN(msg)s id = $list(ret, 2)#; 记录txt日志以及堆栈d ..LogErrors(msg, className, methodName, params...)#; 将业务错误信息返回if (e.%IsA("M.BaseException")){s msg = e.AsSystemError()ret ..Failure2Json(msg _ " 错误日志id:" _ id)}#; 其他错误if (msg = "") {s msg = $ze}ret ..Failure2Json(msg _ " 错误日志id:" _ id)}#; 成功消息ret ..Success2Json(ret)
Errors $zt = ""q ..Failure2Json("意外错误:" _ $ze)
}
/// 二者取其一 - Ztrap方式
/// w ##class(M.GateWay).GateWayZtrap("M.Main","MainLogic",$random(11))
ClassMethod GateWayZtrap(className, methodName, params...)
{s $ec = ""s $ze = ""s $zt = "Error"s ret = $classmethod(className, methodName, params...)ret ..Success2Json(ret)q $$$OK
Errors $zt = ""locktro:($tl > 0)s e = $throwobj#; 提示信息,按需做日志记录,此处没有保存错误日志与堆栈if (e.%IsA("M.WarningException")){s msg = e.AsSystemError()ret ..Warning2Json(msg)}#; 记录系统日志s data = e.Datas:$lv(data) data = $$Format^%qcr(data, 1)s msg = e.Name _ e.Location _ " *" _ datas ret = $$LOG^%ETN(msg)s id = $list(ret, 2)#; 记录txt日志以及堆栈d ..LogErrors(msg, className, methodName, params...)if (e.%IsA("M.BaseException")){s msg = e.AsSystemError()ret ..Failure2Json(msg _ " 错误日志id:" _ id)}if (msg = "") {s msg = $ze}ret ..Failure2Json(msg _ " 错误日志id:" _ id)
}Parameter Filename = "E:\m\error.txt";ClassMethod LogErrors(msg, className, methodName, params...)
{s stream=##class(%FileCharacterStream).%New()s stream.Filename = ..#Filenamed stream.MoveToEnd()d stream.WriteLine("----------------------------------") s str = "日期:" _ $zdt($h, 3)d stream.WriteLine(str) s str = "类:" _ classNamed stream.WriteLine(str) s str = "方法:" _ methodNamed stream.WriteLine(str) s str = "参数:" _ ..GetMethodParams(className, methodName, params...)d stream.WriteLine(str) s str = "错误信息:" _ msgd stream.WriteLine(str) s str = "堆栈信息:"d stream.WriteLine(str) for loop = 0 : 1 : $stack(-1) { s place = $stack(loop, "PLACE")s source = $stack(loop, "MCODE")s ecode = $stack(loop, "ECODE")s ecode = $s(ecode '= "" : ", ecode: " _ ecode,1:"")s info = " [place: " _ place _ ", source: "_ ..Trim(source) _ ecode _"]"d stream.WriteLine(info)}d stream.WriteLine("----------------------------------") d stream.WriteLine("")d stream.WriteLine("")d stream.SaveStream()d stream.%Close()
}/// desc:获取classmethod入参值
ClassMethod GetMethodParams(className, methodName, params...)
{/* 查询类方法对应参数 */s ret = ""s formalSpecParsed = ..GetParamsList(className, methodName)q:(formalSpecParsed = "") ret/* 遍历参数 */for i = 1 : 1 : $ll(formalSpecParsed){/* 参数名称 */s paramName = $lg($lg(formalSpecParsed, i), 1)/* 参数类型 */s paramType = $lg($lg(formalSpecParsed, i), 2)s str = paramName _":"_ params(i)/* 参数字符串以逗号分割 */if (i = 1){s ret = str}else{s ret = ret _ ","_ str}}q ret
}/// desc:获取方法入参数组,无参返回空
ClassMethod GetParamsList(className, methodName)
{s method = className _ "||" _ methodName&SQL(SELECT FormalSpecParsed INTO :formalSpecParsedFROM %Dictionary.CompiledMethodWHERE ID1 = :method )q $g(formalSpecParsed)
}ClassMethod Trim(str As %String) As %String
{q $replace(str, $c(9), "")
}/// 返回消息Json
ClassMethod Msg(code, msg = "", data = "") As %DynamicObject
{s ret = {}s ret.code = codes ret.msg = msgs ret.data = dataq ret
}/// 返回前台的成功消息
ClassMethod Success(data) As %DynamicObject
{q ..Msg(200, "", data)
}/// 返回前台的成功消息转Json
ClassMethod Success2Json(data) As %String
{q ..Success(data).%ToJSON()
}/// 返回前台的失败消息
ClassMethod Failure(msg, data = "") As %DynamicObject
{q ..Msg(-1, msg, data)
}/// 返回前台的失败消息转Json
ClassMethod Failure2Json(msg, data = "") As %String
{q ..Failure(msg, data).%ToJSON()
}/// 返回前台的警告消息
ClassMethod Warning(msg, data = "") As %DynamicObject
{q ..Msg(0, msg, data)
}/// 返回前台的警告消息转Json
ClassMethod Warning2Json(msg, data = "") As %String
{q ..Warning(msg, data).%ToJSON()
}}
M.MainInclude M.BaseClass M.Main Extends %RegisteredObject
{/// w ##class(M.Main).MainLogic()
ClassMethod MainLogic(num = {$random(11)})
{s ret = ..LoginLogic(num)s ret = ..PayLogic(num)s ret = ..StockLogic(num)q ret
}ClassMethod LoginLogic(num)
{// todo 模拟登录逻辑throw:(num = 1) $$$LoginException("输入密码错误")throw:(num = 2) $$$WarningException("账户不存在")throw:(num = 3) $$$LoginException("用户没有启用")q $$$OK
}ClassMethod PayLogic(num)
{// todo 模拟支付逻辑throw:(num = 4) $$$PayException("余额不足")throw:(num = 5) $$$PayException("单笔支付过高")throw:(num = 6) $$$WarningException("重复支付")q $$$OK
}ClassMethod StockLogic(num)
{// todo 模拟库存逻辑throw:(num = 7) $$$WarningException("库存不足")throw:(num = 8) $$$StockException("批次不存在")throw:(num = 9) $$$StockException("订单不存在")q $$$OK
}}
M.OldHandleErrorClass M.OldHandleError Extends %RegisteredObject
{/// w ##class(M.OldHandleError).MainLogic()
ClassMethod MainLogic(num = {$random(11)})
{s $zt = "Error"s ret = ..LoginLogic(num)#; 需要判断分割字符串s code = $p(ret, "^", 1)s msg = $p(ret, "^", 2)q:(code < 0) msgs ret = ..PayLogic(num)#; 需要判断是否为0q:(ret '= 0) rets ret = ..StockLogic(num)#; 需要判断是为JSON对象q:('$isobject(ret)) "异常错误"q:(ret.code <0 ) ret.msgq $$$OK
Errors $zt = ""q $ze
}/// w ##class(M.OldHandleError).LoginLogic($random(4))
ClassMethod LoginLogic(num)
{// todo 模拟登录逻辑q:(num = 1) "-1^输入密码错误"q:(num = 2) "-2^账户不存在"q:(num = 3) "-3^用户没有启用"q 0
}/// w ##class(M.OldHandleError).PayLogic($random(7))
ClassMethod PayLogic(num)
{// todo 模拟支付逻辑q:(num = 4) "余额不足"q:(num = 5) "单笔支付过高"q:(num = 6) "重复支付"q 0
}/// w ##class(M.OldHandleError).StockLogic($random(9)).%ToJSON()
ClassMethod StockLogic(num)
{// todo 模拟库存逻辑q:(num = 7) {"code":-7,"msg":"库存不足"}q:(num = 8) {"code":-8,"msg":"批次不存在"}q:(num = 9) {"code":-9,"msg":"订单不存在"}q {"code":0,"msg":"成功"}
}}