通用的异常处理程序机制与返回值方案
现状
- 相信很多人都为处理错误返回值代码都烦恼过。例如:一个程序嵌套了
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) msg
s ret = ..PayLogic(num)
#; 需要判断是否为0
q:(ret '= 0) ret
s ret = ..StockLogic(num)
#; 需要判断是为JSON对象
q:('$isobject(ret)) "异常错误"
q:(ret.code <0 ) ret.msg
q $$$OK
Error
s $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
}
PayLogic
ClassMethod PayLogic(num)
{
// todo 模拟支付逻辑
throw:(num = 4) $$$PayException("余额不足")
throw:(num = 5) $$$PayException("单笔支付过高")
throw:(num = 6) $$$WarningException("重复支付")
q $$$OK
}
LoginLogic
ClassMethod 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 = code
s ret.msg = msg
s ret.data = data
q 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 = ..#Filename
d stream.MoveToEnd()
d stream.WriteLine("----------------------------------")
s str = "日期:" _ $zdt($h, 3)
d stream.WriteLine(str)
s str = "类:" _ className
d stream.WriteLine(str)
s str = "方法:" _ methodName
d stream.WriteLine(str)
s str = "参数:" _ ..GetMethodParams(className, methodName, params...)
d stream.WriteLine(str)
s str = "错误信息:" _ msg
d 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.Data
s:$lv(data) data = $$Format^%qcr(data, 1)
s msg = e.Name _ e.Location _ " *" _ data
s 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)
Error
s $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
Error
s $zt = ""
lock
tro:($tl > 0)
s e = $throwobj
#; 提示信息,按需做日志记录,此处没有保存错误日志与堆栈
if (e.%IsA("M.WarningException")){
s msg = e.AsSystemError()
ret ..Warning2Json(msg)
}
#; 记录系统日志
s data = e.Data
s:$lv(data) data = $$Format^%qcr(data, 1)
s msg = e.Name _ e.Location _ " *" _ data
s 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.GateWay
Class 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.Data
s:$lv(data) data = $$Format^%qcr(data, 1)
s msg = e.Name _ e.Location _ " *" _ data
s 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)
Error
s $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
Error
s $zt = ""
lock
tro:($tl > 0)
s e = $throwobj
#; 提示信息,按需做日志记录,此处没有保存错误日志与堆栈
if (e.%IsA("M.WarningException")){
s msg = e.AsSystemError()
ret ..Warning2Json(msg)
}
#; 记录系统日志
s data = e.Data
s:$lv(data) data = $$Format^%qcr(data, 1)
s msg = e.Name _ e.Location _ " *" _ data
s 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 = ..#Filename
d stream.MoveToEnd()
d stream.WriteLine("----------------------------------")
s str = "日期:" _ $zdt($h, 3)
d stream.WriteLine(str)
s str = "类:" _ className
d stream.WriteLine(str)
s str = "方法:" _ methodName
d stream.WriteLine(str)
s str = "参数:" _ ..GetMethodParams(className, methodName, params...)
d stream.WriteLine(str)
s str = "错误信息:" _ msg
d 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 :formalSpecParsed
FROM %Dictionary.CompiledMethod
WHERE 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 = code
s ret.msg = msg
s ret.data = data
q 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.Main
Include M.Base
Class 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.OldHandleError
Class 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) msg
s ret = ..PayLogic(num)
#; 需要判断是否为0
q:(ret '= 0) ret
s ret = ..StockLogic(num)
#; 需要判断是为JSON对象
q:('$isobject(ret)) "异常错误"
q:(ret.code <0 ) ret.msg
q $$$OK
Error
s $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":"成功"}
}
}