• 通用的异常处理程序机制与处理返回值方案


    通用的异常处理程序机制与返回值方案

    文章目录

    现状

    • 相信很多人都为处理错误返回值代码都烦恼过。例如:一个程序嵌套了10个方法,嵌套最深的方法一旦有个业务错误代码,那么后续的程序都要在返回值进行判断。
    • 当程序发生系统错误时,例如变量未定义等。也没有记录错误的日志,也不会记录当时报错程序的类、方法,参数等信息。或者说只记录$ze的信息。
    • 当一个无法复现的异常时或者小概率错误时,没有一个机制可以查看调用流程和记录流程所有变量的信息。

    示例

    示例模拟登录后支付处理库存简单的业务操作流程。

    • 登录Login->支付Pay->库存Stock

    先看一下大多数返回值的几种写法:

    1. 登录方法用^分割来处理错误代码。
    ClassMethod LoginLogic(num)
    {
    	// todo 模拟登录逻辑
    	q:(num = 1) "-1^输入密码错误"
    	q:(num = 2) "-2^账户不存在"
    	q:(num = 3) "-3^用户没有启用"
    	q 0
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这是我们最常见的错误返回值代码。这种返回值有以下问题:

    • 需要^分隔符来分割,当分隔符不存在的时候,会出现错误。
    • 当返回值中有多余的分隔符时,会导致返回值按位置取值错误。
    • 每一次需要先按分隔符分割,再判断代码是否正确,方法层数多了很繁琐。
    • 如果其他方法不按此规则返回错误,则会发生异常。例如有写方法直接返回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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. 支付方法直接返回错误描述。
    • 返回值判断比按分隔符方便一些,只需要判断不等于0,即可判断是否成功。
    ClassMethod PayLogic(num)
    {
    	// todo 模拟支付逻辑
    	q:(num = 4) "余额不足"
    	q:(num = 5) "单笔支付过高"
    	q:(num = 6) "重复支付"
    	q 0
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这种返回值有以下问题:

    • 返回处理返回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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. 处理库存方法直接返回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":"订单不存在"}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这种返回值有以下问题:

    • 每次方法都要组装对象,方法层数多,每次也都需要判断很繁琐。
    • 如果有不按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":"成功"}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. 模拟业务方法。
    • 如果业务逻辑,每个方法的返回值处理都不一致。那么主业务逻辑的方法看起来会非常的混乱。
    • 一旦发生报错也非常不好定位。
    • 陷阱错误处理程序也没有记录错误信息和堆栈信息。无法定位和复原当时的情况。
    • 这里还不包括其他返回值的情况。例如返回SQLCODEsc等。
    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
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    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()
    余额不足
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    方案

    基于以上问题我们来设计一套通用的解决方案:

    • 一次性解决返回值的判断问题,方法不用每次在判断上个方法的返回值。只需要接收正确的返回值结果。
    • 记录异常信息或报错方法的类,方法,参数,堆栈信息。
    • 利用系统日志记录错误,查看整个堆栈的环境变量信息。

    原理

    通过Throw命令抛出自定义异常,由统一入口(门面)拦截抛出的信息,进行处理,根据不同的异常信息,可定制成功消息,失败消息,提示消息。

    统一入口(门面模式)的异常处理程序,进行记录系统日志与堆栈信息。

    步骤

    1. 定义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:"")
    }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. 分别定义登录、支付、库存异常类继承M.BaseException
    • M.WarningException - 用于错误提示信息,并非错误。
    Class M.WarningException Extends M.BaseException
    {
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • M.LoginException - 用于处理登录错误。
    Class M.LoginException Extends M.BaseException
    {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • M.PayException - 用于处理支付错误。
    Class M.PayException Extends M.BaseException
    {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • M.StockException- 用于处理库存错误。
    Class M.StockException Extends M.BaseException
    {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    1. 定义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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. 分别修改登录、支付、库存处理返回值程序。
    • 这里模拟了提示信息与错误信息,提示信息用$$$WarningException返回,错误信息用对应的业务异常类来处理。

    • 通过num变量的随机数模拟真实业务场景可能发生的错误。

    • LoginLogic

    ClassMethod LoginLogic(num)
    {
        // todo 模拟登录逻辑
    	throw:(num = 1) $$$LoginException("输入密码错误")
    	throw:(num = 2) $$$WarningException("账户不存在")
    	throw:(num = 3) $$$LoginException("用户没有启用")
    	q $$$OK
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • PayLogic
    ClassMethod PayLogic(num)
    {
        // todo 模拟支付逻辑
    	throw:(num = 4) $$$PayException("余额不足")
    	throw:(num = 5) $$$PayException("单笔支付过高")
    	throw:(num = 6) $$$WarningException("重复支付")
    	q $$$OK
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • LoginLogic
    ClassMethod StockLogic(num)
    {
        // todo 模拟库存逻辑
    	throw:(num = 7) $$$WarningException("库存不足")
    	throw:(num = 8) $$$StockException("批次不存在")
    	throw:(num = 9) $$$StockException("订单不存在")
    	q $$$OK
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. 主业务逻辑
    • 我们发现主业务逻辑没有处理异常信息,仅处理正确返回值。因为调用主逻辑方法需要一个通用的门面入口,所以不必要再此定义异常处理程序。
    ClassMethod MainLogic(num = {$random(11)})
    {
    	s ret = ..LoginLogic(num)
    	s ret = ..PayLogic(num)
    	s ret = ..StockLogic(num)
    	q ret
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 定义通用返回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()
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    1. 定义保存日志方法。
    • 这里我们保存日志的信息使用的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()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    1. 创建调用门面。
    • 门面是统一入口,处理返回值与异常信息,保存系统日志与txt文件。
    • 根据不同异常,返回信息处理不用,提示异常可直接返回,错误异常可记录到日志,成功直接返回。

    注:使用Try-Catch$Ztrap双保险机制,在Try-Catch里处理复杂的异常日志保存程序时可能会发生错误,例如插入日志表时字段长度不够,使用$ZtrapTry-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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 入口方法为统一调用门面方法。其他语言称之为反射。
    USER>w ##class(M.GateWay).GateWay("M.Main","MainLogic",$random(11))
    {"code":-1,"msg":"登录失败 *输入密码错误 错误日志id:49","data":""}
    
    • 1
    • 2
    • 查看txt文件。

    在这里插入图片描述

    • 包含报错程序的,时间,类,方法,参数,错误信息,堆栈信息。这些最简单的信息。需要其他信息根据需要进行添加

    在这里插入图片描述

    • 查看系统错误日志

    在这里插入图片描述

    • 查看系统日志环境变量

    在这里插入图片描述

    • 查看系统日志堆栈信息

    在这里插入图片描述

    总结

    按照上述方式即可达到:

    1. 一次性解决返回值的判断问题,方法不用每次在判断上个方法的返回值。只需要接收正确的返回值结果。
    2. 记录异常信息或报错方法的类,方法,参数,堆栈信息。
    3. 利用系统日志记录错误,查看整个堆栈的环境变量信息。
    4. 示例门面代码可直接复制到你的程序中作为入口使用,调用的方法再也不需要判断返回值了,也不用再给每个方法设置专门的异常处理程序了。

    • $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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    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":""}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

    在这里插入图片描述

    完整代码

    • 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()
    }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 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
    }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 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":"成功"}
    }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
  • 相关阅读:
    Atlas 200 DK开发板问题总结
    港中文、腾讯等联合发布!DynamiCrafter:任意图像秒变动态视频,超高清无缝衔接
    洛谷P1202 黑色星期五Friday the Thirteenth
    KSQL DB 学习笔记1
    简单聊一聊一种很新的DCDC电源-BOB电源
    第二十六章 :Docker 内部 DNS 服务如何使用
    【学习笔记】《模式识别》4:贝叶斯判别准则
    自签名SSL证书的安全隐患和风险有哪些?
    Eolink是国产API接口管理的无冕之王
    大数据Java基础——集合(泛型,增强for遍历,静态导入,集合嵌套)
  • 原文地址:https://blog.csdn.net/yaoxin521123/article/details/128212542