pacman2000 发表于 2015-05-29 23:22

一个程序员在AS400银行核心系统开发中的技术总结

本帖最后由 pacman2000 于 2015-06-07 17:43 编辑

    随着国家的自主安全可控号召,AS400系统看来在银行IT逐渐退出了。因此很遗憾,重新规划开发的AS400新核心系统就告一段落。不过,在开发过程中,有不少技术值得总结共享,如果有谬误,或有更优方法,也请大家指正。
    开发中使用的技术,基本上都是参照IBM信息中心的官方文档,链接在这里:www-01.ibm.com/support/knowledgecenter/ssw_ibm_i/welcome?lang=zh


    *操作系统版本
    系统选择了V6R1的版本,ILE环境(从OPM转到ILE不需要解释了吧)。这是因为V6R1有一个重大提升:RPG程序内部结构的大小限制从64K提升到了16M。这让DS,数组更具实用性。例如,交易程序的PARM,通常是输入接口复合DS,输出接口复合DS,遇到数组时64K就不够用。V7R1,V7R2也有不少改变,特别是V7R2,完善了自由格式,可以让H,F,D等全部拥有了自由格式写法,并且不需要/FREE,/END-FREE标识符即可自由格式固定格式混用,真是不错的选择。当时详细分析了6.1,7.1,7.2的语法改进,下面介绍下不同版本在开发上的重要改进。

    7.2
    CL语言
    对字符串新增了6个函数,%CHECK,%CHECKR,%SCAN,%TRIM,%TRIML,%TRIMR。
    新增6个转换函数,%CHAR,%DEC,%INT,%UINT,%LOWER,%UPPER。
    新增2个大小函数,%LEN,%SIZE。
    是不是CL中处理方便多了?可惜在7.2才能用,6.1只能看看了。

    C/C++语言
    没有什么需要关注的改动。

    RPG语言
    放松对IO输入输出结构的要求。使用*ALL可以让LIKEREC结构可以用在读写等任何IO操作上。这点非常有用,以后介绍LIKEREC时候再详细比较。
    增加了CTL-OPT写法,用于自由格式H表定义。
    增加了DCL-F写法,用于自由格式F表定义。
    增加了DCL-C, DCL-DS, DCL-PI, DCL-PR, DCL-S写法,用于自由格式D表定义。
    增加了DCL-PROC写法,用于子过程定义。
    不再要求/FREE,/END-FREE,自由格式和固定格式可以任意混合编写。
    %SUBDT增加参数,使得可以指定返回几位数字,例如%SUBDT(MyDate:*YEARS:4)。
    timestamp类型可以保存秒以下12位小数(纳秒?),相应的%TIMESTAMP也增加了参数用于指定小数位数。
   

    7.1
    CL语言
    可以使用RTVCLSRC从目标对象来获取ILE CL的源代码了。不过要求编译时ALWRTVSRC参数指定为*YES。
    INT,UINT类型长度可以扩展到8字节。
    可以在编译程序时,用DBGENCKEY,使得只有输入正确的KEY,才能看到源码视图。(这个,嘿嘿)
    CL中的INCLUDE支持嵌套了。

    C/C++语言
    没有什么需要关注的改动。

    RPG语言
    SORTA和%LOOKUP可以依据DS结构的某一个字段,来排序或检索数组啦。例如:SORTA custDs(*).amount_owing; elem = %LOOKUP("K" : custDs(*).account_status);
    SORTA可以用(A),(D)来正序,逆序排序数组。
    增加了%SCANRPL函数,用于将一个字符串中的子串,替换为指定的字符串。(报错信息替换的神器啊,可惜V6R1不能用)
    可以在编译程序时,用DBGENCKEY,使得只有输入正确的KEY,才能看到源码视图。


    6.1
    CL语言
    增加CLOSE语句,可以关闭RCVF打开的文件,用于多次读取一个文件的场景。
    增加INCLUDE语句,包含另外CL一个源文件。

    C/C++语言
    没有什么需要关注的改动。

    RPG语言
    文件定义时候可以指定QUALIFIED,记录格式名变成MYFILE.MYFMT,避免文件内字段名字对程序全局字段的名字污染。但文件操作必须使用DS结构。这点稍后会详细介绍。
    EXTDESC和EXTFILE(*EXTDESC),使得可以编译期不再需要绑定文件描述,而使用字符串形式的'LIBNAME/FILENAME',运行期再去打开文件。
    EXTNAME可以使用字符串形式'LIBNAME/FILENAME',同样不需要编译器绑定文件。
    扩大DS,可变字符串等的最大限制,大小扩到16M。可变字符串的VARYING关键字允许指定2或4字节存放长度,%ADDR(varying : *DATA)可以获取到除去长度后的数据位置指针。
    DIM和OCCURS数组的最大限制,元素个数扩充到16M个,但总大小不超过16M。
    字符串常量大小扩到16k。
    TEMPLATE关键字可用于文件定义和结构定义,不占用存储空间,通常为了LIKEFILE,LIKE,LIKEDS使用。TEMPLATE DS可以有INZ定义,方便参照它的结构使用INZ(*LIKEDS)来初始化。

pacman2000 发表于 2015-05-29 23:41

本帖最后由 pacman2000 于 2015-05-30 11:08 编辑

    *COPYRIGHT设置
    让我们从一个比较无聊的技术开始吧,哈哈。COPYRIGHT就是编译成目标后,可以用DSPPGM,DSPMOD等,翻到版权页,显示的字符串。这个要怎么弄呢?考虑到系统有很多程序,因此采用了设置包含源文件的方式实现。


    RPGLE
    应用程序:H/COPY DSCPPGM,HEAD.
    HEAD源文件:HCOPYRIGHT('Firebird V2.1 corebanking system')
HDATFMT(*ISO) TIMFMT(*ISO) DATEDIT(*YMD)
.

    C/C++
    应用程序:#include "dscppgm/cpyrgt_h".
    CPYRGT_H源文件:#ifndef CPYRGT_H
#define CPYRGT_H
#pragma comment(copyright,"Firebird V2.1 corebanking system")
#endif.


    CLLE
    应用程序:    INCLUDE    SRCMBR(CLHD) SRCFILE(DSCPPGM).

    CLHD源文件:    COPYRIGHTTEXT('Firebird V2.1 corebanking system').


    但是,AS400的CL在COPYRIGHT处理上并不完美。RPG,C都可以设置DBCS中文字符串,CL带中文的话编译报错。DEBUG时,CL也会因为INCLUDE引入而导致没有普通视图。这一点,不知道高版本是否有改善,有环境的话可以测试验证一下。

pacman2000 发表于 2015-05-30 10:59

本帖最后由 pacman2000 于 2015-05-31 15:41 编辑

*数据区域DATA AREA的使用

数据区域DTAARA,是一小片存储对象,通常使用1-2k大小的CHAR类型。持久化的命名数据区域,可以在不同job间交互数据。在系统中使用了这种方式存放配置参数,方便做到不重启应用的动态刷新。另外还有几个特殊数据区域,常见的是*LDA,1k大小的job自有共享区域,可用于job内不同程序间的数据交互,起到全局空间的作用。*LDA不能持久化,随着job结束而消失,因此可以认为是一片job特有的内存,不需加锁访问,速度更快。

在程序中使用DTAARA比较简单,参照下面的例子。

C/C++#include <xxdtaa.h>//读写数据区域使用的头文件
...
_DTAA_NAME_T dtaa = {"MNAR      ", "*LIBL   "};//定义命名数据区域的名称,如果是LDA,则为{"*LDA      ", "          "}
QXXCHGDA(dtaa, DBG_START, sizeof(data), data);//从DBG_START位开始,写入字符串data内容到数据区域
QXXRTVDA(dtaa, 1, sizeof(dtaa_st), (char*)dtaa_s);//从第一位开始获取数据区域内容,存放到自定义的dtaa_st结构变量dtaa_s中
.

RPGLE
*LDA的例子   D*定义公共数据,使用*LDA,内容为TRAR,INHD,OTHD结构的组合
   DT_TRAR         E DS                  EXTNAME(TRAR) QUALIFIED TEMPLATE
   DT_INHD         E DS                  EXTNAME(INHD) QUALIFIED TEMPLATE
   DT_OTHD         E DS                  EXTNAME(OTHD) QUALIFIED TEMPLATE
   DS_LDA         UDS                  DTAARA(*LDA)QUALIFIED
   D TRAR                              LIKEDS(T_TRAR)
   D INHD                              LIKEDS(T_INHD)
   D OTHD                              LIKEDS(T_OTHD)
   ...
   C*读取LDA,并进行错误处理。LDA无需上锁。
   C                   IN(E)   S_LDA
   C                   IF      (%ERROR)
   C*读取失败
   C                   ENDIF
   ...
   C*写入LDA,并进行错误处理。LDA无需上锁。
   C                   OUT(E)    S_LDA
   C                   IF      %ERROR
   C*写入失败
   C                   ENDIF
.

命名DTAARA的例子   D*总长度256字节的数据区域BTAR,内容为5P0的变量MAXJOBS
   DS_BTAR          UDS         256    DTAARA('BTAR') QUALIFIED
   D MAXJOBS                        5P 0
   ...
   C*从BTAR获取MAXJOBS值
   C   *LOCK         IN      S_BTAR
   C                   UNLOCK    S_BTAR
.

在IN读取命名数据区域时,可以用*LOCK选择是否加锁。加锁以后,访问数据区域的其他程序就只能读不能写。如果执行到不加*LOCK的OUT,那么写完即释放锁。如果OUT加了*LOCK,那么锁将会继续保持。使用UNLOCK可以直接在不写入的情况下解除锁。


CLLE及命令行
系统提供了几个命令来操作数据区域。
CRTDTAARA,创建命名数据区域(*LDA不需创建),创建时也可指定初始值,如CRTDTAARA DTAARA(CUSTOMER) TYPE(*CHAR) LEN(100) VALUE('INIT') TEXT(’Customer name area’)
DSPDTAARA,显示或打印数据区域的内容,如DSPDTAARADTAARA(MYLIB/MYDTA)
RTVDTAARA,获取数据区域内容到CL变量,可以指定从第几位取多少位,如RTVDTAARADTAARA(*LDA (5,2)) RTNVAR(&VAL)
CHGDTAARA,与获取类似,写入数据区域,如果字符串长度不足补空格,如CHGDTAARADTAARA(*LDA (5,4)) VALUE('OK'),实际5-8位写入'OK'
DLTDTAARA,删除一个命名数据区域,如DLTDTAARADTAARA(MYLIB/MYDTA)

pacman2000 发表于 2015-06-07 22:40

本帖最后由 pacman2000 于 2015-06-07 22:43 编辑

*用户空间USER SPACE的使用

用户空间USER SPACE,是AS400所提供进程间通讯的另一个机制,可以保存一块较大的持久化数据。常用的三大机制,DATA AREA在核心系统中用于控制参数的设置,例如批处理平台并发数控制,联机平台各种标志,以及全局内容在*LDA的跨PGM保存传递。DATA QUEUE可用于进程间传递消息数据,因核心系统设计上处理进程的隔离性而没有用到。USER SPACE则用于实现输出报文的灵活性,保存核心系统交易处理过程中,可能产生返回的凭证打印报文接口段。
USER SPACE最大长度可达16M,必须命名以持久化。由于在系统中用于暂存一笔联机交易需要返回的附加报文数据,因此对持久化其实没有要求,但性能要求很高,又不能互相影响。于是将USER SPACE建立在QTEMP中。这样不同JOB各自有一份使用,而且QTEMP数据系统应该是在内存中建立的,恰好符合要求。

那么程序中如何访问呢,可以看实际使用的例子。

创建USER SPACE,名称为QTEMP/JOBADDUS。
             PGM      PARM(&SPCLEN)
             INCLUDE    SRCMBR(CLHD) SRCFILE(DSCPPGM)
             DCL      VAR(&PASSPCNM) TYPE(*CHAR) LEN(20)
             DCL      VAR(&SPCATTR) TYPE(*CHAR) LEN(10)
             DCL      VAR(&SPCLEN) TYPE(*UINT) LEN(4)
             DCL      VAR(&SPCVALUE) TYPE(*CHAR) LEN(1)
             DCL      VAR(&SPCAUTH) TYPE(*CHAR) LEN(10)
             DCL      VAR(&SPCTEXT) TYPE(*CHAR) LEN(50)
             CHGVAR   VAR(&PASSPCNM) VALUE('JOBADDUSQTEMP   ')
             CHGVAR   VAR(&SPCAUTH) VALUE('*CHANGE')
             CHGVAR   VAR(&SPCTEXT) VALUE('附加报文用户空间')
             CHGVAR   VAR(&SPCVALUE) VALUE(X'00')
             CALL       PGM(QUSCRTUS) PARM(&PASSPCNM &SPCATTR +
                        &SPCLEN &SPCVALUE &SPCAUTH &SPCTEXT)
             MONMSG   MSGID(CPF9870)
             ENDPGM
.

因为JOB结束时QTEMP自动释放,而JOB不结束的话USER SPACE可以重复使用,因此不需要人为去删除这个USER SPACE。

使用这个USER SPACE,会有两种情况。
一是交易运行过程中,产生需要附加的打印接口数据,这时候调用下面的程序记录进USER SPACE,注意,同时将已附加长度写进*LDA记录。另一种情况是组返回报文时,读取数据打包进报文,这个用的是后续的USER SPACE指针操作。
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>

#include "dscppgm/cpyrgt_h"
#include "config_h"
#include "msginfo_h"
#include "opdtaac_h"

extern void JOBUSPTR(char **usptr);
extern void fill_str(char *buf, const char *str, int len);
extern int get_output_arrays_count(oa_otct*);
extern int set_otlen(int len);

/* parm1 MESG
   parm2 ITNAME10A
   parm3 ITCNT   2Bhostorder
   parm4 ITPLEN2Bhostorder
   parm5 ITBUF   ITCNT*ITPLEN
*/
int main(int argc, char **argv)
{
oa_otct oa;
rpg_msg_info *rpginfo;
int olen, tlen;
unsigned short pcnt, plen, tmp;
char *obuf, *itbuf;
char *itnm;
char linebuf;

if (argc<2)
    return -1;
rpginfo=(rpg_msg_info *)argv;
if (argc!=6)
{
    fill_str(rpginfo->msgflnm, __FILE__, FILENM_LEN);
    sprintf(linebuf, "%6d", __LINE__);
    fill_str(rpginfo->msgline, linebuf, LINENO_LEN);
    fill_str(rpginfo->msgid, "EPLMNOPCK ", MSG_ID_LEN);
    fill_str(rpginfo->msgtext, "组附加报文接口不正确", MSG_TEXT_LEN);
    fill_str(rpginfo->msgotds, "", MSG_TEXT_LEN);
    return -1;
}
itnm=argv;
pcnt=*(unsigned short *)argv;
plen=*(unsigned short *)argv;
itbuf=argv;

JOBUSPTR(&obuf);
get_output_arrays_count(&oa);
olen=oa.ot_len;
obuf += olen;

tlen = olen + ITNAME_SIZE + ITCNT_SIZE + ITPLEN_SIZE
         + (pcnt==0 ? plen : pcnt*plen);
if (tlen > DAT_MAX_LEN)
{
    char errbuf;

    fill_str(rpginfo->msgflnm, __FILE__, FILENM_LEN);
    sprintf(linebuf, "%6d", __LINE__);
    fill_str(rpginfo->msgline, linebuf, LINENO_LEN);
    fill_str(rpginfo->msgid, "EPLMNOLEN ", MSG_ID_LEN);
    sprintf(errbuf, "组附加报文时长度%d + %d超过最大长度%d",
            olen, tlen-olen, DAT_MAX_LEN);
    fill_str(rpginfo->msgtext, errbuf, MSG_TEXT_LEN);
    fill_str(rpginfo->msgotds, "", MSG_TEXT_LEN);
    return -1;
}

fill_str(obuf, itnm, ITNAME_SIZE);
obuf += ITNAME_SIZE;
tmp = htons(pcnt);
memcpy(obuf, &tmp, ITCNT_SIZE);
obuf += ITCNT_SIZE;
tmp = htons(plen);
memcpy(obuf, &tmp, ITPLEN_SIZE);
obuf += ITPLEN_SIZE;
memcpy(obuf, itbuf, pcnt==0 ? plen : pcnt*plen);

set_otlen(olen);
return tlen;
}
.
这个程序将一个报文接口,以ITNAME接口名,ITCNT接口数组条数,ITLEN接口单条长度,ITBUF接口数据的方式,打包记录进USER SPACE。其中,JOBUSPTR用于获取USER SPACE的指针,然后就像普通内存一样操作。下面就是JOBUSPTR的程序。
             PGM      PARM(&USPTR)
             INCLUDE    SRCMBR(CLHD) SRCFILE(DSCPPGM)
             DCL      VAR(&PASSPCNM) TYPE(*CHAR) LEN(20)
             DCL      VAR(&USPTR) TYPE(*PTR)
             CHGVAR   VAR(&PASSPCNM) VALUE('JOBADDUSQTEMP   ')
             CALL       PGM(QUSPTRUS) PARM(&PASSPCNM &USPTR)
             ENDPGM
.

核心系统中的用法到此为止。实际上,USER SPACE相关的命令和调用有不少,修改内容的方式除了获取指针,还可以用QUSRTVUS获取USER SPACE内容,QUSCHGUS修改USER SPACE内容。这两个调用以字符串的形式操作。遗憾的是,CL对USER SPACE的支持较少,需要用到的话自己写程序扩充吧。

cuilei286 发表于 2015-06-09 11:05

给大师点个赞,辛苦了!很受用

pacman2000 发表于 2015-06-09 17:23

cuilei286 发表于 2015-06-09 11:05 static/image/common/back.gif
给大师点个赞,辛苦了!很受用


哈哈,因为接下来可能不太会用400了,所以总结一下,一方面共享知识,另外也免得以后自己忘记了。
还有不少技术上的功能点,以后会慢慢补充。

send_linux 发表于 2015-06-12 23:25

pacman2000 发表于 2015-06-09 17:23 static/image/common/back.gif
哈哈,因为接下来可能不太会用400了,所以总结一下,一方面共享知识,另外也免得以后自己忘记了。
还 ...

感谢楼主分享哈~

josh565 发表于 2015-06-13 11:22

感谢楼主分享~:victory:

rtm009 发表于 2015-06-15 14:14

:dizzy:18摸现在也仅仅是维持as400了吧?

pacman2000 发表于 2015-06-18 07:18

本帖最后由 pacman2000 于 2015-06-18 07:19 编辑

*多语言与多环境

系统对多语言的支持,这在某些情况下,也是非常有用的,例如将核心系统部署为海外分行系统。通常情况下,海外系统独立搭建,操作人员部分是当地人员,也可能有派驻人员。同一套环境的多语言随时切换,其实并不存在实际场景,因为数据表内容存放,只会按照一个语言标准。举个例子,核心系统的户名地址等字段,是根据系统所在国家设语言,存放成不同文字,比如中文,而这些字段都会在输入输出中展示。因此,即使是将用户环境和操作界面切换成英文环境,字段内容仍会出现中文。另外,多语言不应额外增加处理的负担。
于是,系统的多语言,就分为两个方面。一是这套系统是否可以轻松做到转向其他语言的翻译,即如何作为一套海外系统部署。二是当系统后台维护人员运行工具时,如果能根据用户做到参数说明和输出展示的语言选择,尽可能符合本地人员和派驻人员的习惯。下面以中文(默认)和英文(辅助)为例,总结一下做法。

数据表部分,对于不同国家的版本,将各数据存放对象的CCSID设置为相应国家语言代码,如中文则为1388。
应用程序部分,区别在于返回报文的某些字符串字段内容语言不同。这些字段的数据来源,除了数据库中直接记录的字段,还可以在应用程序中拼接而成,比如出错信息。核心系统使用了错误信息表,在RPG程序中做错误信息的拼接组装。例如,“EIOER”错误代码内容为“接口&1字段&2值&3不正确”。其中&1,&2,&3由应用程序字符串代入。因此,应用程序中,全部将文本定义成命名为C_MGXXXX的常量。制作其他语言版本时,只需翻译所有C_MG常量,错误信息表和文件的中文描述即可(因为某些错误信息,会拼接表的中文信息,例如:更新表&1失败,&1为PF的中文描述活期分户账,如何获取在统一错误处理部分详细介绍)。
工具部分,就需要考虑操作用户不同时的语言不同了。AS400为CMD提供了多语言提示文件的方法,但CMD只是发起命令界面,执行程序的过程显示就没有办法了。因此换了一种做法,将工具的CMD,PGM等分多套语言版本编译到不同的目标库中。如XXXOBJMON放置默认的中文工具程序,XXXOBJMONE放置辅助的英文版本程序。然后通过用户USRPRF的初始化程序不同,来指定LIBL中的先后顺序,达到执行的不同语言版本的效果。

下面是中文语言习惯的用户USRPRF初始化程序。
             PGM
             INCLUDE    SRCMBR(CLHD) SRCFILE(DSCPPGM)
             DCL      VAR(&PFX) TYPE(*CHAR) LEN(3)
             DCL      VAR(&JRN) TYPE(*CHAR) LEN(10)
             DCL      VAR(&JRNLIB) TYPE(*CHAR) LEN(10)
             DCL      VAR(&OTHLIB) TYPE(*CHAR) LEN(10)
             CALL       PGM(GETPREFIX) PARM(&PFX)
             CHGVAR   VAR(&JRN) VALUE(&PFX *CAT 'JRN')
             CHGVAR   VAR(&JRNLIB) VALUE(&PFX *CAT 'SYSJRN')
             CHGVAR   VAR(&OTHLIB) VALUE(&PFX *CAT 'OBJOTH')
             CHGSYSLIBL LIB(QSYS2989)
             CHGCURLIBCURLIB(&OTHLIB)
             STRCMTCTLLCKLVL(*CHG) CMTSCOPE(*JOB) +
                        DFTJRN(&JRNLIB/&JRN)
             MONMSG   MSGID(CPF8351)
             ENDPGM
.

下面是英文语言习惯的用户USRPRF初始化程序。注意在OBJMON前会先搜索OBJMONE。
             PGM
             INCLUDE    SRCMBR(CLHD) SRCFILE(DSCPPGM)
             DCL      VAR(&PFX) TYPE(*CHAR) LEN(3)
             DCL      VAR(&JRN) TYPE(*CHAR) LEN(10)
             DCL      VAR(&JRNLIB) TYPE(*CHAR) LEN(10)
             DCL      VAR(&MONLIB) TYPE(*CHAR) LEN(10)
             DCL      VAR(&ENULIB) TYPE(*CHAR) LEN(10)
             DCL      VAR(&OTHLIB) TYPE(*CHAR) LEN(10)
             CALL       PGM(GETPREFIX) PARM(&PFX)
             CHGVAR   VAR(&JRN) VALUE(&PFX *CAT 'JRN')
             CHGVAR   VAR(&JRNLIB) VALUE(&PFX *CAT 'SYSJRN')
             CHGVAR   VAR(&MONLIB) VALUE(&PFX *CAT 'OBJMON')
             CHGVAR   VAR(&ENULIB) VALUE(&PFX *CAT 'OBJMONE')
             CHGVAR   VAR(&OTHLIB) VALUE(&PFX *CAT 'OBJOTH')
             ADDLIBLE   LIB(&ENULIB) POSITION(*BEFORE &MONLIB)
             CHGCURLIBCURLIB(&OTHLIB)
             STRCMTCTLLCKLVL(*CHG) CMTSCOPE(*JOB) +
                        DFTJRN(&JRNLIB/&JRN)
             MONMSG   MSGID(CPF8351)
             ENDPGM
.

其中,GETPREFIX程序,是获取当前应用环境。系统在一台机器上可以部署多套环境,以前三位环境代码区分。所有的库名前三位即为环境代码,用户登录时,就可以根据LIBL的设置,对应到相应环境。应用程序编写时,尽量使用*LIBL查找目标,如果确需指定库名,则先调用GETPREFIX,根据用户USRPRF的初始化程序所在库前三位,获得环境代码。
             PGM      PARM(&PREFIX)
             INCLUDE    SRCMBR(CLHD) SRCFILE(DSCPPGM)
             DCL      VAR(&PREFIX) TYPE(*CHAR) LEN(3)
             DCL      VAR(&PGMLIB) TYPE(*CHAR) LEN(10)
             RTVUSRPRFINLPGMLIB(&PGMLIB)
             CHGVAR   VAR(&PREFIX) VALUE(%SST(&PGMLIB 1 3))
             ENDPGM
.

页: [1] 2 3 4 5
查看完整版本: 一个程序员在AS400银行核心系统开发中的技术总结