免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: pacman2000
打印 上一主题 下一主题

一个程序员在AS400银行核心系统开发中的技术总结 [复制链接]

论坛徽章:
0
21 [报告]
发表于 2015-08-20 01:15 |只看该作者
本帖最后由 RAIITSMXH 于 2015-08-20 01:17 编辑

AS400就算国企不用了,外企也还用,国内不会没有工作,估计到时候能做400工作的都是技术过硬的忠实的400爱好者;
关于去‘IOE',阿里巴巴可以做到,银行未必可以,拭目以待,就算最终做到了,那最起码要5-10年吧

论坛徽章:
5
2015亚冠之阿尔艾因
日期:2015-08-10 02:23:34操作系统版块每日发帖之星
日期:2015-08-13 06:20:002015七夕节徽章
日期:2015-08-21 11:06:1715-16赛季CBA联赛之山西
日期:2016-04-13 02:36:59操作系统版块每日发帖之星
日期:2016-04-14 06:20:00
22 [报告]
发表于 2015-08-21 01:59 |只看该作者
*多成员表

    银行核心系统通常生命周期在10年左右,现在随着银行系统的数量和复杂度增加,系统的生命期也有逐渐变长的趋势。通常核心系统上线时,很多数据表的记录数并不太多,而当系统运行年限越来越长,巨型数据表就越来越多。另外,随着城商、农信的省级集中,用以前针对城市级商业银行的系统,来应对省级的数据量,原先规模合理的表,也会变成巨型表。在实际项目中,已经遇到记录数上亿,和十亿级别的表了,不光严重影响系统性能,在7*24小时不间断运行条件下,数据清理都是个难题。以往的系统,在这方面考虑是比较少的,大概是因为系统设计实现者通常只管上线,不会长期维护,而且原先系统面向银行的规模较小。因此,要么没考虑大数据量表的拆分,要么常处理方法也只是简单区分下当前表和历史表,在日终时做个当前倒历史的批量(历史表巨大的话,倒历史时间也会变得很长)。
分析银行的数据表,容易产生很多记录数的案例,就会发现通常有两种状况比较常见。一种是明细流水性质的表,产生一笔就会新增一条记录,日积月累就会变得很大。这里面大多数都可以用日期时间为顺序和查询依据的,例如账户明细表,传票表等。这类表很明显可以按日期进行划分。超过一定的期限以后,历史的数据就可以清理了,通常历史查询功能由ODS之类的数据仓库系统提供。经过清理后,数据量可以保持稳定。另一种是账户主表,凭证主表,客户信息表等,随着系统多年使用逐渐增长,而且可清理的不多,数据量随业务发展总体呈增长趋势。

这里我们看看在Firebird系统中巨型表的解决办法。从根本上说,应当将大表分小,并且要适合使用的情景。在开放平台数据库,有分区表的概念,而在400平台,对应的就是*FILE的多MEMBER。对于可按日期划分的表,我们可以带日期为MEMBER名,每日一个MEMBER,预先创建好,无需倒历史切换,解决了7*24小时和数据清理的问题。对于主表,可按主键号码或某些字段hash计算分MEMBER。例如,客户表按客户号的hash分区,凭证表按凭证号的hash分区。但账户表要按账户所属机构号分区,这是为了优化结息批处理性能,利于每个按机构并发的结息程序只访问一个MEMBER。
400系统对多MEMBER的限制有哪些呢?首先,同一个*FILE支持的MEMBER数量,最大为32767个。按每日分区计算,大约可以支持90年。按hash计算,10亿级别的表hash到100个MEMBER,每个MEMBER仅1000万记录数量。其次,单个MEMBER最多记录数为2^32,大约42亿。但实际上记录数过多,访问操作性能会指数级下降。单个MEMBER内记录数在1000万级别比较合适。最后,对于LF文件的单个MEMBER,能关联PF的MEMBER数量是有限制的,大概是32个,但实际使用中LF的MEMBER命名与PF相同,完全的一对一建立。如果要搜索多个MEMBER,在应用程序中循环读取实现。

有关多MEMBER的命令有以下这些。
CRTPF或CHGPF时指定MAXMBRS为*NOMAX。
CRTLF或CHGLF时指定MAXMBRS为*NOMAX。
ADDPFM 在PF中新增一个MEMBER。
ADDLFM 在LF中新增一个MEMBER。
RMVM 删除一个指定的MEMBER。
CLRPFM 清空一个MEMBER的所有记录。

下面的CL程序是给PF和LF增加连续日期的MEMBER例子。MEMBER名类似M20150821。

  1.              PGM        PARM(&FLNM &STNMC &EDNMC)
  2.              INCLUDE    SRCMBR(CLHD) SRCFILE(DSCPPGM)
  3.              DCL        VAR(&FLNM) TYPE(*CHAR) LEN(10)
  4.              DCL        VAR(&STNMC) TYPE(*CHAR) LEN(8)
  5.              DCL        VAR(&EDNMC) TYPE(*CHAR) LEN(8)
  6.              DCL        VAR(&STNM) TYPE(*CHAR) LEN(10)
  7.              DCL        VAR(&EDNM) TYPE(*CHAR) LEN(10)
  8.              DCL        VAR(&MBNM) TYPE(*CHAR) LEN(10)
  9.              DCL        VAR(&DAYS) TYPE(*DEC) LEN(5 0)
  10.              DCL        VAR(&I) TYPE(*DEC) LEN(5 0)
  11.              DCL        VAR(&NM) TYPE(*CHAR) LEN(10)
  12.              DCL        VAR(&FLTP) TYPE(*CHAR) LEN(10)
  13.              DCL        VAR(&FLLB) TYPE(*CHAR) LEN(10)
  14.              DCL        VAR(&PFNM) TYPE(*CHAR) LEN(10)
  15.              RTVOBJD    OBJ(&FLNM) OBJTYPE(*FILE) OBJATR(&FLTP) RTNLIB(&FLLB)
  16.              CHGVAR     VAR(&STNM) VALUE(&STNMC)
  17.              CHGVAR     VAR(&EDNM) VALUE(&EDNMC)
  18.              CALL       PGM(DATECALC) PARM(&STNM &EDNM &DAYS)
  19.              IF         COND(&DAYS *LT 0) THEN(GOTO CMDLBL(ENDPGM))
  20.              CHGVAR     VAR(&I) VALUE(0)
  21. ADD:        IF         COND(&I *LE &DAYS) THEN(DO)
  22.              CHGVAR     VAR(&NM) VALUE('          ')
  23.              CALL       PGM(DATECALC) PARM(&STNM &NM &I)
  24.              CHGVAR     VAR(&MBNM) VALUE('M' *TCAT &NM)
  25.              IF         COND(&FLTP *EQ 'PF') THEN(DO)
  26.              ADDPFM     FILE(&FLNM) MBR(&MBNM)
  27.              ENDDO
  28.              IF         COND(&FLTP *EQ 'LF') THEN(DO)
  29.              CHGVAR     VAR(&PFNM) VALUE(%SST(&FLNM 1 7))
  30.              ADDLFM     FILE(&FLNM) MBR(&MBNM) DTAMBRS((&FLLB/&PFNM +
  31.                           (&MBNM)))
  32.              ENDDO
  33.              CHGVAR     VAR(&I) VALUE(&I + 1)
  34.              GOTO       CMDLBL(ADD)
  35.              ENDDO
  36. ENDPGM:     ENDPGM
复制代码
下面的CL程序是给PF和LF增加连续hash的MEMBER例子。MEMBER名类似M01到M99。另外还有3位数M001到M999的程序就不贴了。

  1.              PGM        PARM(&FLNM &STNMC &EDNMC)
  2.              INCLUDE    SRCMBR(CLHD) SRCFILE(DSCPPGM)
  3.              DCL        VAR(&FLNM) TYPE(*CHAR) LEN(10)
  4.              DCL        VAR(&STNMC) TYPE(*CHAR) LEN(2)
  5.              DCL        VAR(&EDNMC) TYPE(*CHAR) LEN(2)
  6.              DCL        VAR(&STNM) TYPE(*INT)
  7.              DCL        VAR(&EDNM) TYPE(*INT)
  8.              DCL        VAR(&MBNM) TYPE(*CHAR) LEN(10)
  9.              DCL        VAR(&NM) TYPE(*CHAR) LEN(2)
  10.              DCL        VAR(&FLTP) TYPE(*CHAR) LEN(10)
  11.              DCL        VAR(&FLLB) TYPE(*CHAR) LEN(10)
  12.              DCL        VAR(&PFNM) TYPE(*CHAR) LEN(10)
  13.              RTVOBJD    OBJ(&FLNM) OBJTYPE(*FILE) OBJATR(&FLTP) RTNLIB(&FLLB)
  14.              CHGVAR     VAR(&STNM) VALUE(&STNMC)
  15.              CHGVAR     VAR(&EDNM) VALUE(&EDNMC)
  16. ADD:        IF         COND(&STNM *LE &EDNM) THEN(DO)
  17.              CHGVAR     VAR(&NM) VALUE(&STNM)
  18.              CHGVAR     VAR(&MBNM) VALUE('M' *TCAT &NM)
  19.              IF         COND(&FLTP *EQ 'PF') THEN(DO)
  20.              ADDPFM     FILE(&FLNM) MBR(&MBNM)
  21.              ENDDO
  22.              IF         COND(&FLTP *EQ 'LF') THEN(DO)
  23.              CHGVAR     VAR(&PFNM) VALUE(%SST(&FLNM 1 7))
  24.              ADDLFM     FILE(&FLNM) MBR(&MBNM) DTAMBRS((&FLLB/&PFNM +
  25.                           (&MBNM)))
  26.              ENDDO
  27.              CHGVAR     VAR(&STNM) VALUE(&STNM + 1)
  28.              GOTO       CMDLBL(ADD)
  29.              ENDDO
  30.              ENDPGM
复制代码
建立起多MEMBER文件以后,在应用程序中使用就有注意的地方了。
首要原则,对上层组件尽量透明,把多MEMBER的操作尽可能封装在数据表访问组件内部,对外就像操作的是一个文件一样。这涉及到核心系统整体规划上的“交易--模块公有组件--模块私有组件--数据访问封装”体系,就是另外的话题了。
那么,在RPG程序中,多MEMBER文件的代码怎么写呢?主要区别在F表定义上。F表定义文件的时候,选项部分需要多写两个,EXTMBR(V_MBRNAME) USROPN。EXTMBR用于根据变量V_MBRNAME的值指示要操作的MEMBER名,USROPN则是在程序执行到OPEN语句时再打开文件。通过程序中先给V_MBRNAME赋值,再执行OPEN语句,就可以操作指定的MEMBER啦。值得注意的是,前面介绍过激活组会保持文件的打开状态以供下次调用使用,多MEMBER时,不能简单通过%OPEN()内置函数来判断文件是否已打开,因为%OPEN()函数对不同MEMBER不作区分,比如已打开M003,这次虽然V_MBRNAME赋值了M998,但%OPEN()判断时仍认为文件已打开,必须要通过打开的MEMBER名是否相同来额外区分。

RPG对多MEMBER表的使用例子节选如下。

  1.      **********************************************************************
  2.      FAPLBTTR   O    E             DISK    INFSR(#FLEX) INFDS(S_PLBTTR)
  3.      F                                     EXTMBR(V_MBRNAME)
  4.      F                                     QUALIFIED USROPN COMMIT
  5.      **********************************************************************
  6.      D*引入数据字典枚举常量
  7.      D/COPY DSCPPGM,ENUM
  8.      D*引入程序公共结构
  9.      D/COPY DSCPPGM,PGDS
  10.      D*文件结构定义
  11.      DS_PLBTTR         DS                  LIKEDS(FILESDS)
  12.      DR_PLBTTR         DS                  LIKEREC(APLBTTR.RAPLBTTR:*OUTPUT)
  13.      *************************
  14.      D*常量定义
  15.      DC_PREMBR         C                   CONST('M')
  16.      *************************
  17.      **日期结构
  18.      DD_DATE           DS                  QUALIFIED
  19.      D YEA                            4A
  20.      D FLG1                           1A
  21.      D MON                            2A
  22.      D FLG2                           1A
  23.      D DAY                            2A
  24.      *************************
  25.      D*临时变量结构
  26.      DD_VARS           DS
  27.      D V_MBRNAME                           LIKE(DICT.@@FLNM)
  28.      **********************************************************************
  29.      ** @MAIN程序的主流程
  30.      **********************************************************************
  31.      C     @MAIN         BEGSR
  32.      C                   EVAL      FILEDSP = %ADDR(S_PLBTTR)
  33.      C                   EVAL      D_DATE = %CHAR(BTWTF1.BTWKDT)
  34.      C                   EVAL      V_MBRNAME = C_PREMBR + D_DATE.YEA +
  35.      C                                         D_DATE.MON + D_DATE.DAY
  36.      C*判断已打开的成员不一致则关闭重新打开
  37.      C                   IF        FL_MEMBER <> V_MBRNAME
  38.      C                   CLOSE     APLBTTR
  39.      C                   ENDIF
  40.      C*打开指定成员
  41.      C                   IF        NOT %OPEN(APLBTTR)
  42.      C                   OPEN      APLBTTR
  43.      C                   ENDIF
  44.      C                   CLEAR                   R_PLBTTR
  45.      C                   EVAL-CORR R_PLBTTR = BTWTF1
  46.      C                   EVAL      R_PLBTTR.STTMSP  = %TIMESTAMP()
  47.      C                   EVAL      R_PLBTTR.RPSHNU  = 0
  48.      C                   EVAL      R_PLBTTR.BKEXST  = EXST_NOTEX
  49.       /FREE
  50.                          WRITE     APLBTTR.RAPLBTTR R_PLBTTR;
  51.       /END-FREE
  52.      C                   ENDSR
  53.      **********************************************************************
复制代码

论坛徽章:
5
2015亚冠之阿尔艾因
日期:2015-08-10 02:23:34操作系统版块每日发帖之星
日期:2015-08-13 06:20:002015七夕节徽章
日期:2015-08-21 11:06:1715-16赛季CBA联赛之山西
日期:2016-04-13 02:36:59操作系统版块每日发帖之星
日期:2016-04-14 06:20:00
23 [报告]
发表于 2015-08-24 02:31 |只看该作者
*报文格式

AS400核心系统的联机交易报文,是体现系统设计优劣的一个重要部分,并且,也决定了联机交易主控的处理方式。

系统的报文规范遵循简单灵活安全高效的原则,应做到以下几点。首先,交易通过短连接单次通讯完成,不需要先分配端口,多次交互,也没有任何前后交易的状态耦合。其次,交易报文一次一密全文加密,并且不需要每次交易做多次密钥交换动作。即使将成功报文原样重新发送,系统也会识别为重复提交而不会重复执行。最后,报文组织应充分考虑AS400应用程序的写法,既要满足交易程序参数传递的性能,又要尽量保持报文灵活,并减少无意义的内容传送。在交易RPG程序中,输入输出接口,基本上都是以DS或DS数组的形式PARM传递进来处理。因此,体现单个字段key-value的XML,JSON之类格式,就不太适合作为AS400报文使用。因此,Firebird设计了一种特定的报文格式。
整体报文结构规则是,不论输入报文或者输出报文,除了少数报文头字段,其余均以接口结构方式组织。接口结构由接口名称,记录条数,单条长度,接口内容四个部分组成。一个交易的接口结构顺序,是否出现均不固定,不影响交易的执行。特别的,当接口为数组时,报文只需出现实际条数的接口内容,如果数组为空,则整个数组接口都不会出现。这样就减少了报文的大小,提升了通讯效率。在交易输出报文中,除了预先定义的交易输出接口外,还可以附加的方式返回其他特殊用途接口。例如提示信息,常用于交易成功但需要传递的提醒或警告,需要下传的凭证打印等。这在之前用户空间USER SPACE时介绍过。每个交易除了公共定义的接口外,最多有9个自定义输入接口,9个自定义输出接口,这些接口都可以是单一结构或数组,单条接口结构最大64KB。接口内部是定长的,但接口数量和出现顺序可任意组合。
目前系统设定的输入输出报文大小上限是1MB,通过修改主控的常量定义,可最大扩充到16MB。报文在通讯中作为整体一次TCP传递,一笔交易一次通讯交互即可,无需应用系统分片。

具体的输入报文格式如下:
总长度 INT 4
交易序号 INT 8
校验码 INT 4
公共接口名称 STR 10
公共接口记录条数 INT 2
公共接口单条长度 INT 2
公共接口内容 BUF
接口一名称 STR 10
接口一记录条数 INT 2
接口一单条长度 INT 2
接口一内容 BUF
……
接口N名称 STR 10
接口N记录条数 INT 2
接口N单条长度 INT 2
接口N内容 BUF


输出报文格式分两种情况。
当返回的是交易成功报文时:
总长度 INT 4
交易序号 INT 8
校验码 INT 4
返回码 STR 10
公共接口名称 STR 10
公共接口记录条数 INT 2
公共接口单条长度 INT 2
公共接口内容 BUF
接口一名称 STR 10
接口一记录条数 INT 2
接口一单条长度 INT 2
接口一内容 BUF
……
接口N名称 STR 10
接口N记录条数 INT 2
接口N单条长度 INT 2
接口N内容 BUF

当返回的是出错报文时:
总长度 INT 4
交易序号 INT 8
校验码 INT 4
返回码 STR 10
出错文件名 STR 10
错误信息内容 STR 80
接口一名称 STR 10
接口一记录条数 INT 2
接口一单条长度 INT 2
接口一内容 BUF
……
接口N名称 STR 10
接口N记录条数 INT 2
接口N单条长度 INT 2
接口N内容 BUF


下面详细解释一下报文头各字段含义。
总长度:输入输出报文的开头4个字节为报文总长度,即从交易序号开始,后续报文的字节数。总长度如果是负数,有特殊用途,表示这笔交易会运行于调试模式,而不影响其他交易的正常模式,适合用于生产环境的疑难问题跟踪重现。
交易序号:用于标识每一笔交易的唯一增长值。无符号长整形数值,Firebird使用的是全局时间戳(毫秒级)加2位组合步骤序号。举例来说,如果是柜面渠道直连核心交易,那么就是时间戳加00,如2015082401315512300,表示2015-08-24 01:31:55.123发送的交易。如果是通过综合前置或ESB调度的组合交易,如2015082401315512302,表示2015-08-24 01:31:55.123发送的交易,在组合流程中的第2步调用。交易序号应保证按柜员号每笔增长,系统拒绝不正常的交易序号,以防止伪造或重复报文攻击。
校验码:对后续报文全文加密并计算出的校验码,用于防止报文被篡改。
返回码:输出报文时存在,如果交易正常,返回码为'OK',如果出错,则为错误代码。如果为AS400系统级错误,则为F+系统错误代码,如FCPF3282。如果是应用错误,则为E+应用错误代码,如ECNBRNORL。
出错文件名:交易出错时存在此字段,表示产生错误的源程序名。在后台主控日志中,还可以查到产生系统错误的源程序行号。
错误信息内容:错误的说明信息,通常为代入数据后的错误中文描述。例如:机构表中机构号064001不存在。

接下来就是接口部分。
输入的公共接口有3个,INHD是输入公共结构,INFE是交易收费数组,INCS是券面数组。注意,后2者只有存在数据时才会出现。
输出的公共接口有2个,OTHD是输出公共结构,OTVC是交易产生的传票数组。同样,OTVC只有产生传票的交易才会出现。
输出特有的接口有1个,OTFE是输出收费数组,用于收费输入不正确或没输时,交易报错并回显正确的收费项。因此这个接口只有在特定出错场景才会出现。
输出额外附加的接口,包括OTWN,交易警告和提示信息数组,以及下传的其他凭证打印接口结构。

那么,在交易程序中,报文接口是怎么体现的呢?可以看一下具体的程序代码片段。

  1.      **定义常量
  2.      DC_NBO1           C                   CONST(50)
  3.      DC_NBO2           C                   CONST(50)
  4.      DC_NBO3           C                   CONST(50)
  5.      *************************
  6.      **定义参照外部结构的结构、数组 
  7.      DT_INHD         E DS                  EXTNAME(INHD)  QUALIFIED  TEMPLATE
  8.      DT_INFE         E DS                  EXTNAME(INFE)  QUALIFIED  TEMPLATE
  9.      DT_INCS         E DS                  EXTNAME(INCS)  QUALIFIED  TEMPLATE
  10.      DT_OTHD         E DS                  EXTNAME(OTHD)  QUALIFIED  TEMPLATE
  11.      DT_OTVC         E DS                  EXTNAME(OTVC)  QUALIFIED  TEMPLATE
  12.      DT_I1           E DS                  EXTNAME(T020002I1) QUALIFIED TEMPLATE
  13.      DT_O1           E DS                  EXTNAME(T020002O1) QUALIFIED TEMPLATE
  14.      DT_O2           E DS                  EXTNAME(T020002O2) QUALIFIED TEMPLATE
  15.      DT_O3           E DS                  EXTNAME(T020002O3) QUALIFIED TEMPLATE
  16.      *************************
  17.      **定义参照内部结构、变量(LIKE)的结构、数组
  18.      DPIN              DS                  QUALIFIED
  19.      D INHD                                LIKEDS(T_INHD)
  20.      D INFE                                DIM(C_NBINFE) LIKEDS(T_INFE)
  21.      D INCS                                DIM(C_NBINCS) LIKEDS(T_INCS)
  22.      D I1                                  LIKEDS(T_I1)
  23.      **
  24.      DPOT              DS                  QUALIFIED
  25.      D OTHD                                LIKEDS(T_OTHD)
  26.      D OTVC                                DIM(C_NBOTVC) LIKEDS(T_OTVC)
  27.      D O1                                  DIM(C_NBO1) LIKEDS(T_O1)
  28.      D O2                                  DIM(C_NBO2) LIKEDS(T_O2)
  29.      D O3                                  DIM(C_NBO3) LIKEDS(T_O3)
  30.      ***********************************************************
  31.      **定义程序入口
  32.      C     *ENTRY        PLIST
  33.      C                   PARM                    PMG
  34.      C                   PARM                    PIN
  35.      C                   PARM                    POT
复制代码
这段是020002交易的主程序。联机交易的PARM都是3个,PMG是错误信息结构,PIN是输入接口结构,POT是输出接口结构。020002交易有1个自定义输入I1,3个自定义输出O1,O2,O3均为最大50条记录的数组。因此加上公共接口,就体现为PIN,POT的完整内容。因为报文只会出现有数据的接口内容,所以将内容填入正确的位置,就是主控程序的工作职责了,对交易程序来说,无需关心。

论坛徽章:
10
数据库技术版块每日发帖之星
日期:2015-06-14 22:20:00数据库技术版块每日发帖之星
日期:2016-03-10 06:20:00数据库技术版块每日发帖之星
日期:2015-12-01 06:20:00IT运维版块每日发帖之星
日期:2015-11-09 06:20:00IT运维版块每日发帖之星
日期:2015-11-02 06:20:00IT运维版块每日发帖之星
日期:2015-07-13 22:59:28IT运维版块每日发帖之星
日期:2015-06-23 22:20:00程序设计版块每日发帖之星
日期:2015-06-21 22:20:00每日论坛发贴之星
日期:2015-06-14 22:20:00IT运维版块每日发帖之星
日期:2016-08-02 06:20:00
24 [报告]
发表于 2015-08-25 19:02 |只看该作者
这样的系统,必定会被加速淘汰。曾经的辉煌没落于更新太慢,创新不足。

论坛徽章:
10
数据库技术版块每日发帖之星
日期:2015-06-14 22:20:00数据库技术版块每日发帖之星
日期:2016-03-10 06:20:00数据库技术版块每日发帖之星
日期:2015-12-01 06:20:00IT运维版块每日发帖之星
日期:2015-11-09 06:20:00IT运维版块每日发帖之星
日期:2015-11-02 06:20:00IT运维版块每日发帖之星
日期:2015-07-13 22:59:28IT运维版块每日发帖之星
日期:2015-06-23 22:20:00程序设计版块每日发帖之星
日期:2015-06-21 22:20:00每日论坛发贴之星
日期:2015-06-14 22:20:00IT运维版块每日发帖之星
日期:2016-08-02 06:20:00
25 [报告]
发表于 2015-08-25 19:04 |只看该作者
陈旧的理论,固步自封于范式。
回复 22# pacman2000


   

论坛徽章:
0
26 [报告]
发表于 2015-09-17 13:36 |只看该作者
谢谢楼主分享,来支持呢。

论坛徽章:
5
2015亚冠之阿尔艾因
日期:2015-08-10 02:23:34操作系统版块每日发帖之星
日期:2015-08-13 06:20:002015七夕节徽章
日期:2015-08-21 11:06:1715-16赛季CBA联赛之山西
日期:2016-04-13 02:36:59操作系统版块每日发帖之星
日期:2016-04-14 06:20:00
27 [报告]
发表于 2016-02-21 23:49 |只看该作者
*数据字典和枚举值

    在核心系统应用开发中,数据字典有举足轻重的作用。数据字典是对系统中所有字段的归纳抽象,需要规范同类字段的类型长度,方便定义和修改。
    但是,在过去的核心系统中,数据字典的理解往往也会有失误。常见的错误方法,是试图穷举定义所有可以出现的字段名字。可是实际上核心系统应用程序内容是不断扩充的,功能和交易程序都在不停增加,所用到的字段也是无穷无尽。如果需要列举的是所有字段,就像建巴比伦塔一样困难。而且不可避免会出现账号1到账号9这样的重复。
    在Firebird系统中,数据字典的使用方式回归了本源意义,即数据字典定义的是不重复的字段基础类型。举例来说,“日期”就是一个基础类型,归数据字典管理。而“交易日期”,“开户日期”这些字段,都只是使用基础类型定义出来的具体字段变量,不归集到数据字典管理,但命名上要求具体字段必须以数据字典基础类型结尾,体现归属关系。“日期”在数据字典里定义的是WKDT,“开户日期”起名可以叫OPWKDT,“交易日期”起名可以叫TRWKDT,不同场景中也可以起名不一样。这样的数据字典和字段命名规则,既满足应用系统字段的不断扩充,又保持数据字典的精简和稳定。

    数据字典的实现方式,是建立一个DICT的PF文件并编译,里面的内容即是所有基础类型的定义。摘录部分如下所示。

  1.      A          R RDICT
  2.      A*长度 L
  3.      A            @@WKDT          L         TEXT('日期')
  4.      A
  5.      A*长度 T
  6.      A            @@WKTM          T         TEXT('时间')
  7.      A
  8.      A*长度 Z
  9.      A            @@TMSP          Z         TEXT('时间戳')
  10.      A
  11.      A*长度 1A
  12.      A            @@CHAR         1A         TEXT('字符')
  13.      A            @@YNFG         1A         TEXT('是否标志')
  14.      A*                            1 是 YES
  15.      A*                            0 否 NO
  16.      ......
  17.      A*长度 3P
  18.      A            @@EXSQ         3P 0       TEXT('执行序号')
  19.      A*长度 4A
  20.      A            @@SMCD         4A         TEXT('摘要代码')
  21.      A            @@ITCD         4A         TEXT('核算代码')
  22.      A            @@CSCD         4A         TEXT('现金项目代码')
  23.      ......
复制代码
为了与实际应用程序中的字段名区分,数据字典中的基础类型字段名以@@开头。

    在表结构PF字段定义,通用结构定义,交易、组件输入输出接口PF字段定义时,就可以参照数据字典了。
    以尾箱凭证登记表ACNVCBB为例展示如下。

  1.      A                                      UNIQUE
  2.      A                                      REF(DICT)
  3.      A          R RACNVCBB
  4.      A            BKBRNO    R               REFFLD(@@BRNO)
  5.      A                                      TEXT('机构号')
  6.      A                                      COLHDG('机构号')
  7.      A            BKBXNO    R               REFFLD(@@BXNO)
  8.      A                                      TEXT('尾箱号')
  9.      A                                      COLHDG('尾箱号')
  10.      A            BKVCTP    R               REFFLD(@@VCTP)
  11.      A                                      TEXT('凭证种类')
  12.      A                                      COLHDG('凭证' '种类')
  13.      A            VBMDNU    R               REFFLD(@@MDNU)
  14.      A                                      TEXT('凭证张数')
  15.      A                                      COLHDG('凭证' '张数')
  16.      A            BKSTCD    R               REFFLD(@@STCD)
  17.      A                                      TEXT('记录状态')
  18.      A                                      COLHDG('记录状态')
  19.      A          K BKBRNO
  20.      A          K BKBXNO
  21.      A          K BKVCTP
复制代码
以尾箱主表ACNBXBX为例展示如下。

  1.      A                                      UNIQUE
  2.      A                                      REF(DICT)
  3.      A          R RACNBXBX
  4.      A            BKBRNO    R               REFFLD(@@BRNO)
  5.      A                                      TEXT('机构号')
  6.      A                                      COLHDG('机构号')
  7.      A            BKBXNO    R               REFFLD(@@BXNO)
  8.      A                                      TEXT('尾箱号')
  9.      A                                      COLHDG('尾箱号')
  10.      A            VCACNO    R               REFFLD(@@ACNO)
  11.      A                                      TEXT('凭证账号')
  12.      A                                      COLHDG('凭证' '账号')
  13.      A            BKTLNO    R               REFFLD(@@TLNO)
  14.      A                                      TEXT('柜员号')
  15.      A                                      COLHDG('柜员号')
  16.      A            EQYNFG    R               REFFLD(@@YNFG)
  17.      A                                      TEXT('碰平标志')
  18.      A                                      COLHDG('碰平' '标志')
  19.      A            OPBRNO    R               REFFLD(@@BRNO)
  20.      A                                      TEXT('创建机构')
  21.      A                                      COLHDG('创建' '机构')
  22.      A            OPDPNO    R               REFFLD(@@DPNO)
  23.      A                                      TEXT('创建部门')
  24.      A                                      COLHDG('创建' '部门')
  25.      A            OPTLNO    R               REFFLD(@@TLNO)
  26.      A                                      TEXT('创建柜员')
  27.      A                                      COLHDG('创建' '柜员')
  28.      A            OPWKDT    R               REFFLD(@@WKDT)
  29.      A                                      TEXT('创建日期')
  30.      A                                      COLHDG('创建' '日期')
  31.      A            OPWKTM    R               REFFLD(@@WKTM)
  32.      A                                      TEXT('创建时间')
  33.      A                                      COLHDG('创建' '时间')
  34.      A            MTBRNO    R               REFFLD(@@BRNO)
  35.      A                                      TEXT('修改机构')
  36.      A                                      COLHDG('修改' '机构')
  37.      A            MTDPNO    R               REFFLD(@@DPNO)
  38.      A                                      TEXT('修改部门')
  39.      A                                      COLHDG('修改' '部门')
  40.      A            MTTLNO    R               REFFLD(@@TLNO)
  41.      A                                      TEXT('修改柜员')
  42.      A                                      COLHDG('修改' '柜员')
  43.      A            MTWKDT    R               REFFLD(@@WKDT)
  44.      A                                      TEXT('修改日期')
  45.      A                                      COLHDG('修改' '日期')
  46.      A            MTWKTM    R               REFFLD(@@WKTM)
  47.      A                                      TEXT('修改时间')
  48.      A                                      COLHDG('修改' '时间')
  49.      A            BKSTCD    R               REFFLD(@@STCD)
  50.      A                                      TEXT('记录状态')
  51.      A                                      COLHDG('记录' '状态')
  52.      A          K BKBRNO
  53.      A          K BKBXNO
复制代码
从上面的例子可以看出,PF定义中首先要REF(DICT)引入数据字典,然后定义具体字段时候,REFFLD指明数据字典的基础类型字段。注意的是命名规范要求,具体字段的后4位必须和数据字典基础字段名字一致。这样在程序中可以轻松认出是哪种类型(这就是应用型匈牙利命名法,有关匈牙利命名法的误解和讨论参见《软件随想录 -- Joel on software》的第23章“让错误的代码显而易见”)。不同的PF字段名字可能会一样,因此在RPG程序中需要QUALIFIED防止重叠,这在后续“命名限定”时候再介绍。

    那么如果在RPG程序中需要参照数据字典定义变量该怎么写呢。这里就需要使用一个新的关键字TEMPLATE,它可以引入一个PF结构但并不分配存储空间。同样让例子程序说话。

  1.      **********************************************************************
  2.      **公共结构
  3.      **引入数据字典
  4.      DDICT           E DS                  EXTNAME(DICT) QUALIFIED TEMPLATE
  5.      *************************
  6.      **临时变量结构
  7.      DD_VARS           DS
  8.      **定义参照数据字典(LIKE)的变量 
  9.      D V_BKBRNO                            LIKE(DICT.@@BRNO)
  10.      D V_EQYNFG                            LIKE(DICT.@@YNFG)
  11.      **定义无法参照的临时变量 
  12.      D V_NUM                          5P 0
  13.      **********************************************************************
复制代码
先是D段定义了DICT,引入外部PF结构DICT,并且是QUALIFIED限定为以点访问字段,TEMPLATE指明只声明不分配空间,然后定义变量时就可以用LIKE了。

    数据字典除了定义了字段类型长度,还对枚举项列表进行了管理。比如数据字典例子中,@@YNFG就是一个枚举,1表示是,0表示否。在尾箱主表的EQYNFG碰平标志就是用了这个枚举。在程序中如果写0,1的话,既不便于阅读程序,也不方便枚举值的变化。因此,Firebird中根据DICT自动生成了ENUM这个枚举列表,这样RPG程序中就可以用YNFG_YES代表1,YNFG_NO代表0。摘录DSCPPGM/ENUM这个RPGLE源码部分如下。

  1.      **BEGIN***************************************************************
  2.      **程序名称:数据字典枚举值                                         *
  3.      **功能描述:数据字典枚举值常量定义                                 *
  4.      **                                                                   *
  5.      **版本:2014-11-08_1                                               *
  6.      **                                                                   *
  7.      **设计人员:PACMAN          开发人员:AUTOGEN                    *
  8.      **设计日期:2011-08-30      开发日期:2011-08-30                 *
  9.      **                                                                   *
  10.      **END*****************************************************************
  11.      **
  12.       *是否标志:是YES
  13.      D YNFG_YES        C                   CONST('1')
  14.       *是否标志:否NO
  15.      D YNFG_NO         C                   CONST('0')
  16.       *
  17.       *借贷标志:借方DEBIT
  18.      D DCFG_DEBIT      C                   CONST('1')
  19.       *借贷标志:贷方CREDIT
  20.      D DCFG_CREDT      C                   CONST('2')
  21.       *
  22.       *记录状态:正常NORMAL
  23.      D STCD_NORML      C                   CONST('1')
  24.       *记录状态:删除ABNORMAL
  25.      D STCD_ABNML      C                   CONST('2')
  26.       *
复制代码
在RPG程序中,通过D/COPY DSCPPGM,ENUM 即可引入数据字典枚举常量。之后就可以 EVAL V_EQYNFG=YNFG_YES 这么写了。

论坛徽章:
5
2015亚冠之阿尔艾因
日期:2015-08-10 02:23:34操作系统版块每日发帖之星
日期:2015-08-13 06:20:002015七夕节徽章
日期:2015-08-21 11:06:1715-16赛季CBA联赛之山西
日期:2016-04-13 02:36:59操作系统版块每日发帖之星
日期:2016-04-14 06:20:00
28 [报告]
发表于 2016-02-22 00:09 |只看该作者
因为参与新的核心系统项目工作紧张,并且前一阵身体抱恙,所以有一段时间没有更新了。不过有时间还是会坚持完成这个系列连载。

剩余还有几篇内容,这里先预告一下,也以免自己忘记:
13.命名限定。主要介绍RPGLE程序中文件的TEMPLATE限定写法,QUALIFIED关键字,以及EVAL-CORR结构同名字段赋值语句。
14.异常处理。介绍AS400编程中的异常捕捉处理,主要包括RPGLE语言的文件异常INFDS结构,程序异常PSDS结构,程序异常处理程序*PSSR和文件异常处理程序,(E)语句和%ERROR处理,以及JOB出现MSGW后抽取MSGW原因和上层MSGW捕获。另外还附带如何在程序中获取PF文件的中文名,可用于返回错误说明报文的组织哦。
15.面向对象实现。介绍Firebird核心系统中面向对象的使用场景,以及联机交易平台根据交易码进行交易流程调度的运行中解析,模拟多态的实现方法。
16.交易和组件的写法。以实际的样例模版程序,展示交易和组件的文件组织结构和程序写法,之前介绍的各种技术方法,是如何组合到真正的程序中的。

论坛徽章:
5
2015亚冠之阿尔艾因
日期:2015-08-10 02:23:34操作系统版块每日发帖之星
日期:2015-08-13 06:20:002015七夕节徽章
日期:2015-08-21 11:06:1715-16赛季CBA联赛之山西
日期:2016-04-13 02:36:59操作系统版块每日发帖之星
日期:2016-04-14 06:20:00
29 [报告]
发表于 2016-04-04 18:23 |只看该作者
*命名限定

在以往的RPG程序中,很让人头疼的一点就是字段的来源不直观,以及同名字段的相互影响。在程序中,字段可以是通过F表定义文件而引入的记录格式字段,也可以是EXTNAME引入的参照外部文件形成的结构字段,还可以是DS定义的字段以及直接用D定义的变量。如果考虑到有/COPY引入DS定义的情况,通常会造成在程序中看到一个字段名,一下子不清楚到底是在哪里定义的。如果文件、DS结构内遇到字段名相同,程序会认定同名字段为同一块内存空间,字段的值同步变动,这给程序查错带来了很大的困扰。
现代的程序设计语言中,已经注意到了全局名字空间污染的问题,对于结构内的字段,通常采用“结构名.字段名”的方式表示。RPGLE的自由格式扩展了语法的长度限制,消除了写法上的障碍,因此现在全面采用这种写法已经没有问题了。下面详细介绍一下QUALIFIED关键字的用法。

F表定义文件中使用QUALIFIED的方式。
表ACNTLMF的定义摘选如下。

  1.      A                                      UNIQUE
  2.      A                                      REF(DICT)
  3.      A          R RACNTLMF
  4.      A            BKTLNO    R               REFFLD(@@TLNO)
  5.      A                                      TEXT('柜员号')
  6.      A                                      COLHDG('柜员号')
  7.      A            BKTLTP    R               REFFLD(@@TLTP)
  8.      A                                      TEXT('柜员类型')
  9.      A                                      COLHDG('柜员' '类型')
  10.      A            TLPCNM    R               REFFLD(@@PCNM)
  11.      A                                      TEXT('柜员姓名')
  12.      A                                      COLHDG('柜员' '姓名')
  13.      A            BKBRNO    R               REFFLD(@@BRNO)
  14.      A                                      TEXT('机构号')
  15.      A                                      COLHDG('机构号')
  16.      A          K BKTLNO
复制代码
RPGLE程序中写法示意如下。

  1.      FACNTLMF   UF A E           K DISK    COMMIT QUALIFIED
  2.      **
  3.      DR_CNTLMF1        DS                  LIKEREC(ACNTLMF.RACNTLMF:*INPUT)
  4.      DR_CNTLMF2        DS                  LIKEREC(ACNTLMF.RACNTLMF:*OUTPUT)
  5.      DK_CNTLMF         DS                  LIKEREC(ACNTLMF.RACNTLMF:*KEY)
  6.      **
  7.       /FREE
  8.          CLEAR  R_CNTLMF1;
  9.          CLEAR  K_CNTLMF;
  10.          K_CNTLMF.BKTLNO = TLADF1.BKTLNO;
  11.          CHAIN  K_CNTLMF  ACNTLMF.RACNTLMF  R_CNTLMF1;
  12.          CLEAR  R_CNTLMF2;
  13.          EVAL-CORR  R_CNTLMF2 = R_CNTLMF1;
  14.          R_CNTLMF2.BKPCNM = TLADF1.BKPCNM;
  15.          UPDATE  ACNTLMF.RACNTLMF  R_CNTLMF2;
  16.        /END-FREE
复制代码
那么,程序中由于QUALIFIED的存在,记录格式名必须加上文件名限定,并且不能再在程序中直接使用文件中的字段,必须使用自由格式的读写方式,使文件操作结果关联一个指定的DS结构,这个结构是以LIKEREC方式给出。如上面的代码所示,根据ACNTLMF,LIKEREC定义了3个DS结构。*KEY定义的K_CNTLMF用于取代固定格式的KLIST,作为文件操作语句的键值定位,会选取文件定义中的所有KEY字段。*INPUT定义的R_CNTLMF1可用于READ,CHAIN等读取语句存放结果。*OUTPUT定义的R_CNTLMF2可用于UPDATE,WRITE等写入语句的数据来源。V6R1中,*INPUT和*OUTPUT不能通用,给写程序带来了一定的麻烦,READ以后的UPDATE,必须用EVAL-CORR从*INPUT往*OUTPUT赋值(EVAL-CORR前面介绍过,是在两个不同结构间同名字段的赋值)。在V7R2中,放松了这个限制,可以用*ALL定义既可用于读取又可用于写入的结构,就不需要赋值了。注意使用LIKEREC定义的结构,默认均为QUALIFIED方式DS,使用“结构名.字段名”的写法。

DS结构定义中使用QUALIFIED的方式。
DS结构使用了QUALIFIED方式,则使用“结构名.字段名”的写法。如果DS本身是用LIKEREC,LIKEDS定义的,那么系统对这种参照的DS默认必须是QUALIFIED的。其他的DS定义或者EXTNAME的,可以写明QUALIFIED关键字来达到效果。
以下面的代码片段为例。

  1.      **引入数据字典
  2.      DDICT           E DS                  EXTNAME(DICT) QUALIFIED TEMPLATE
  3.      DC_NBTLADF2       C                   CONST(50)
  4.      DT_TLADF2       E DS                  EXTNAME(SCNTLADF2) QUALIFIED TEMPLATE
  5.      DTLADF1         E DS                  EXTNAME(SCNTLADF1) QUALIFIED
  6.      DTLADF2           DS                  QUALIFIED
  7.      D RECD                                LIKE(DICT.@@RECD)
  8.      D ARR                                 DIM(C_NBTLADF2) LIKEDS(T_TLADF2)
  9.      **程序原型定义
  10.      DSCNTLAD          PR                  EXTPGM('SCNTLAD')
  11.      D TLADF1                              LIKEDS(TLADF1)
  12.      D TLADF2                              LIKEDS(TLADF2)
复制代码
这里定义了SCNTLAD的两个参数DS,一个是单个结构TLADF1,另一个是带实际记录条数的数组复合DS,TLADF2.RECD是实际条数,TLADF2.ARR是DIM数组。这里值得注意的是TEMPLATE关键字的使用。在DS定义中,如果使用了TEMPLATE,那么程序不会为这个定义的DS分配实际的存储空间,而只是用作编译期定义使用。数据字典定义DICT,以及作为数组单个结构参照的T_TLADF2,正好非常符合这个用法。

在RPGLE自由格式中,转换成命名限定的写法,是有必要的。这避免了以前固定格式中用的很多的PREFIX和RENAME,使得程序中的字段更可读。
另外要提一下,在开发过程中,V6R1版本有一个PTF补丁问题,导致TEMPLATE编译造成编译器软件错误。这时候可以用BASED(@)代替TEMPLATE,意为指定为空指针,即无需分配空间。

论坛徽章:
5
2015亚冠之阿尔艾因
日期:2015-08-10 02:23:34操作系统版块每日发帖之星
日期:2015-08-13 06:20:002015七夕节徽章
日期:2015-08-21 11:06:1715-16赛季CBA联赛之山西
日期:2016-04-13 02:36:59操作系统版块每日发帖之星
日期:2016-04-14 06:20:00
30 [报告]
发表于 2016-04-05 02:25 |只看该作者
本帖最后由 pacman2000 于 2016-04-05 02:27 编辑

*异常处理

在核心系统开发和运行中,异常处理是特别重要的一环。异常的规划,对于系统的表现能力和查错能力有着巨大的作用,仔细的设计是十分值得的。
之前的报文格式中,提到Firebird的返回码,是10位STRING。如果交易正常,返回码为'OK',如果出错,则为错误代码。如果为AS400系统级错误,则为F+系统错误代码,如FCPF3282。如果是应用错误,则为E+应用错误代码,如ECNBRNORL。另外,在返回报文中,还返回出错的程序源文件名,以及如果是系统错误,要在日志中记录出错的代码行号,访问的物理文件或者逻辑文件名等备查。

应用部分的出错信息处理比较简单。Firebird没有使用MSGF,而是普通的PF定义错误代码表,里面使用&1到&9用于文本的替换。应用的错误代码根据模块适用范围,定义为E+[一级模块]+[二级模块]+错误代码,通用的错误代码模块名可以省略。例如,EALEX表示“&1中&2记录已经存在”,ECNCMATDC表示“账户的余额性质&1和余额方向&2不符”。在RPGLE程序中报应用错误的示例如下。

  1.       /FREE
  2.          CHAIN  K_CNTLMF  ACNTLMF.RACNTLMF  R_CNTLMF1;
  3.          IF  %FOUND(ACNTLMF);                //柜员已存在报错
  4.            PMG.BKMSID = 'EALEX';
  5.            A_MSDS(1) = C_MGTLMF;
  6.            A_MSDS(2) = C_MGTLNO + TLADF1.BKTLNO;
  7.            EXSR  #ERR;
  8.          ENDIF;
  9.        /END-FREE
  10.      **********************************************************************
  11.      ** #ERR 取错误信息
  12.      **********************************************************************
  13.      C     #ERR          BEGSR
  14.      **
  15.      C                   EVAL      PMG.MSFLNM = PG_SRCMBR
  16.      C                   IF        PMG.BKMSDS = *BLANKS
  17.      C                   CALL      'SCNCMMG'
  18.      C                   PARM                    PMG
  19.      C                   PARM                    A_MSDS
  20.      C                   ENDIF
  21.      C                   EXSR      #EXIT
  22.      **
  23.      C                   ENDSR
  24.      **********************************************************************
复制代码
其中PMG结构保存了错误代码,报错文件名,错误描述,在#ERR这个公用过程中,调用了SCNCMMG程序,以A_MSDS数组对&1到&9进行了替换,结果放在PMG.BKMSDS中,层层传递到最外层主控程序。PMG.MSFLNM使用的是PSDS结构,这个在接下来会有介绍。还记得V7R1中的新增内置函数%SCANRPL吗,用于错误信息替换正合适,V6R1就只能自己写一大段处理程序了。

相对于应用报错的写法,RPGLE程序中系统错误的捕获和处理就有技巧得多。要做到使得应用程序简单,又不缩减错误信息,还要不遗漏所有出错场景处理,并不容易。在RPGLE程序中,处理异常通常有4种方法,有置出错指示器,(E)加%ERROR内置函数,设置错误处理过程,MONITOR和ON-ERROR语句组。前两种是以往系统常见的写法,但是应用程序就不好看,大段的系统错误处理把正常逻辑都淹没了。因此,Firebird选用了对应用最透明的错误处理过程写法,达到绝大多数情况无需应用关注即可捕获异常组织错误信息,如果有特殊处理,则可结合(E)加%ERROR使用,满足特殊要求。下面分别详细介绍程序错误和文件错误的错误处理过程。

程序错误的捕获和报错,在RPGLE中使用的是PSDS自动结构,以及*PSSR异常处理过程。即在遇到错误时,系统自动会设置PSDS结构内容,并调用*PSSR过程。

  1.      **程序状态结构
  2.      DRPGPSDS         SDS
  3.      D PG_MAINPROC             1     10A
  4.      D PG_STATUS              11     15A
  5.      D PG_PRVSTAT             16     20A
  6.      D PG_SRCLINE             21     28A
  7.      D PG_ROUTINE             29     36A
  8.      D PG_PARMNUM             37     39S 0
  9.      D PG_MSID                40     46A
  10.      D PG_PGMLIB              81     90A
  11.      D PG_EXCPDT              91    170A
  12.      D PG_LSERRFL            175    184A
  13.      D PG_JOBDATE            191    198A
  14.      D PG_FLINFO             209    243A
  15.      D PG_JOBNAME            244    253A
  16.      D PG_JOBUSER            254    263A
  17.      D PG_JOBNUM             264    269S 0
  18.      D PG_RUNDATE            276    281S 0
  19.      D PG_RUNTIME            282    287S 0
  20.      D PG_SRCFILE            304    313A
  21.      D PG_SRCLIB             314    323A
  22.      D PG_SRCMBR             324    333A
  23.      D PG_PGMNAME            334    343A
  24.      D PG_MODNAME            344    353A
  25.      D PG_SRCLNADD           354    355B 0
  26.      D PG_FLILNADD           356    357B 0
  27.      D PG_USRPRF             358    367A
  28.      D PG_EXTERR             368    371I 0
  29.      **********************************************************************
  30.      **特殊变量
  31.      **是否执行过错误处理程序
  32.      DPG_ERYNFG        S                   LIKE(DICT.@@YNFG) INZ(YNFG_NO)
  33.      **********************************************************************
  34.      ** *PSSR程序异常处理
  35.      **********************************************************************
  36.      C     *PSSR         BEGSR
  37.      **
  38.      C                   IF        PG_ERYNFG = YNFG_NO
  39.      C                   EVAL      PG_ERYNFG = YNFG_YES
  40.      C                   EVAL      PMG.MSFLNM = PG_SRCMBR
  41.      C                   EVAL      PMG.MSCDLN = PG_SRCLINE
  42.      C                   EVAL      PMG.BKMSID = 'F' + PG_MSID
  43.      C                   EVAL      PMG.BKMSDS = PG_EXCPDT
  44.      C                   EVAL      PMG.OTMSDS = PG_STATUS+' '+PG_PGMLIB+' '+
  45.      C                                          PG_PGMNAME
  46.      C                   EXSR      #EXIT
  47.      C                   ENDIF
  48.      **
  49.      C                   ENDSR
  50.      **********************************************************************
复制代码
注意这里为了简化,PSDS没有定义为QUALIFIED。PG_ERYNFG的作用,是为了防止错误处理程序的多次重入(避免错误程序中又抛出系统异常,导致无限递归)。
*PSSR过程中,通过结合PSDS结构,直接设置了系统异常错误代码(前面加了'F'),出错源文件名,出错源码行号,异常信息,以及附加信息,做到了报错信息组织的全自动化。

文件错误的捕获和报错,相对就复杂一些。在RPGLE中使用的是INFDS自动结构,以及INFSR异常处理过程。即在遇到错误时,系统根据F表定义文件时指定的名字,自动会设置INFDS结构内容,并调用INFSR过程。但是,这里有特例,如果在F表时就有错误(比如自动打开的文件不存在),那么仍然会执行系统默认异常处理而造成MSGW,这时候就需要外层主控进行捕获处理了。示例代码节选如下。

  1.      FCCNTLCA   IF   E           K DISK    INFSR(#FLEX) INFDS(S_CNTLCA)
  2.      F                                     QUALIFIED
  3.      FACNTLTC   UF A E           K DISK    COMMIT QUALIFIED
  4.      F                                     INFSR(#FLEX) INFDS(S_CNTLTC)
  5.      **********************************************************************
  6.      **记录被锁错误信息
  7.      DC_MGLOCK         C                   CONST('记录被锁')
  8.      **文件状态结构
  9.      DFILESDS          DS                  BASED(FILEDSP)
  10.      D FL_FILE                 1      8A
  11.      D FL_OPNIND               9      9A
  12.      D FL_EOFIND              10     10A
  13.      D FL_STATUS              11     15S 0
  14.      D FL_OPCODE              16     21A
  15.      D FL_ROUTINE             22     29A
  16.      D FL_SRCLINE             30     37A
  17.      D FL_RECORD              38     45A
  18.      D FL_MSID                46     52A
  19.      D FL_SRCLNADD            77     78B 0
  20.      D FL_ODPTYPE             81     82A
  21.      D FL_FILENAME            83     92A
  22.      D FL_LIBRARY             93    102A
  23.      D FL_SPLFILE            103    112A
  24.      D FL_SPLLIB             113    122A
  25.      D FL_RCDLEN             125    126I 0
  26.      D FL_KEYLEN             127    128I 0
  27.      D FL_MEMBER             129    138A
  28.      D FL_TYPE               147    148I 0
  29.      D FL_RCDNUM             156    159I 0
  30.      D FL_SPLNUM             160    163I 0
  31.      D FL_OVERFLOW           188    189I 0
  32.      D FL_BASEDMBRS          211    212I 0
  33.      D FL_OPENID             214    215B 0
  34.      D FL_RCDFMTLEN          216    217I 0
  35.      D FL_CCSID              218    219I 0
  36.      D FL_FBSIZE             367    370I 0
  37.      D FL_KEYNUM             387    388I 0
  38.      D FL_FBKEYLEN           393    394I 0
  39.      D FL_MBRNUM             395    396I 0
  40.      D FL_RRN                397    400I 0
  41.      D FL_KEY                401   2400A
  42.      **文件状态结构指针
  43.      DFILEDSP          S               *
  44.      **
  45.      DS_CNTLCA         DS                  LIKEDS(FILESDS)
  46.      DR_CNTLCA         DS                  LIKEREC(CCNTLCA.RCCNTLCA:*INPUT)
  47.      DK_CNTLCA         DS                  LIKEREC(CCNTLCA.RCCNTLCA:*KEY)
  48.      **
  49.      DS_CNTLTC         DS                  LIKEDS(FILESDS)
  50.      DR_CNTLTC1        DS                  LIKEREC(ACNTLTC.RACNTLTC:*INPUT)
  51.      DR_CNTLTC2        DS                  LIKEREC(ACNTLTC.RACNTLTC:*OUTPUT)
  52.      DK_CNTLTC         DS                  LIKEREC(ACNTLTC.RACNTLTC:*KEY)
  53.      **
  54.       /FREE
  55.            CLEAR  R_CNTLCA;                //取角色表信息
  56.            CLEAR  K_CNTLCA;
  57.            FILEDSP = %ADDR(S_CNTLCA);
  58.            K_CNTLCA.BKCHGN = TLADF3.ARR(V_RECD1).BKCHGN ;
  59.            CHAIN  %KDS(K_CNTLCA)  CCNTLCA.RCCNTLCA  R_CNTLCA ;
  60.            IF  NOT %FOUND(CCNTLCA);
  61.              PMG.BKMSID = 'ENTRD';
  62.              A_MSDS(1) = C_MGTLCA;
  63.              A_MSDS(2) = C_MGCHGN + TLADF3.ARR(V_RECD1).BKCHGN;
  64.              EXSR #ERR;
  65.            ENDIF;
  66.        /END-FREE
  67.      **********************************************************************
  68.      ** #FLEX文件异常处理
  69.      **********************************************************************
  70.      C     #FLEX         BEGSR
  71.      **
  72.      C                   EVAL      PMG.MSFLNM = PG_SRCMBR
  73.      C                   EVAL      PMG.MSCDLN = FL_SRCLINE
  74.      C                   EVAL      PMG.BKMSID = 'F' + FL_MSID
  75.      C                   IF        FL_STATUS = 1218
  76.      C                   CALL      'GETOBJTXT'
  77.      C                   PARM      FL_LIBRARY    V_FLEXLIB        10
  78.      C                   PARM      FL_FILENAME   V_FLEXOBJ        10
  79.      C                   PARM      '*FILE'       V_FLEXTYP         7
  80.      C                   PARM      *BLANKS       V_FLEXTEXT       50
  81.      C                   EVAL      PMG.BKMSDS = %TRIM(V_FLEXTEXT) +
  82.      C                                          %TRIM(FL_FILENAME) + '.' +
  83.      C                                          %TRIM(FL_MEMBER) + C_MGLOCK +
  84.      C                                          ',' + PG_EXCPDT
  85.      C                   ELSE
  86.      C                   EVAL      PMG.BKMSDS = PG_EXCPDT
  87.      C                   ENDIF
  88.      C                   EVAL      PMG.OTMSDS = PG_FLINFO
  89.      C                   EXSR      #EXIT
  90.      **
  91.      C                   ENDSR
  92.      **********************************************************************
复制代码
这段就需要解释一下了。首先,F表定义的多个文件,INFSR可以共用一个,但INFDS系统要求是不能共享的,必须每个文件定义一份。为了#FLEX的通用写法,因此对FILESDS指定了一个指针FILEDSP,每次在文件操作前,程序中像这样FILEDSP = %ADDR(S_CNTLCA)设置指针指向要操作文件对应的FILESDS结构。然后如果后续的文件操作出错,FILEDSP指向被自动填充的文件结构,系统执行#FLEX过程,类似程序异常一样做到了报错信息组织的全自动化。这里特别注意对于错误状态1218,即文件记录被锁定超时,抛出的错误带经过GETOBJTXT转换的PF中文注释,形如“柜员角色表CCNTLCA.CCNTLCA记录被锁”,对渠道端错误信息展示更友好。

  1. /*BEGIN***************************************************************/
  2. /*程序名称:GETOBJTXT                                              */
  3. /*功能描述:                                                       */
  4. /*                                                                   */
  5. /*设计人员:PACMAN          开发人员:PACMAN                     */
  6. /*设计日期:2014-11-27      开发日期:2014-11-27                 */
  7. /*-------------------------------------------------------------------*/
  8. /*维护人员:                                                       */
  9. /*维护日期:                                                       */
  10. /*维护内容:                                                       */
  11. /*                                                                   */
  12. /*END*****************************************************************/

  13.              PGM        PARM(&LIBNM &OBJNM &OBJTP &TEXT)
  14.              INCLUDE    SRCMBR(CLHD) SRCFILE(DSCPPGM)
  15.              DCL        VAR(&LIBNM) TYPE(*CHAR) LEN(10)
  16.              DCL        VAR(&OBJNM) TYPE(*CHAR) LEN(10)
  17.              DCL        VAR(&OBJTP) TYPE(*CHAR) LEN(7)
  18.              DCL        VAR(&TEXT) TYPE(*CHAR) LEN(50)
  19.              IF         COND(&LIBNM *EQ ' ') THEN(DO)
  20.              CHGVAR     VAR(&LIBNM) VALUE('*LIBL')
  21.              ENDDO
  22.              RTVOBJD    OBJ(&LIBNM/&OBJNM) OBJTYPE(&OBJTP) TEXT(&TEXT)
  23.              MONMSG     MSGID(CPF0000)
  24. ENDPGM:     ENDPGM
复制代码
在真正的组件和交易程序中,PSDS,FILESDS结构都封装到/COPY的PGDS中,#ERR,*PSSR,#FLEX都封装到/COPY的PGCM中,应用程序篇幅就简化了很多。

通过*PSSR和#FLEX解决了大部分问题,但最后还遗留了一点会造成MSGW的未能捕获的异常。这种情况显然也不能放过,就需要更高级的主控程序处理了。联机交易主控程序MONSVR会扫描监视真正执行的JOBSVR,结束掉MSGW的JOBSVR并记录出错信息,然后重新启动一个新的代替。MONSVR代码片段如下。

  1.   /*保存最大启动进程数*/
  2.   arr[0]=num;
  3.   freenum=num;

  4.     /*监测子进程是否正常*/
  5.     for (i=arr[0];i>=1;i--)
  6.     {
  7.       if (arr[i]!=0)
  8.       {
  9.         char reason[3009];

  10.         memset(reason, 0, sizeof(reason));
  11.         rc=checkJob(arr[i], reason);
  12.         if (rc==1)
  13.         {
  14.           if (i<=num && new_reg_ver!=0)
  15.             writeLog("WARNING", JOBNAME, __FILE__, __LINE__,
  16.                      "任务S%05d%03d %d不存在", port, i, arr[i]);
  17.           else
  18.             debugLog(svrdebug, JOBNAME, __FILE__, __LINE__,
  19.                      "任务S%05d%03d %d已退出", port, i, arr[i]);
  20.           arr[i]=0;
  21.         }
  22.         else if (rc==2)
  23.         {
  24.           writeLog("INFO", JOBNAME, __FILE__, __LINE__,
  25.                    "任务S%05d%03d %d已出错:%s", port, i, arr[i],
  26.                    reason);
  27.           if (!svrdebug)
  28.           {
  29.             endJob(arr[i]);
  30.             arr[i]=0;
  31.           }
  32.         }
  33.         else if (rc==3)
  34.             freenum++;
  35.       }
  36.       if (i==arr[0] && arr[i]==0)
  37.         arr[0]--;
  38.     }

  39.     /*保存最大启动进程数*/
  40.     arr[0] = arr[0]>num ? arr[0] : num;
  41.     /*检验进程是否存在并启动*/
  42.     for (i=1;i<=num;i++)
  43.     {
  44.       if (arr[i]==0)
  45.       {
  46.         sprintf(jobname,"S%05d%03d",port,i);
  47.         sprintf(argstr, "%d %d %d %d %d %d", port, savedate,
  48.                 reg_ver, reg_num, i, bakmode);
  49.         pid=spawn(pathstr, 1, spawn_fdmap, &inherit, spawn_argv,
  50.                   spawn_envp);
  51.         if (pid<0)
  52.         {
  53.           writeLog("WARNING", JOBNAME, __FILE__, __LINE__, "重启%s出错%d",
  54.                    jobname, errno);
  55.           arr[i]=0;
  56.         }
  57.         else
  58.         {
  59.           arr[i]=pid;
  60.           debugLog(svrdebug, JOBNAME, __FILE__, __LINE__,
  61.                    "重启%s成功%d", jobname, pid);
  62.         }
  63.       }
  64.     }
复制代码
其中重要的是这么几点,checkJob()用于检查某个pid进程的状态,如果MSGW则获取详细出错信息到reason字符串中。endJob()用于结束一个pid进程。spawn()函数产生一个进程执行指定的程序。通过writeLog(),MONSVR将出错的进程和错误信息写到主控日志备查。下面是checkJob和endJob的代码。

  1. /*BEGIN***************************************************************/
  2. /*程序名称:JOBCTL                                                 */
  3. /*功能描述:任务相关的调用封装                                     */
  4. /*                                                                   */
  5. /*设计人员:PACMAN          开发人员:PACMAN                     */
  6. /*设计日期:2011-11-11      开发日期:2011-11-11                 */
  7. /*-------------------------------------------------------------------*/
  8. /*维护人员:                                                       */
  9. /*维护日期:                                                       */
  10. /*维护内容:                                                       */
  11. /*                                                                   */
  12. /*END*****************************************************************/

  13. #include <string.h>
  14. #include <sys/types.h>
  15. #include <qp0wpid.h>

  16. #include "dscppgm/cpyrgt_h"

  17. extern void ENDJOBCL(char *jobnb, char *usrnm, char*jobnm);
  18. extern void GETJOBCL(char *jobnb, char *usrnm, char*jobnm, char *rtcd,
  19.                      char *reason);

  20. /*返回1表示未找到,返回2表示MSGW*/
  21. int checkJob(pid_t pid, char *reason)
  22. {
  23.   QP0W_Job_ID_T jobinfo;
  24.   int ret;
  25.   char rtcd[1];

  26.   ret=Qp0wGetJobID(pid, &jobinfo);
  27.   if (ret!=0)
  28.     return 1;
  29.   GETJOBCL(jobinfo.jobnumber, jobinfo.username, jobinfo.jobname, rtcd,
  30.            reason);
  31.   return rtcd[0]-'0';
  32. }

  33. int endJob(pid_t pid)
  34. {
  35.   QP0W_Job_ID_T jobinfo;
  36.   int ret;

  37.   ret=Qp0wGetJobID(pid, &jobinfo);
  38.   if (ret!=0)
  39.     return ret;
  40.   ENDJOBCL(jobinfo.jobnumber, jobinfo.username, jobinfo.jobname);
  41.   return 0;
  42. }

  43. int getJobname(pid_t pid, char *name)
  44. {
  45.   QP0W_Job_ID_T jobinfo;
  46.   int ret;

  47.   ret=Qp0wGetJobID(pid, &jobinfo);
  48.   if (ret!=0)
  49.     return ret;
  50.   strncpy(name, jobinfo.jobname, 10);
  51.   name[10]='\0';
  52.   return 0;
  53. }
复制代码
里面使用了GETJOBCL和ENDJOBCL这两个CL程序。代码如下。

  1. /*BEGIN***************************************************************/
  2. /*程序名称:GETJOBCL                                               */
  3. /*功能描述:获取指定任务的运行状态                                 */
  4. /*           RTCD   返回状态                                       */
  5. /*                  '0'    正常                                    */
  6. /*                  '1'    未找到                                  */
  7. /*                  '2'    MSGW                                      */
  8. /*                  '3'    TIMW或TIMA                              */
  9. /*           REASON MSGW原因,前7位为MSGID后3000字节为原因   */
  10. /*                                                                   */
  11. /*设计人员:PACMAN          开发人员:PACMAN                     */
  12. /*设计日期:2011-11-11      开发日期:2011-11-11                 */
  13. /*-------------------------------------------------------------------*/
  14. /*维护人员:                                                       */
  15. /*维护日期:                                                       */
  16. /*维护内容:                                                       */
  17. /*                                                                   */
  18. /*END*****************************************************************/

  19.              PGM        PARM(&JOBNUM &USRNAM &JOBN &RTCD &REASON)
  20.              INCLUDE    SRCMBR(CLHD) SRCFILE(DSCPPGM)
  21.              DCL        VAR(&JOBNUM) TYPE(*CHAR) LEN(6)
  22.              DCL        VAR(&USRNAM) TYPE(*CHAR) LEN(10)
  23.              DCL        VAR(&JOBN) TYPE(*CHAR) LEN(10)
  24.              DCL        VAR(&RTCD) TYPE(*CHAR) LEN(1)
  25.              DCL        VAR(&JOBINF) TYPE(*CHAR) LEN(225)
  26.              DCL        VAR(&JOBST) TYPE(*CHAR) LEN(10)
  27.              DCL        VAR(&JOBACTST) TYPE(*CHAR) LEN(4)
  28.              DCL        VAR(&MSGKEY) TYPE(*CHAR) LEN(4)
  29.              DCL        VAR(&MSGQUE) TYPE(*CHAR) LEN(10)
  30.              DCL        VAR(&MSGLIB) TYPE(*CHAR) LEN(10)
  31.              DCL        VAR(&MSGASP) TYPE(*CHAR) LEN(10)
  32.              DCL        VAR(&JOBNAM) TYPE(*CHAR) LEN(26)
  33.              DCL        VAR(&SENDER) TYPE(*CHAR) LEN(80)
  34.              DCL        VAR(&REASON) TYPE(*CHAR) LEN(3008)
  35.              DCL        VAR(&MSGID)  TYPE(*CHAR) LEN(7)
  36.              DCL        VAR(&MSGHLP) TYPE(*CHAR) LEN(3000)
  37.              CHGVAR     VAR(&RTCD) VALUE('0')
  38.              CHGVAR     VAR(&JOBNAM) VALUE(&JOBN *CAT &USRNAM *CAT +
  39.                           &JOBNUM)
  40.              CHGVAR     VAR(&SENDER) VALUE(&JOBNAM)
  41.              CALL       PGM(QUSRJOBI) PARM(&JOBINF X'000000D1' +
  42.                           'JOBI0200' &JOBNAM ' ')
  43.              MONMSG     MSGID(CPF3C51 FPF3C52 CPF3C53 CPF3C54 +
  44.                           CPF3C55) EXEC(DO)
  45.              CHGVAR     VAR(&RTCD) VALUE('1')
  46.              GOTO       CMDLBL(END)
  47.              ENDDO
  48.              CHGVAR     VAR(&JOBST) VALUE(%SST(&JOBINF 51 10))
  49.              CHGVAR     VAR(&JOBACTST) VALUE(%SST(&JOBINF 108 4))
  50.              IF         COND(&JOBACTST *EQ 'MSGW') THEN(DO)
  51.              CHGVAR     VAR(&RTCD) VALUE('2')
  52.              CHGVAR     VAR(&MSGKEY) VALUE(%SST(&JOBINF 192 4))
  53.              CHGVAR     VAR(&MSGQUE) VALUE(%SST(&JOBINF 196 10))
  54.              CHGVAR     VAR(&MSGLIB) VALUE(%SST(&JOBINF 206 10))
  55.              CHGVAR     VAR(&MSGASP) VALUE(%SST(&JOBINF 216 10))
  56.              RCVMSG     MSGQ(&MSGQUE) MSGTYPE(*INQ) RMV(*NO) +
  57.                         MSGKEY(&MSGKEY) SECLVL(&MSGHLP) +
  58.                         MSGID(&MSGID) +
  59.                         SENDER(&SENDER) SENDERFMT(*SHORT)
  60.              MONMSG     MSGID(CPF0000) EXEC(DO)
  61.              GOTO       CMDLBL(END)
  62.              ENDDO
  63.              CHGVAR     VAR(&REASON) VALUE(&MSGID *CAT ':' *CAT +
  64.                           &MSGHLP)
  65.              GOTO       CMDLBL(END)
  66.              ENDDO
  67.              IF         COND((&JOBACTST *EQ 'TIMW') *OR (&JOBACTST +
  68.                           *EQ 'TIMA')) THEN(DO)
  69.              CHGVAR     VAR(&RTCD) VALUE('3')
  70.              ENDDO
  71. END:
  72.              ENDPGM
复制代码

  1. /*BEGIN***************************************************************/
  2. /*程序名称:ENDJOBCL                                               */
  3. /*功能描述:结束指定任务                                           */
  4. /*                                                                   */
  5. /*设计人员:PACMAN          开发人员:PACMAN                     */
  6. /*设计日期:2011-11-11      开发日期:2011-11-11                 */
  7. /*-------------------------------------------------------------------*/
  8. /*维护人员:                                                       */
  9. /*维护日期:                                                       */
  10. /*维护内容:                                                       */
  11. /*                                                                   */
  12. /*END*****************************************************************/

  13.              PGM        PARM(&JOBNB &USRNM &JOBNM)
  14.              INCLUDE    SRCMBR(CLHD) SRCFILE(DSCPPGM)
  15.              DCL        VAR(&JOBNB) TYPE(*CHAR) LEN(6)
  16.              DCL        VAR(&USRNM) TYPE(*CHAR) LEN(10)
  17.              DCL        VAR(&JOBNM) TYPE(*CHAR) LEN(10)
  18.              ENDJOB     JOB(&JOBNB/&USRNM/&JOBNM) OPTION(*IMMED)
  19.              MONMSG     MSGID(CPF1321)
  20.              ENDPGM
复制代码
批处理的主控也是类似,使用GETJOBCL和ENDJOBCL进行批量任务分段JOB的监视和错误登记,这里就不再赘述了。

通过主控,RPGLE程序层层把关,以及统一的错误处理程序,Firebird减轻了应用开发人员的错误处理负担,并且让错误信息更为友好,尽可能的保存了错误的丰富信息,这对于系统的维护可以带来很大的帮助。而这些,正是系统设计时就需要重视并在整体架构里考虑,才能做好的。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP