Skip to content

五、进阶知识

FTK edited this page Jun 4, 2024 · 2 revisions

1.YAML/XML/注解/数据库配置方式使用场景

  • 注解方式语义清晰,适合终端系统对报文进行处理;
  • YAML/XML文件具有动态加载功能,工具包提供在线刷新方法,可手动触发或与第三方配置中心服务集成,可以做到配置热更新; 所以YAML/XML方式适合交易转发类应用,修改配置无需重启;
  • 相对与YAML,XML可以通过schema增强校验,减少配置出错的概率;
  • 数据库配置方式同YAML/XML文件配置方式优势基本相同,但数据库方式语义更强,配置更灵活;可以同后台管理系统集成,通过页面进行维护。

注:四种配置方式可以同时使用,针对同一报文配置项,先加载会被后加载的覆盖。

2.报文配置在线刷新

YAML/XML/数据库配置方式通过调用MessageDefinitionBuilder#refresh()方法,实现配置在线刷新;注解方式不支持在线刷新。

注:在线刷新时可能会导致报文解析过程出现中间状态,建议通过应用层面解决(比如灰度发布),后续版本考虑优化该问题。

3.YAML/XML/注解配置方式与报文容器对应关系

前文提到YAML/XML方式对应自定义容器MessageHolder,注解方式对应其配置POJO;以上为默认映射关系,特殊场景下可手动进行变换。

3.1.YAML方式指定POJO

alatka.messages:
  remark: 明细查询
  type: fixed
  group: "0395"
  code: 3116
  holder:
    request: com.xxx.yyy.POJO1
    response: com.xxx.yyy.POJO2
    subPayload:
      F13: com.xxx.yyy.POJO3
  .....

3.2.XML方式指定POJO

<request clazz="com.xxx.yyy.POJO1">
    ......
</request>
<response clazz="com.xxx.yyy.POJO2">
    ......
</response>
<subPayload domain="F13" clazz="com.xxx.yyy.POJO3">
    ......
</subPayload>

3.3.数据库方式指定POJO

  • ALK_MESSAGE_DEFINITION
M_ID M_TYPE M_GROUP M_CODE M_KIND M_DOMAIN M_USAGE M_DOMAIN_TYPE M_HOLDER M_CHARSET M_REMARK
3 fixed 0395 3116 request NONE com.xxx.yyy.POJO1 GB18030 明细查询请求报文
4 fixed 0395 3116 response NONE com.xxx.yyy.POJO2 GB18030 明细查询应答报文
5 fixed 0395 3116 subPayload F13 NONE com.xxx.yyy.POJO3 GB18030 明细查询应答分页子域

注:POJO字段名称及java类型需要与YAML/XML/数据库配置中一一对应。

3.4.注解方式指定MessageHolder

@MessageMeta(
        type = MessageDefinition.Type.fixed,
        group = "0395",
        code = "3116",
        kind = MessageDefinition.Kind.response,
        customize = true, // true: MessageHolder, false: 当前POJO
        remark = "明细查询")
public class Fixed3006Res extends FixedHeader {

    @FixedFieldMeta(domainNo = 8, length = 19, remark = "卡号")
    private String cardNbr;

    // getter setter
}

4.不解析报文域

前置类系统转发交易数据,一般只需解析个别域,大部分域是不处理原样转发;

针对无需解析的域,一般的处理方式是将该域的class属性配置为byte[],这样可以省去java类型映射转换的操作,但变长域的场景下,仍然需要对长度域进行处理;

如果变长域也不需要处理,可以配置该域的status属性为RAW注:配置status = RAW后该域的class属性不再生效,域值将以byte[]形式进行存储。

5.外卡组织报文格式一键转换

对接外卡组织的前置系统,一般涉及到EBCDIC码、BCD码、ASCII码、HEX相互转换;以对接JCB为例,该场景下可做如下配置: 对接外卡组织jcb.common.iso.ymlYAML文件:

......
- { "domainNo": 2, "name": "pan", "fixed": false, "length": 1, "maxLength": 10, "remark": "主账号", "clazz": "java.lang.String", "parseType": "BCD" }
- { "domainNo": 3, "name": "processingCode", "fixed": true, "length": 3, "remark": "交易处理码", "clazz": "java.lang.String", "parseType": "BCD" }
- { "domainNo": 4, "name": "amtTrans", "fixed": true, "length": 6, "remark": "交易金额", "clazz": "java.math.BigDecimal", "parseType": "BCD" }
......
- { "domainNo": 37, "name": "retrivlRefNum", "fixed": true, "length": 12, "remark": "检索参考号", "clazz": "java.lang.String", "parseType": "EBCDIC" }
- { "domainNo": 38, "name": "authrIdResp", "fixed": true, "length": 6, "remark": "授权标识应答码", "clazz": "java.lang.String", "parseType": "EBCDIC" }
......

对接行内jcb.bank.iso.ymlYAML文件:

......
- { "domainNo": 2, "name": "pan", "fixed": false, "length": 2, "maxLength": 19, "remark": "主账号", "clazz": "java.lang.String" }
- { "domainNo": 3, "name": "processingCode", "fixed": true, "length": 6, "remark": "交易处理码", "clazz": "java.lang.String" }
- { "domainNo": 4, "name": "amtTrans", "fixed": true, "length": 12, "remark": "交易金额", "clazz": "java.math.BigDecimal" }
......
- { "domainNo": 37, "name": "retrivlRefNum", "fixed": true, "length": 12, "remark": "检索参考号", "clazz": "java.lang.String" }
- { "domainNo": 38, "name": "authrIdResp", "fixed": true, "length": 6, "remark": "授权标识应答码", "clazz": "java.lang.String" }
......

jcb发送行内的交易

byte[] unpack = ...; // jcb->前置的交易报文
MessageHolder holder = MessageBuilder.init("iso:jcb:common:payload").unpack(unpack); // 解包为MessageHolder

MessageHolder holder1 = holder.copyOf("iso:jcb:bank:payload"); // MessageHolder复制
byte[] pack = MessageBuilder.init("iso:jcb:bank:payload").pack(holder1); //前置->行内的交易报文打包

行内发送jcb的交易

byte[] unpack = ...; // 行内->前置的交易报文
MessageHolder holder = MessageBuilder.init("iso:jcb:bank:payload").unpack(unpack); // 解包为MessageHolder

MessageHolder holder1 = holder.copyOf("iso:jcb:common:payload"); // MessageHolder复制
byte[] pack = MessageBuilder.init("iso:jcb:common:payload").pack(holder1); //前置->jcb的交易报文打包

6.单独解析子域

MessageBuilder除了解析完整8583/固定格式报文以外,还可以单独解析8583某一子域;

MessageBuilder.init("iso:cups:common:subPayload:F55").unpack(bytes); // 银联55域
MessageBuilder.init("iso:cups:common:subPayload:F48:IN").unpack(bytes); // 银联48域usage=IN
MessageBuilder.init("iso:cups:common:subPayload:F60_F11").unpack(bytes); // 银联60.11域
MessageBuilder.init("iso:cups:common:subPayload:F59$QR_F3").unpack(bytes); // 银联59域usage=QR F3子域

注:不能单独解析分页子域和不定长子域。

7.完善的报错信息

工具包提供完善的错误信息日志及跟踪日志,方便线上问题跟踪和线下debug。

针对打包解包功能,提供精确到具体域的报错信息,例如:

  • 银联48域未配置子域usage=ON
java.lang.RuntimeException: MessageDefinition{iso:cups:common:payload:NONE:银联8583报文} -> {F48:addtnlDataPrivate:3~512:附加数据——私有}解析报错

	at com.alatka.messages.message.MessageBuilder.doUnpack(MessageBuilder.java:113)
	at com.alatka.messages.message.MessageBuilder.lambda$unpack$3(MessageBuilder.java:95)
	at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
	at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
	at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
	at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
	at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
	at com.alatka.messages.message.MessageBuilder.unpack(MessageBuilder.java:96)
	at com.alatka.messages.message.MessageBuilder.unpack(MessageBuilder.java:84)
	at com.alatka.messages.DomainTest.test(DomainTest.java:454)
	... 80 more
Caused by: java.lang.RuntimeException: {F48:addtnlDataPrivate:3~512:附加数据——私有}未配置子域usage: ON
	at com.alatka.messages.field.SubdomainFieldBuilder.validate(SubdomainFieldBuilder.java:68)
	at com.alatka.messages.field.AbstractULVSubdomainFieldBuilder.unpack(AbstractULVSubdomainFieldBuilder.java:50)
	at com.alatka.messages.field.UVASSubdomainFieldBuilder.unpack(UVASSubdomainFieldBuilder.java:52)
	at com.alatka.messages.field.UVASSubdomainFieldBuilder.unpack(UVASSubdomainFieldBuilder.java:18)
	at com.alatka.messages.field.SubdomainFieldBuilder.toObjectWithNone(SubdomainFieldBuilder.java:49)
	at com.alatka.messages.field.AbstractFieldBuilder.toObject(AbstractFieldBuilder.java:103)
	at com.alatka.messages.field.AbstractFieldBuilder.deserialize(AbstractFieldBuilder.java:34)
	at com.alatka.messages.message.MessageBuilder.doUnpack(MessageBuilder.java:110)
	... 80 more
  • 银联123域数据超过最大长度(最大100,实际226)
java.lang.RuntimeException: MessageDefinition{iso:cups:common:payload:NONE:银联8583报文} -> {F123:issrInstResvd:3~100:发卡方保留}解析报错

	at com.alatka.messages.message.MessageBuilder.lambda$unpack$3(MessageBuilder.java:113)
	at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
	at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
	at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
	at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
	at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
	at com.alatka.messages.message.MessageBuilder.unpack(MessageBuilder.java:115)
	at com.alatka.messages.message.MessageBuilder.unpack(MessageBuilder.java:98)
	at com.alatka.messages.MessageCupsYamlTest.doTest(MessageCupsYamlTest.java:17)
	at com.alatka.messages.MessageCupsYamlTest.test04(MessageCupsYamlTest.java:48)
	... 80 more
Caused by: java.lang.IllegalArgumentException: fieldDefinition: {F123:issrInstResvd:3~100:发卡方保留} max length: 100, actually length: 226
	at com.alatka.messages.domain.LVDomainParsed.unpack(LVDomainParsed.java:42)
	at com.alatka.messages.message.MessageBuilder.doUnpack(MessageBuilder.java:124)
	at com.alatka.messages.message.MessageBuilder.lambda$unpack$3(MessageBuilder.java:111)
	... 80 more
  • 银联60.1域据超过最大长度(最大4,实际5)
java.lang.RuntimeException: MessageDefinition{iso:cups:common:payload:NONE:银联8583报文} -> {F60:reserved:3~100:自定义域}解析报错

	at com.alatka.messages.message.MessageBuilder.lambda$pack$0(MessageBuilder.java:74)
	at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
	at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
	at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
	at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.util.stream.ReferencePipeline.reduce(ReferencePipeline.java:474)
	at com.alatka.messages.message.MessageBuilder.pack(MessageBuilder.java:79)
	at com.alatka.messages.DomainTest.doTest(DomainTest.java:31)
	at com.alatka.messages.DomainTest.test16(DomainTest.java:414)
        ... 78 more
Caused by: java.lang.RuntimeException: MessageDefinition{iso:cups:common:subPayload:F60:NONE:银联8583报文} -> {F1:f60f1:4:报文原因码}解析报错
	at com.alatka.messages.message.MessageBuilder.lambda$pack$0(MessageBuilder.java:74)
	at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
	at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
	at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
	at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.util.stream.ReferencePipeline.reduce(ReferencePipeline.java:474)
	at com.alatka.messages.message.MessageBuilder.pack(MessageBuilder.java:79)
	at com.alatka.messages.field.FixedSubdomainFieldBuilder.pack(FixedSubdomainFieldBuilder.java:24)
	at com.alatka.messages.field.SubdomainFieldBuilder.fromObjectToNone(SubdomainFieldBuilder.java:40)
	at com.alatka.messages.field.AbstractFieldBuilder.fromObject(AbstractFieldBuilder.java:68)
	at com.alatka.messages.field.AbstractFieldBuilder.serialize(AbstractFieldBuilder.java:26)
	at com.alatka.messages.message.MessageBuilder.doPack(MessageBuilder.java:87)
	at com.alatka.messages.message.MessageBuilder.lambda$pack$0(MessageBuilder.java:72)
	... 78 more
Caused by: java.lang.IllegalArgumentException: {F1:f60f1:4:报文原因码} actual length: 5, expected length: 4
	at com.alatka.messages.domain.AbstractDomainParsed.padding(AbstractDomainParsed.java:28)
	at com.alatka.messages.domain.FixedDomainParsed.pack(FixedDomainParsed.java:30)
	at com.alatka.messages.message.UnfixedSubdomainMessageBuilder.doPack(UnfixedSubdomainMessageBuilder.java:41)
	at com.alatka.messages.message.MessageBuilder.lambda$pack$0(MessageBuilder.java:72)
	... 93 more
  • TODO

8.MessageHolder容器格式化输出

MessageHolder容器提供格式化输出功能,方便线上打印日志及线下问题调试。

@Test
public void test01() {
    String hex = "2E02303535383134353338323030202020303030313030303020202000000000303030303030303000303030303030323030F23A46C1A8E19A100000000010000080313636323234323432303030303030303231303030303030303030303030303130303030303531313233343230333030303031323233343230333035313130353131353431313035313030303030303630383932303130303030303839323031303030303334363232343234323030303030303032314432393032323031353639393033313030373030303030303030303031323132333435363738313233343536373839303132333435D6D0B9FAD2F8C1AA4348494E4120554E494F4E5041592053494D554C41544F52202020202020202030343741534F4E30343031323232313132323231313232323131323232313132323231313232323131323232313132323231313536EC2804D2AC42710A323630303030303030303030303030303131355F2A02015682027D00950500001800009A032405119C01019F02060000000100009F03060000000000009F09009F100807000103A02000019F1A0201569F1E0832303033313233309F26085576AF9865559F3E9F2701809F3303E098C09F34036003029F3501229F360200139F37040B7FBD483032373030303030363030303330303030303030303030303031323030303038313435333832303030343335314353323230303030303435303331303030303020202030303030303030303030303030303030303030";
    String header = hex.substring(0, 92);
    String body = hex.substring(92);
    MessageHolder headerMessage = MessageBuilder.init("iso:cups:common:header").unpack(header);
    System.out.println(headerMessage);
    MessageHolder bodyMessage = MessageBuilder.init("iso:cups:common:payload").unpack(body);
    System.out.println(bodyMessage);
}

输出内容:

{
  "{F1:headerLength:1:报文头长度}" : 46,
  "{F2:version:1:头标识和版本号}" : "00000010",
  "{F3:messageLength:4:报文长度}" : 558,
  "{F4:destinationId:11:目的ID}" : "14538200",
  "{F5:sourceId:11:源ID}" : "00010000",
  "{F6:reserved:3:保留使用}" : null,
  "{F7:batchNum:1:批次号}" : "00000000",
  "{F8:transInfo:8:交易信息}" : "00000000",
  "{F9:userInfo:1:用户信息}" : "00000000",
  "{F10:rejectCode:5:拒绝码}" : "00000"
}
{
  "{F0:messageType:4:报文类型}" : "0200",
  "{F1:bitMap:16~null:位图}" : {
    "1" : true,
    "2" : true,
    "3" : true,
    "4" : true,
    "5" : false,
    "6" : false,
    "7" : true,
    "8" : false,
    "9" : false,
    "10" : false,
    "11" : true,
    "12" : true,
    "13" : true,
    "14" : false,
    "15" : true,
    "16" : false,
    "17" : false,
    "18" : true,
    "19" : false,
    "20" : false,
    "21" : false,
    "22" : true,
    "23" : true,
    "24" : false,
    "25" : true,
    "26" : true,
    "27" : false,
    "28" : false,
    "29" : false,
    "30" : false,
    "31" : false,
    "32" : true,
    "33" : true,
    "34" : false,
    "35" : true,
    "36" : false,
    "37" : true,
    "38" : false,
    "39" : false,
    "40" : false,
    "41" : true,
    "42" : true,
    "43" : true,
    "44" : false,
    "45" : false,
    "46" : false,
    "47" : false,
    "48" : true,
    "49" : true,
    "50" : false,
    "51" : false,
    "52" : true,
    "53" : true,
    "54" : false,
    "55" : true,
    "56" : false,
    "57" : false,
    "58" : false,
    "59" : false,
    "60" : true,
    "61" : false,
    "62" : false,
    "63" : false,
    "64" : false,
    "65" : false,
    "66" : false,
    "67" : false,
    "68" : false,
    "69" : false,
    "70" : false,
    "71" : false,
    "72" : false,
    "73" : false,
    "74" : false,
    "75" : false,
    "76" : false,
    "77" : false,
    "78" : false,
    "79" : false,
    "80" : false,
    "81" : false,
    "82" : false,
    "83" : false,
    "84" : false,
    "85" : false,
    "86" : false,
    "87" : false,
    "88" : false,
    "89" : false,
    "90" : false,
    "91" : false,
    "92" : false,
    "93" : false,
    "94" : false,
    "95" : false,
    "96" : false,
    "97" : false,
    "98" : false,
    "99" : false,
    "100" : true,
    "101" : false,
    "102" : false,
    "103" : false,
    "104" : false,
    "105" : false,
    "106" : false,
    "107" : false,
    "108" : false,
    "109" : false,
    "110" : false,
    "111" : false,
    "112" : false,
    "113" : false,
    "114" : false,
    "115" : false,
    "116" : false,
    "117" : false,
    "118" : false,
    "119" : false,
    "120" : false,
    "121" : true,
    "122" : false,
    "123" : false,
    "124" : false,
    "125" : false,
    "126" : false,
    "127" : false,
    "128" : false
  },
  "{F2:pan:2~19:主账号}" : "6224242000000021",
  "{F3:processingCode:6:交易处理码}" : "000000",
  "{F4:amtTrans:12:交易金额}" : 10000,
  "{F5:amtSettlmt:12:清算金额}" : null,
  "{F6:amtCdhldrBil:12:持卡人扣账金额}" : null,
  "{F7:transmsnDateTime:10:交易传输时间}" : [ 2024, 5, 11, 23, 42, 3 ],
  "{F9:convRateSettlmt:8:清算汇率}" : null,
  "{F10:convRateCdhldrBil:8:持卡人扣账汇率}" : null,
  "{F11:sysTraceAuditNum:6:系统跟踪号}" : 12,
  "{F12:timeLocalTrans:6:受卡方所在地时间}" : [ 23, 42, 3 ],
  "{F13:dateLocalTrans:4:受卡方所在地日期}" : "--05-11",
  "{F14:dateExpr:4:卡有效期}" : null,
  "{F15:dateSettlmt:4:清算日期}" : "--05-11",
  "{F16:dateConv:4:兑换日期}" : null,
  "{F18:mchntType:4:商户类型}" : "5411",
  "{F19:mchntCntryCode:3:商户国家代码}" : null,
  "{F22:posEntryModeCode:3:服务点输入方式码}" : "051",
  "{F23:cardSeqId:3:卡序列号}" : "000",
  "{F25:posCondCode:2:服务点条件码}" : "00",
  "{F26:posPinCaptrCode:2:服务点PIN获取码}" : "06",
  "{F28:amtTransFee:9:交易费}" : null,
  "{F32:acqInstIdCode:2~11:代理机构标识码}" : "92010000",
  "{F33:fwdInstIdCode:2~11:发送机构标识码}" : "92010000",
  "{F35:tracK2Data:2~37:第二磁道数据}" : "6224242000000021D29022015699031007",
  "{F36:tracK3Data:3~104:第三磁道数据}" : null,
  "{F37:retrivlRefNum:12:检索参考号}" : "000000000012",
  "{F38:authrIdResp:6:授权标识应答码}" : null,
  "{F39:respCode:2:应答码}" : null,
  "{F41:cardAccptrTermnlId:8:受卡机终端标识码}" : "12345678",
  "{F42:cardAccptrId:15:受卡方标识码}" : "123456789012345",
  "{F43:cardAccptrNameLoc:40:受卡方名称地址}" : "中国银联CHINA UNIONPAY SIMULATOR",
  "{F44:addtnlRespCode:2~25:附加响应数据}" : null,
  "{F45:tracK1Data:2~76:第一磁道数据}" : null,
  "{F48:addtnlDataPrivate:3~512:附加数据——私有}" : {
    "ON" : {
      "{F1:orderNo:40:订单号}" : "1222112221122211222112221122211222112221"
    }
  },
  "{F49:currcyCodeTrans:3:交易货币代码}" : "156",
  "{F50:currcyCodeSettlmt:3:清算货币代码}" : null,
  "{F51:currcyCodeCdhldrBil:3:持卡人帐户货币代码}" : null,
  "{F52:pinData:8:个人标识码数据}" : "EC2804D2AC42710A",
  "{F53:secRelatdCtrlInfo:16:安全控制信息}" : {
    "{F1:keyType:1:重置密钥的类型/PIN格式}" : "2",
    "{F2:encryptionMethodUsed:1:加密算法标志}" : "6",
    "{F3:reserved:14:保留使用}" : "00000000000000"
  },
  "{F54:addtnlAmt:3~40:实际余额}" : null,
  "{F55:iccData:3~255:IC卡数据域}" : {
    "{F1:crypt:8:应用密文}" : "5576AF9865559F3E",
    "{F2:cryptInfoData:1:密文信息数据}" : "80",
    "{F3:issuerAppData:-2147483648~32:发卡行应用数据}" : "07000103A0200001",
    "{F4:unpredictableNumber:4:不可预知数}" : "0B7FBD48",
    "{F5:appTransCounter:2:应用交易计数器}" : "13",
    "{F6:termVerificationResult:5:终端验证结果}" : "180000",
    "{F7:transDate:3:交易日期}" : [ 2024, 5, 11 ],
    "{F8:transType:1:交易类型}" : 1,
    "{F9:transAmt:6:授权金额}" : 10000,
    "{F10:transCurrencyCode:2:交易货币代码}" : 156,
    "{F11:appInterchangeProfile:2:应用交互特征}" : "7D00",
    "{F12:termCountryCode:2:终端国家代码}" : 156,
    "{F13:otherAmt:6:其它金额}" : 0,
    "{F14:termCap:3:终端性能}" : "E098C0",
    "{F15:cardholderVerificationMethodResults:3:持卡人验证方法结果}" : "600302",
    "{F16:termType:1:终端类型}" : 22,
    "{F17:interfaceDeviceSerialNumber:8:接口设备序列号}" : "20031230",
    "{F18:dedicatedFileName:-2147483648~16:专用文件名称}" : null,
    "{F19:termAppVersionNumber:2:应用版本号}" : null,
    "{F20:transSequenceCounter:-2147483648~4:交易序列计数器}" : null,
    "{F21:issuerAuthenticationData:-2147483648~16:发卡行认证数据}" : null,
    "{F22:issuerScriptTemplate1:-2147483648~128:发卡行脚本1}" : null,
    "{F23:issuerScriptTemplate2:-2147483648~128:发卡行脚本2}" : null,
    "{F24:issuerScriptResults:-2147483648~21:发卡方脚本结果}" : null,
    "{F25:issuerAuthorizationCode:6:电子现金发卡行授权码}" : null,
    "{F26:cardProductIdentification:16:卡产品标识信息}" : null,
    "{F27:authorizationResponseCode:2:授权响应码}" : null
  },
  "{F56:addtnlData56:3~512:附加信息}" : null,
  "{F57:addtnlData57:3~100:附加交易信息}" : null,
  "{F59:detailInqrng:3~600:明细查询数据}" : null,
  "{F60:reserved:3~100:自定义域}" : {
    "{F1:f60f1:4:报文原因码}" : "0000",
    "{F2:f60f2:1:账户所有人类型}" : "0",
    "{F3:f60f3:1:终端读取能力}" : "6",
    "{F4:f60f4:1:IC卡条件代码}" : "0",
    "{F5:f60f5:1:保留使用}" : "0",
    "{F6:f60f6:2:终端类型}" : "03",
    "{F7:f60f7:1:受理免验密码标志}" : "0",
    "{F8:f60f8:1:IC卡验证可靠性标志}" : "0",
    "{F9:f60f9:2:电子商务标志}" : "00",
    "{F10:f60f10:1:交互方式标志}" : "0",
    "{F11:f60f11:-1~15:交易发生附加信息}" : {
      "{F1:f60f11f1:2:特殊计费类型}" : "00",
      "{F2:f60f11f2:1:特殊计费档次}" : "0",
      "{F3:f60f11f3:3:保留使用(第3位为MAC算法标识)}" : "000",
      "{F4:f60f11f4:1:支持部分承兑和返回余额标志}" : "0",
      "{F5:f60f11f5:1:交易发起方式}" : "1",
      "{F6:f60f11f6:1:交易介质}" : "2",
      "{F7:f60f11f7:1:IC 卡的应用类型}" : "0",
      "{F8:f60f11f8:2:账户结算类型}" : "00",
      "{F9:f60f11f9:1:卡账户等级}" : null,
      "{F10:f60f11f10:2:卡产品}" : null
    }
  },
  "{F61:chAuthInfo:3~200:持卡人身份认证信息}" : null,
  "{F62:switchingData:3~200:交换中心数据}" : null,
  "{F63:finaclNetData:3~512:金融网络数据}" : null,
  "{F70:netwkMgmtInfoCode:3:网络管理信息码}" : null,
  "{F90:origDataElemts:42:原始数据元}" : null,
  "{F96:msgSecurityCode:8:报文安全码}" : null,
  "{F100:rcvgInstIdCode:2~11:接收机构标识码}" : "14538200",
  "{F102:acctId1:2~28:帐户标识1}" : null,
  "{F103:acctId2:2~28:帐户标识2}" : null,
  "{F104:addtnlData104:3~512:附加信息}" : null,
  "{F113:addtnlData113:3~512:附加信息}" : null,
  "{F116:addtnlData116:3~512:附加信息}" : null,
  "{F117:addtnlData117:3~256:附加信息}" : null,
  "{F121:nationalSwResved:3~100:银联处理中心保留}" : {
    "{F1:f121f1:1:应答原因码}" : "5",
    "{F2:f121f2:1:单/双或双/单转换码}" : "1",
    "{F3:f121f3:1:卡性质}" : "C",
    "{F4:f121f4:40:CUPS保留}" : "S22000004503100000   0000000000000000000",
    "{F5:f121f5:38:转入和转出方标识代码/手续费信息}" : null
  },
  "{F122:acqInstResvd:3~100:受理方保留}" : null,
  "{F123:issrInstResvd:3~100:发卡方保留}" : null,
  "{F125:addtnlData125:3~256:附加信息}" : null,
  "{F126:addtnlData126:3~256:附加信息}" : null,
  "{F128:msgAuthnCode:8:报文鉴别码}" : null
}

针对注解配置,使用POJO作为数据容器的场景,可以使用MessageHolder#fromPojo(Object)方法,将POJO转换为MessageHolder,然后再格式化输出。

9.maven引入工具包

alatka-messages及其相关制品已上传至阿里云仓库,如需下载可进行如下配置:

1.设置仓库凭证

在 settings.xml 文件中设置以下仓库的访问凭证。

<servers>
  <server>
    <id>2448467-release-d4LMO3</id>
    <username>661f7b16a0522beff388bd5c</username>
    <password>R06ilmt)Ii=j</password>
  </server>
</servers>

2.配置仓库和包信息

在settings.xml文件节点中加入对应的仓库使用地址。

<repositories>
  <repository>
    <id>2448467-release-d4LMO3</id>
    <url>https://packages.aliyun.com/maven/repository/2448467-release-d4LMO3</url>
    <releases>
      <enabled>true</enabled>
    </releases>
    <snapshots>
      <enabled>true</enabled>
    </snapshots>
  </repository>
</repositories>

10.报文配置内存优化

报文配置信息(MessageDefinitionFieldDefinition)在MessageDefinitionBuilder#build()方法执行后全部加载到jvm内存中,供后续报文打包解包使用; 每个报文域对应一个FieldDefinition对象,随着报文接口的增多,jvm内存开销线性增加;在接口较多的场景下,内存开销问题不容忽视。

TODO

注意事项

  • 报文域类型不支持java原始类型(int/byte/long/short/boolean...):原始类型有默认值,会影响报文域取值和赋值;
  • TODO