159
浙江电信移动业务接入实践 1 浙江电信移动业务接入实践 浙江电信互联网与信息事业部 林路 2010-05-13

浙江电信移动业务接入实践20100513

Embed Size (px)

DESCRIPTION

 

Citation preview

Page 1: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

1

浙江电信移动业务接入实践

浙江电信互联网与信息事业部

林路

2010-05-13

Page 2: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

2

目录

一. ISAG接入篇 ............................................................................................................................................... 4

1. ISAG简介 .......................................................................................................................................... 4

1.1 ISAG在电信网络里面的位置 ........................................................................................................... 6

1.2 ISAG与WEB SERVICE技术 ........................................................................................................... 6

1.3开发环境搭建 ...................................................................................................................................... 7

1.3.1 安装 JDK ................................................................................................................................. 7

1.3.2下载安装 ECLIPSE .................................................................................................................. 7

1.3.3 安装WTP模块 ....................................................................................................................... 8

1.3.4 安装 Tomcat ............................................................................................................................. 9

1.3.5 ISAG接口相关通用参数及说明........................................................................................... 10

2. SMS...................................................................................................................................................... 13

2.1短信接口开发环境搭建 ............................................................................................................ 13

2.2 SendSMS短信发送接口开发 ................................................................................................... 19

2.3 SendSMS短信上行,回执接口开发 ....................................................................................... 39

3. WAP PUSH .......................................................................................................................................... 46

3.1 WAP PUSH简介 .................................................................................................................... 46

3.2 WAP PUSH网络结构和业务实现 ........................................................................................ 46

3.3 WAP PUSH是什么?WAP PUSH不是什么? .................................................................... 48

3.4 WAP Push接口开发环境搭建 .................................................................................................. 49

3.5 WAP Push发送接口开发 .......................................................................................................... 53

3.6 WAP Push 状态报告接口开发 ................................................................................................. 58

4. 彩信................................................................................................................................................. 61

4.1彩信的构成 ................................................................................................................................ 61

4.2 彩信在电信网络里面的网络结构 ........................................................................................... 62

4.3 彩信开发接口开发环境搭建 ................................................................................................... 63

彩信发送接口开发 .......................................................................................................................... 69

4.5 彩信上行 .................................................................................................................................. 81

5.1 TcpMon使用 ............................................................................................................................. 94

5.2 Web Services Explorer ............................................................................................................... 98

附录一: SendSms短信发送接口报文数据: ..................................................................................... 100

1.1请求(SP发起): ...................................................................................................................... 100

2.2应答(ISAG应答) ............................................................................................................... 101

附录二: WAP PUSH发送报文数据: ................................................................................................ 102

1.1请求(SP发起): ...................................................................................................................... 102

2.2应答(ISAG应答) ............................................................................................................... 103

附录三: SendMessage彩信发送接口报文数据: .............................................................................. 104

1.1请求(SP发起): ...................................................................................................................... 104

1.2 ISAG响应 ............................................................................................................................... 106

二. 与 ISMP接口实现 ................................................................................................................................. 107

1.ISMP简介 .......................................................................................................................................... 107

1.1与 ISMP相关的术语 ...................................................................................................................... 107

2.业务流程............................................................................................................................................. 109

Page 3: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

3

2.1点播业务流程 .......................................................................................................................... 109

2.2包月业务流程 .......................................................................................................................... 110

3. ISMP接口开发环境搭建 ................................................................................................................. 111

4. 订购关系同步 orderRelationUpdateNotify方法实现 ..................................................................... 117

5. 管理信息同步 notifyManagementInfo方法实现 ............................................................................ 122

6. 服务使用通知接口 serviceConsumeNotify方法实现 .................................................................... 124

7.反向取消接口 spWithdrawSubscription接口实现 ........................................................................... 126

8. 程序部署........................................................................................................................................... 129

9. 接口测试........................................................................................................................................... 131

三.WAP业务开发 ...................................................................................................................................... 135

1 WAP业务简介 ................................................................................................................................... 135

1.1 WAP业务的网络结构 ............................................................................................................ 136

1.2 WAP业务实现原理 ................................................................................................................ 137

2 点播实现............................................................................................................................................ 140

3 包月实现............................................................................................................................................ 142

4. WAP 1.0还是WAP 2.0 ..................................................................................................................... 144

5.UA适配 .............................................................................................................................................. 145

6.WAP 业务测试 .................................................................................................................................. 147

四. 短信网关 SMGP接入篇 ........................................................................................................................ 150

1. 短信网关接口协议 SMGP概述 ...................................................................................................... 150

2. SMGP封包结构 ................................................................................................................................ 151

3. SMGP相关术语解释 ........................................................................................................................ 154

4. 封包拼装和解析 ............................................................................................................................... 155

4.1登录请求相关封包:Login,Login_Resp ................................................................................ 156

4.2发送短信相关封包:Submit,Submit_Resp ............................................................................ 158

4.3 上行短信相关封包:Deliver,Deliver_Resp ......................................................................... 158

4.4 退出请求相关封包:Exit,Exit_Resp ..................................................................................... 158

4.5 TLV字段封装 ...................................................................................................................... 158

五. 关于本文档 ............................................................................................................................................. 159

Page 4: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

4

一. ISAG接入篇

1. ISAG简介

关于ISAG是啥,我们直接看下百度百科上的说明,这个很好诠释了什么是ISAG:

ISAG综合业务接入网关,英文全称为Integrated Service Access Gateway。

中国电信的综合业务接入网关(ISAG)将为应用提供符合国际标准的Parlay X2.0接口,并对该接

口作了适当增强,为应用提供更为丰富的业务能力,采用开放的综合业务接入架构的ISAG具有以下

优点:

1. 采用了统一的符合国际标准的接口,业务可以多网移植或跨网运行。基于这种业务接入架构,

一个应用可以使用来自多种网络(PHS、PSTN、3G 和 SIP 等)的能力,形成融合的业务应用。

2. 采用统一的业务接入、安全认证及控制机制,与 ISMP 平台有机地结合起来,便于业务的统一

管理、统一计费,有效保护电信核心网络资源及运营商的利益;

3. 由于 PARLAY X 是基于 XML 的开放接口,不依赖于任何专门的电信协议,这使得数量巨大的 IT

业软件开发者也可以开发电信业务,真正实现电信增值业务与 Internet 业务的融合;

4. 由于采用了标准的、简单的开放接口,使得业务开发周期缩短,开发成本降低;

5. 简化了组合业务的开发及计费工作;

6. 运营商可以非常灵活地对业务接入平台所提供的业务能力进行组合,为应用提供组合的业务能

力,进一步简化组合业务的开发。

ISAG是移动业务网络中实现业务统一接入和服务质量监控的功能实体。使运营商能够开放电信网络

资源,并控制对网络资源的使用;为CP/SP屏蔽底层网络技术复杂性,提供统一业务开发环境,降低

业务开发门槛;为用户提供融合业务,丰富业务形式,有利于开发企业应用,拓展企业用户市场。

ISAG在业务网络中主要功能为:

ISAG屏蔽了底层网络的复杂性,实现对移动数据、移动语音、PHS业务的业务能力高度抽象,封装

成开放、统一、标准应用开发接口提供给CP/SP,支持电信自营增值业务、第三方CP/SP增值业务及

企业应用的接入;为CP/SP提供统一的增值应用集成开发和测试环境;协同ISMP完成业务应用过程

中认证、鉴权、计费和管理等功能;实现组合业务计费;对业务流进行质量监控和内容安全控制。

Page 5: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

5

以上已经很好的诠释了什么是 ISAG,在移动业务接入的初期,业务的承载模式基本上

以短信为主,SP如需接入运营商的平台,应根据运营商短信网关的接口协议:CMPP(中国

移动),SGIP(中国联通),SMGP(中国电信)直接接入短信网关,同时与短信管理平台

实现订购关系同步计费等接口。这些协议从某种意义上是平台的私有接口。

而现有的移动增值业务已经不仅仅是短信。如果 SP需要接入彩信业务,需要与彩信

接入网关实现MM7协议;如果需要接入WAP PUSH业务需要与PPG(PUSH代理网关)实现PAP

协议;如果需要实现定位业务,还需要与运营商的定位系统进行对接。我们可以看到的

是,这些接口的实现方式完全不同,这给SP接入增值业务带来了很大的难度和困惑。

为了解决这个问题,各运营商希望能够屏蔽各个接口的私有协议,而采用标准的协

议进行统一接入。而 SOA的盛行,使得 WEB SERVICE成为统一接入最好的承载模式。而

1999年一个由 65家通信和 IT领域的公司共同参与的非盈利性组织,提出了Parlay/OSA

应用程序接口规范,并且发布了多个版本,其中包括:

1) Parlay 5 - 2005.4.11

2) Parlay 4 - 2005.3.8

3) Parlay X Web Services Specification, Version 2.0 - 2005.3

4) Parlay X Web Services Specification, Version 1.0.1 - 2004.6

5) Parlay X Web Services Specification, Version 1.0. - 2003.4

而中国电信选取了Parlay X Web Services Specification, Version 2.0作为了ISAG

的标准规范。

Page 6: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

6

1.1 ISAG在电信网络里面的位置

紫色箭头为 SP需要实现的接口,在整个体系中,SP与 ISAG通过不同的Web Services接口实现

短信,彩信,WAP的业务接入,ISAG将收到的Web Service请求转换成各引擎相应的接口,与短信

网关采用 SMGP协议,与 PPG采用 PAP协议,与彩信中心实现MM7协议,与定位系统采用MLP协

议。同时 SP还需要与 ISMP实现接口,实现订购关系的同步。

通过上图可以看出,由于 ISAG的引入,SP侧只需要按照规范实现Web Services接口即可,而不

需要再跟具体的协议比如 SMGP,PAP,MM7打交道。

1.2 ISAG与WEB SERVICE技术

这里不再详细讲解什么是WEB SERVICE,如果你对WEB SERVICE缺乏了解,可以参考

相应的书籍或者拜Google为老师。

简单对象访问协议(SOAP)是 WEB SERVICE承载的协议,这里需要说明的你的程序与

ISAG进行交互的时候需要设置SOAP HEADER进行相关鉴权信息的传送,而某些接口(比

如彩信)还需要你的WEB SERVICE程序支持附件。

目前 AXIS是目前应用最广的 WEB SERVICE的引擎,对于上述的两项都能够很好的支

持,所以以下涉及到开发的例子我们会使用它作为我们开发的引擎。简单说下AXIS,AXIS

是Apache Extensible Interaction System的英文缩写,意为:阿帕奇可扩展交互系统。

Page 7: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

7

他是APACHE基金会的一个开源项目,Axis本质上就是一个SOAP引擎,提供创建服务器

端、客户端和网关SOAP操作的基本框架。Axis目前版本是为Java编写的。

当然同样ECLIPSE是目前最广泛应用的JAVA开发工具。 Eclipse最初是由IBM公司

开发的替代商业软件Visual Age for Java的下一代IDE开发环境,2001年11月贡献给

开源社区,现在它由非营利软件供应商联盟 Eclipse基金会(Eclipse Foundation)管

理。

整个教程将会以JAVA作为开发语言,以Eclipse作为开发工具,AXIS作为SOAP引擎

进行讲解如何进行ISAG的接口开发。如果你习惯使用微软的.NET进行开发;非常抱歉,

本人虽然不是反微软的斗士,但是对于微软的东西有种天生的莫名的排斥,虽然我的笔

记本也安装着Windows操作系统,但是对于微软的开发工具一窍不通。

1.3开发环境搭建

1.3.1 安装 JDK

需要使用 JAVA开发,必然需要安装 JDK,目前 JDK常用的版本有 1.3、1.4、1.5以及

1.6。1.3和1.4版本目前已经很少使用,至于1.5还是1.6需要你根据你服务器上的JSP

引擎的java版本进行匹配安装。

JDK的下载地址:http://java.sun.com

1.3.2下载安装 ECLIPSE

Eclipse可以在 http://www.eclipse.org 上下载,eclipse因为不仅仅是一个 JAVA

开发工具,所以他有多个版本,我们需要下载的是Eclipse IDE for Java EE Developers

版本,如果下载的是企业版,因为其自带了WTP模块,忽略下面步骤1.4.3安装WTP模

块。

当然你也可以选择Eclipse IDE for Java Developers版本,如果选择该版本需要根

据1.4.3安装WTP模块。

Page 8: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

8

Eclipse下载以后解压缩到某一目录,然后运行eclipse.exe即可。

1.3.3 安装WTP模块

注:如果选择安装了Eclipse IDE for Java EE Developers版本,忽略该步骤。

WTP是 Web Tools Platform的缩写,顾名思义是 Web工具包,其官方网站是

http://www.eclipse.org/webtools/,同样他跟Eclipse一样是个Free的软件。

先安装好 eclipse IDE for java,运行 Eclipse,然后菜单选择 help->Install New

Software,在Work With输入:

http://download.eclipse.org/webtools/updates/

选中上述项目以后,点击Next。稍等片刻就会完成WTP模块的安装。

Page 9: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

9

1.3.4 安装 Tomcat

由于接口需要建立一个 Web Service进行发送结果的接受,需要开发相应的 Web

Service Server程序,另外与ISMP的订购关系同步也需要开发相应的服务端软件,我们

需要一个能够运行Web Service的Web服务器,Tomcat是目前常用的Jsp服务器,当然

你可以其他的JSP引擎,比如Resin,WebSphere等,考虑到Tomcat是免费的,容易获得,

所以我们下面的例子都会以Tomcat为例。

Tomcat的官方网站是:http://tomcat.apache.org/,你可以到网站上下载5.5或者

6.0版本。 如果你下载的是Zip方式,安装与Eclipse类似,解压缩到某个目录下即可,

当然你也可以选择Windows Service Installer的文件包,下载后直接安装。

安装以后,在tomcat的安装目录有如下目录。

其中:

目录bin是tomcat的运行目录,运行该目录下的startup.bat 即可启动Tomcat

目录webapps是应用程序部署的目录,程序完成开发以后需要将文件部署到该目录

下。

其他目录的作用请参考相关资料,这里不再详述。

Page 10: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

10

1.3.5 ISAG接口相关通用参数及说明

在讲解具体的 ISAG接口前,我们需要将 ISAG的接口进行相关概要的说明。

在与 ISAG进行交互的时候,Web Service相关的程序都需要设置 SOAP HEADER,负责接口间鉴权

的实现。

SOAP HEADER需要包含以下参数:

参数名 类型 描述

spid String SP编号,这个编号是 8位,比如浙江电信接入

的应该是 121XXXXXXX这样的数字,其中 12

表示浙江,第三位的 1表示 CDMA的 SP

spPassword String SP密码(MD5加密)

MD5加密算法如下:

SP密码=SPID+密钥+时间戳

密钥部分由 ISMP 分配,你在申请业务能力的

时候填写的密钥。

时间戳,由 SP端生成,格式:MMDDHHMMSS,

月日时分秒

timeStamp String 时间戳,由 SP端生成,格式:MMDDHHMMSS,

月日时分秒,要与上面加密用的密钥一致。

productId String 产品编号

SAN String 业务接入码,可选

transactionId String 业务流水号;可选

transEnd EndReason 交易技术标识,用户组和业务。可选

linkID String 事务关联 ID,用户点播业务

OA anyURI 业务订购地址。群发时不填

FA anyURI 付费地址。可选

Multi-cast Messaging Boolean True:群发

False:非群发

缺省为 false

Page 11: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

11

程序中还会用到 anyURI 的类型,如果该类型标识用户号码的时候需要加上”tel:”,以说

明是电话号码,比如 18905718888这个号码,anyURI表示应该为 tel:+8618905718888。

另外用到的MD5加密,加密完成后需要转换成 16进制表示的字串,我们需要增加一

个加密的类,代码如下:

package cn.com.chinatelecom.util;

import java.security.*;

public class MD5 {

private final static String[] hexDigits = {

"0", "1", "2", "3", "4", "5", "6", "7",

"8", "9", "a", "b", "c", "d", "e", "f"};

/**

* 转换字节数组为16进制字串

* @param b 字节数组

* @return 16进制字串

*/

public static String byteArrayToHexString(byte[] b) {

StringBuffer resultSb = new StringBuffer();

for (int i = 0; i < b.length; i++) {

resultSb.append(byteToHexString(b[i]));

}

return resultSb.toString();

}

private static String byteToHexString(byte b) {

int n = b;

if (n < 0)

n = 256 + n;

int d1 = n / 16;

int d2 = n % 16;

return hexDigits[d1] + hexDigits[d2];

}

public static String compile(String origin) {

String resultString = null;

try {

resultString=new String(origin);

MessageDigest md = MessageDigest.getInstance("MD5");

Page 12: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

12

resultString=byteArrayToHexString(md.digest(resultString.getBytes()));

}

catch (Exception ex) {

}

return resultString;

}

}

这样的话我们就可以使用 MD5.compile(“SPID”+”密钥”+”时间戳”) 进行 spPassword 的数

据生成。

Page 13: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

13

2. SMS

特别说明:目前短信业务接入有两种模式,一种是ISMP+短信网关,短信接入采用SMGP

3.0协议;一种是ISMP+ISAG方式接入,短信的接入采用Web Service。目前由于浙江ISAG

的能力有限,所以浙江电信的短信业务是采用ISMP+短信网关方式,该方式不在这里讲述。

因为短信也是 ISAG的一个接入的一个基本功能,而且相对 WAP PUSH和彩信接口比

较简单,接口与彩信和 WAP PUSH相近,理解 ISAG短信接入的接口协议也有利于了解整

个ISAG的接口协议。所以这里还是就ISAG接入短信业务进行讲解。

2.1短信接口开发环境搭建

与ISAG相关的短信接口,需要实现3个接口:

接口名称 接口方法 作用 谁是服务器端

SendSMS sendSms 发送短信

ISAG

sendSmsLogo 发送图标

sendSmsRingtone 发送铃声

getSmsDeliverStatus 查询发送状态

SmsNotifactionService notifySmsReception 接受上行消息 SP端程序

notifySmsDeliveryReception 接收状态报告

需要注意接口谁作为服务器端,谁作为客户端。这个开发的时候有很大的区别。

1)新建项目

如下图,新建一个项目,项目选择类型为:Dynamic Web Project, 我们这里将这个项目

命名为sms(可以根据自己喜好确定)。

Page 14: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

14

Page 15: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

15

我本机上安装的是tomcat 5.5,所以我在Target runtime选择了它。

点击确认以后,我们会在Project Explorer看到如下目录。

2)导入WSDL

Page 16: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

16

与短信接入相关的wsdl文件有如下:

与ISAG接口相关的wsdl

ctcc_sms_types_2_1.xsd

ctcc_sms_send_service_2_1.wsdl

ctcc_sms_send_interface_2_1.wsdl

ctcc_sms_receive_service_2_1.wsdl

ctcc_sms_receive_interface_2_1.wsdl

ctcc_sms_notification_service_2_1.wsdl

ctcc_sms_notification_interface_2_1.wsdl

ctcc_common_types_2_1.xsd

ctcc_common_faults_2_0.wsdl

与ISMP相关的wsdl

IsmpSpEngine.wsdl

为了区分其他程序的文件,我们为 wsdl新建一个目录,在 sms的项目按右键选择

New->Folder,我们将这个目录起名为wsdl。

选中wsdl目录,按右键选择Import。

Page 17: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

17

在 Import的对话框选择 General->File System

找到你本机上WSDL的存放目录

Page 18: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

18

选择我们需要的WSDL文件,然后点击 Finish,至此我们需要的 wsdl文件就已经导入进

来。可能部分开发人员习惯新建一个Web Service,然后指定相应的 wsdl文件进行生成,

但由于 ISAG的接口协议由多个描述文件构成,如果这样的方式很容易导致Web Service

生成的时候提示语法不对,或者缺少相应描述文件。

Page 19: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

19

2.2 SendSMS短信发送接口开发

短信发送的接口过程中,SP侧的软件是作为Web Service Client,而 ISAG作为 Server,

所以我们这里需要生成发送接口的客户端软件。

方法非常简单,选中 wsdl目录下的ctcc_sms_send_service_2_1.wsdl文件,按右键

选择Web Services->Generate Client。

完成以后我们会看到 Java Resources:src目录下有如下文件:

其中 SendSmsServiceLocator就是我们需要发送短信的客户端软件。

Page 20: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

20

我们再写个软件来测试下,新建一个 SendSmsTest的类

代码如下:

import java.math.BigDecimal;

import java.net.MalformedURLException;

import java.rmi.RemoteException;

import java.text.SimpleDateFormat;

import java.util.Date;

import javax.xml.rpc.ServiceException;

import javax.xml.soap.SOAPException;

import org.apache.axis.message.SOAPHeaderElement;

import org.apache.axis.types.URI.MalformedURIException;

import cn.com.chinatelecom.util.MD5;

import cn.com.chinatelecom.www.schema.ctcc.common.v2_1.ChargingInformation;

import cn.com.chinatelecom.www.schema.ctcc.common.v2_1.PolicyException;

import cn.com.chinatelecom.www.schema.ctcc.common.v2_1.SimpleReference;

import

cn.com.chinatelecom.www.wsdl.ctcc.sms.send.v2_1.service.SendSmsBindingStub;

import

cn.com.chinatelecom.www.wsdl.ctcc.sms.send.v2_1.service.SendSmsServiceLocator;

import cn.com.chinatelecom.www.wsdl.ctcc.sms.send.v2_1._interface.SendSms;

public class SendSmsTest {

public static void main(String[] args) {

String webserviceUrl = "isag接口地址,非wsdl地址";

String SPID = "SPID"; // SPID

String Token = "密钥"; // 密钥

String DestNum = "tel:+86153XXXXXXXX"; // 发送号码

String ProductID = "21位的产品编号,1开头"; // 产品编号

String ServiceID = "21位的业务编号,2开头"; // 业务编号

String TimeStamp = dateString(); // 当前时间

String senderName = "10620000"; // 短信主叫号码,在浙江ISAG实际无效,ISAG会读

取业务部署时候的接入号

String message = "测试短信"; // 短信内容

Page 21: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

21

SOAPHeaderElement SoapHeader = new SOAPHeaderElement(

"http://www.chinatelecom.com.cn/schema/ctcc/common/v2_1",

"RequestSOAPHeader");

try {

/* 初始化Web Service Client */

SendSmsServiceLocator ssl = new SendSmsServiceLocator();

SendSms sendSms = ssl.getSendSms(new java.net.URL(webserviceUrl));

/* 设置SOAP Header */

SoapHeader.addChildElement("spId").addTextNode(SPID); // SpID

SoapHeader.addChildElement("timeStamp").addTextNode(TimeStamp);

SoapHeader.addChildElement("spPassword").addTextNode(

MD5.compile(SPID + Token + TimeStamp).toUpperCase());// MD5加

SoapHeader.addChildElement("productId").addTextNode(ProductID);

SoapHeader.addChildElement("OA").addTextNode(DestNum);

SoapHeader.addChildElement("FA").addTextNode(DestNum);

SoapHeader.addChildElement("multicastMessaging").addTextNode(

"false"); // 是否群发

((SendSmsBindingStub) sendSms).setHeader(SoapHeader); // 添加SOAP头

/* 设置被叫号码 */

org.apache.axis.types.URI[] addresses = new

org.apache.axis.types.URI[1];

addresses[0] = new org.apache.axis.types.URI(DestNum);

/* 设置ChargingInformation */

ChargingInformation charging = new ChargingInformation();

charging.setDescription("gm");// 描述

charging.setAmount(new BigDecimal(1));// 扣费数目

charging.setCode(ServiceID);// 业务代码

charging.setCurrency("0");

/* 设置SimpleReference */

SimpleReference receiptRequest = new SimpleReference();

receiptRequest.setCorrelator("1001559"); // 序号自己随机增加

receiptRequest.setInterfaceName("SmsNotificationService");

receiptRequest.setEndpoint(new org.apache.axis.types.URI(

"http://开头的接收短信状态报告的webserivce地址,后续章节会讲"));

/* 发送短信 */

String result = sendSms.sendSms(addresses, senderName, charging,

Page 22: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

22

message, receiptRequest);

System.out.println("result:" + result);

}catch

(cn.com.chinatelecom.www.schema.ctcc.common.v2_1.ServiceException e) {

//鉴权失败

System.out.println("MessageId:" + e.getMessageId());

System.out.println("Text:" + e.getText());

String[] variables = e.getVariables();

for (int i = 0; i < variables.length; i++) {

System.out.println("Variable:" + variables[i]);

}

} catch (cn.com.chinatelecom.www.schema.ctcc.common.v2_1.PolicyException e)

{

System.out.println("MessageId:" + e.getMessageId());

System.out.println("Text:" + e.getText());

String[] variables = e.getVariables();

for (int i = 0; i < variables.length; i++) {

System.out.println("Variable:" + variables[i]);

}

} catch (MalformedURLException e) {

e.printStackTrace();

} catch (ServiceException e) {

e.printStackTrace();

} catch (SOAPException e) {

e.printStackTrace();

} catch (MalformedURIException e) {

e.printStackTrace();

} catch (RemoteException e) {

e.printStackTrace();

}

}

private static String dateString() {

SimpleDateFormat sdf = new SimpleDateFormat("MMddHHmmss");

return sdf.format(new Date());

}

}

Page 23: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

23

注:红色部分请根据问题提示替换成你的实际数据。

我们来解释下这个程序,首先我们看下:

1)初始化 SendSmsServiceLocator

a)类:SendSmsServiceLocator 说明:

他有如下方法:

public SendSmsServiceLocator()

说明:构造函数

public SendSmsServiceLocator(org.apache.axis.EngineConfiguration config)

说明:构造函数 可以通过设置 EngineConfiguration 的方式,构造一个

SendSmsServiceLocator

public SendSmsServiceLocator(java.lang.String wsdlLoc, javax.xml.namespace.QName

sName) throws javax.xml.rpc.ServiceException

说明:构造函数,可以通过设置 wsdl地址和 QName来构造 Client

public java.lang.String getSendSmsAddress()

说明:返回 SendSms的接口地址

public java.lang.String getSendSmsWSDDServiceName()

说明:返回服务名称,这里固定返回 SendSms

public void setSendSmsWSDDServiceName(java.lang.String name)

说明:设置服务名称

public cn.com.chinatelecom.www.wsdl.ctcc.sms.send.v2_1._interface.SendSms

getSendSms() throws javax.xml.rpc.ServiceException

说明:返回发送短信接口方法。可以通过该方法得到一个实际可以发送短信的类

SendSms

public cn.com.chinatelecom.www.wsdl.ctcc.sms.send.v2_1._interface.SendSms

getSendSms(java.net.URL portAddress) throws javax.xml.rpc.ServiceException

说明:返回发送短信接口方法。可以通过该方法得到一个实际可以发送短信的类

SendSms

public void setSendSmsEndpointAddress(java.lang.String address)

说明:设置服务器端WebService地址

Page 24: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

24

public java.rmi.Remote getPort(Class serviceEndpointInterface)

说明:返回Webservice端口

public javax.xml.namespace.QName getServiceName()

说明:返回 QName方式表示的服务名称

public java.util.Iterator getPorts()

说明:返回端口

public void setEndpointAddress(java.lang.String portName, java.lang.String address)

throws javax.xml.rpc.ServiceException

说明:设置请求地址

public void setEndpointAddress(javax.xml.namespace.QName portName,

java.lang.String address)

说明:通过 QName方式设置请求地址

b)类:SendSmsServiceLocator使用

我们需要初始化一个SendSmsServiceLocator类,直接通过构造函数 public

SendSmsServiceLocator() 构建,代码如下:

SendSmsServiceLocator ssl = new SendSmsServiceLocator();

由于提供给SP开发的WSDL文件里面,设定的WebService地址是:

http://localhost:9080/SendSmsService/services/SendSms

所以需要根据实际请求地址设定WebService的调用地址,代码如下 :

String webserviceUrl = "isag接口地址(非wsdl地址)";

SendSms sendSms = ssl.getSendSms(new

java.net.URL(webserviceUrl));

当然你可以直接修改SendSmsServiceLocator.java里面的代码:

private java.lang.String SendSms_address =

"http://localhost:9080/SendSmsService/services/SendSms";

Page 25: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

25

修改为

private java.lang.String SendSms_address = " isag接口地址(非

wsdl地址)";

考虑到程序的通用性,建议在初始化服务的时候设置Web Service地址,而不是

直接修改SendSmsServiceLocator.java程序。

2)类 SendSMS的使用

通过上述初始化,我们得到一个 SendSMS对象,他是我们发送短信实际需要的类,他

有如下方法:

public java.lang.String sendSms(

org.apache.axis.types.URI[] addresses,

java.lang.String senderName,

cn.com.chinatelecom.www.schema.ctcc.common.v2_1.ChargingInformation charging,

java.lang.String message,

cn.com.chinatelecom.www.schema.ctcc.common.v2_1.SimpleReference receiptRequest

)

throws java.rmi.RemoteException,

cn.com.chinatelecom.www.schema.ctcc.common.v2_1.PolicyException,

cn.com.chinatelecom.www.schema.ctcc.common.v2_1.ServiceException;

说明:

org.apache.axis.types.URI[] addresses:接受方地址,由于这里是 URI表示的,addresses应

该为 new URI(“tel:+86手机号码”)

String senderName:String类型,说明发送源地址号码

ChargingInformation charging:计费类型

String message:消息内容

SimpleReference receiptRequest:接口回调地址,也就是该短信的状态报告返回地址。

public java.lang.String sendSmsLogo(

org.apache.axis.types.URI[] addresses,

java.lang.String senderName,

cn.com.chinatelecom.www.schema.ctcc.common.v2_1.ChargingInformation charging,

byte[] image,

cn.com.chinatelecom.www.schema.ctcc.sms.v2_1.SmsFormat smsFormat,

cn.com.chinatelecom.www.schema.ctcc.common.v2_1.SimpleReference receiptRequest

Page 26: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

26

)

throws

java.rmi.RemoteException,

cn.com.chinatelecom.www.schema.ctcc.common.v2_1.PolicyException,

cn.com.chinatelecom.www.schema.ctcc.common.v2_1.ServiceException;

说明:

org.apache.axis.types.URI[] addresses:接受方地址,由于这里是 URI表示的,addresses应

该为 new URI(“tel:+86手机号码”)

String senderName:String类型,说明发送源地址号码

ChargingInformation charging:计费类型

String message:消息内容

byte[] image:图片数据

cn.com.chinatelecom.www.schema.ctcc.sms.v2_1.SmsFormat smsFormat:短信格式

SimpleReference receiptRequest:接口回调地址,也就是该短信的状态报告返回地址。

public java.lang.String sendSmsRingtone(

org.apache.axis.types.URI[] addresses,

java.lang.String senderName,

cn.com.chinatelecom.www.schema.ctcc.common.v2_1.ChargingInformation charging,

java.lang.String ringtone,

cn.com.chinatelecom.www.schema.ctcc.sms.v2_1.SmsFormat smsFormat,

cn.com.chinatelecom.www.schema.ctcc.common.v2_1.SimpleReference receiptRequest

)

throws

java.rmi.RemoteException,

cn.com.chinatelecom.www.schema.ctcc.common.v2_1.PolicyException,

cn.com.chinatelecom.www.schema.ctcc.common.v2_1.ServiceException;

说明:

org.apache.axis.types.URI[] addresses:接受方地址,由于这里是 URI表示的,addresses应

该为 new URI(“tel:+86手机号码”)

String senderName:String类型,说明发送源地址号码

ChargingInformation charging:计费类型

String message:消息内容

java.lang.String ringtone,:铃声数据

cn.com.chinatelecom.www.schema.ctcc.sms.v2_1.SmsFormat smsFormat:短信格式

Page 27: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

27

SimpleReference receiptRequest:接口回调地址,也就是该短信的状态报告返回地址。

public cn.com.chinatelecom.www.schema.ctcc.sms.v2_1.DeliveryInformation[]

getSmsDeliveryStatus(

java.lang.String requestIdentifier

) throws

java.rmi.RemoteException,

cn.com.chinatelecom.www.schema.ctcc.common.v2_1.PolicyException,

cn.com.chinatelecom.www.schema.ctcc.common.v2_1.ServiceException;

说明:查询消息发送状态,requestIdentifier为发送消息时候返回的字串。

我们这里需要调用的是 SendSms方法

String DestNum = "tel:+86153XXXXXXXX"; // 发送号码

String ServiceID = "21位的业务编号,2开头"; // 业务编号

/* 设置被叫号码 */

org.apache.axis.types.URI[] addresses = new

org.apache.axis.types.URI[1];

addresses[0] = new org.apache.axis.types.URI(DestNum);

/* 设置ChargingInformation */

ChargingInformation charging = new ChargingInformation();

charging.setDescription("gm");// 描述

charging.setAmount(new BigDecimal(1));// 扣费数目

charging.setCode(ServiceID);// 业务代码

charging.setCurrency("0");

/* 设置SimpleReference */

SimpleReference receiptRequest = new SimpleReference();

receiptRequest.setCorrelator("1001559"); // 序号自己随机增加

receiptRequest.setInterfaceName("SmsNotificationService");

receiptRequest.setEndpoint(new org.apache.axis.types.URI(

"http://开头的接收短信状态报告的webserivce地址,后续章节会讲"));

/* 发送短信 */

String result = sendSms.sendSms(addresses, senderName, charging,

message, receiptRequest);

System.out.println("result:" + result);

Page 28: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

28

调用 sendSms的方法以后,系统会返回一个短信表示字串叫做 RequestIdentifier,这个

为长度为 32位的数字,类似在短信网关里面MessageID,用以识别短信,如果后续查询短

信状态报告的时候都需要通过此参数进行查询。比如 19120004a924d2c021b1,前面 191200

前面 6为表示 isag的设备号,后面一位 0预留,4a924d2c021b1由设备厂商内部编码。

3)设置 SOAP HEADER

正如 1.4.5我们提到与 ISAG交互的时候,需要传送 SOAPHeader以作验证用。

SOAPHeader在 AXIS里面有一个对象:SOAPHeaderElement对应:

相关文档可以查询:

http://ws.apache.org/axis/java/apiDocs/org/apache/axis/message/SOAPHeaderElement.html

我们看下 SOAPHeaderElement的定义:

public class SOAPHeaderElement

extends MessageElement

implements SOAPHeaderElement

A simple header element abstraction. Extends MessageElement with header-specific

stuff like mustUnderstand, actor, and a 'processed' flag.

它是MessageElement的扩展类,用于定义 SOAP HEADER

其构造函数有如下方法:

SOAPHeaderElement构造函数

SOAPHeaderElement(Name name)

SOAPHeaderElement(QName qname)

SOAPHeaderElement(QName qname, java.lang.Object value)

SOAPHeaderElement(java.lang.String namespace,

java.lang.String localPart)

SOAPHeaderElement(java.lang.String namespace,

java.lang.String localPart, java.lang.Object value)

Page 29: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

29

SOAPHeaderElement(java.lang.String namespace,

java.lang.String localPart, java.lang.String prefix,

org.xml.sax.Attributes attributes, DeserializationContext context)

参考文档《中国电信综合业务接入网关_ISAG_开放接口协议 01-总册 RC V1.0.0》7.2

SOAP Header的定义,我们可以定义如下 soap header:

SOAPHeaderElement SoapHeader = new SOAPHeaderElement(

"http://www.chinatelecom.com.cn/schema/ctcc/common/v2_1",

"RequestSOAPHeader");

如何在 SOAP Header设置参数名和参数的值,我们看下 SOAPHeaderElement的父类MessageElement,

其增加参数的方法为:addChildElement

返回值 方法

SOAPElement addChildElement(Name childName)

add the child element

SOAPElement addChildElement(SOAPElement element)

The added child must be an instance of MessageElement rather than an

abitrary SOAPElement otherwise a (wrapped) ClassCastException will be

thrown.

SOAPElement addChildElement(java.lang.String localName)

add a child element in the message element's own namespace

SOAPElement addChildElement(java.lang.String localName, java.lang.String

prefixName)

add a child element

SOAPElement java.lang.String childPrefix, java.lang.String uri)

add a child element

通过 MessageElement的 addChildElement 方法我们可以增加一个 SOAP Header

Page 30: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

30

的一个参数,我们的参数比较简单,都为 String 字段,所以可以直接使用

addChildElement(java.lang.String localName)方法进行参数的增加。

addChildElement其返回值为 SOAPElement,所以我们可以直接通过 SOAPElement

设置相应的值,我们继续看下 SOAPElement ,他有

SOAPElement addTextNode(java.lang.String text)

Creates a new Text object initialized with the given String and adds it to

this SOAPElement object.

由于与 ISAG涉及到的参数都是 String类型,所以我们可以通过方法:

//增加SPID

SoapHeader.addChildElement("spId").addTextNode("12100000");

可以通过上述这样的方法来设置 spId(注意字母的大小写),其他同理。

整个代码如下:

/* 设置SOAP Header */

SoapHeader.addChildElement("spId").addTextNode(SPID); // SpID

SoapHeader.addChildElement("timeStamp").addTextNode(TimeStamp);

SoapHeader.addChildElement("spPassword").addTextNode(

MD5.compile(SPID + Token + TimeStamp).toUpperCase());// MD5

加密

SoapHeader.addChildElement("productId").addTextNode(ProductID);

SoapHeader.addChildElement("OA").addTextNode(DestNum);

SoapHeader.addChildElement("FA").addTextNode(DestNum);

SoapHeader.addChildElement("multicastMessaging").addTextNode(

"false"); // 是否群发

绑定 SOAP HEADER

我们构建好 SOAP Header以后需要将 Soap header与服务请求进行绑定,代码如

下:

Page 31: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

31

((SendSmsBindingStub) sendSms).setHeader(SoapHeader);

4)抛出异常处理

在 SendSms类的使用介绍中,我们知道 SendSms方法会抛出 3个异常:

java.rmi.RemoteException

cn.com.chinatelecom.www.schema.ctcc.common.v2_1.PolicyException

cn.com.chinatelecom.www.schema.ctcc.common.v2_1.ServiceException

java.rmi.RemoteException是 Java远程调用产生的异常,产生这个异常的原因很多这里不再详述。

PolicyException,ServiceException这两个异常是 ISAG服务端程序抛出的,具体异常查看规范《中

国电信综合业务网关_ISAG_开发接口协议 01-总册》查询。这两个异常都会返回 Messsage Id,以说明

错误的类别;会返回 Text是错误的描述;还会返回 Variables数组说明错误的具体错误原因。

因为这个两个错误很容易发生,比如用户订购关系不存在会抛出 ServiceException;比如你没有接

口权限会抛出 PolicyException。为了程序能够正常处理,我们需要对这两个异常做特殊处理。代码如

下:

catch

(cn.com.chinatelecom.www.schema.ctcc.common.v2_1.ServiceException e) {

//鉴权失败

System.out.println("MessageId:" + e.getMessageId());

System.out.println("Text:" + e.getText());

String[] variables = e.getVariables();

for (int i = 0; i < variables.length; i++) {

System.out.println("Variable:" + variables[i]);

}

} catch (cn.com.chinatelecom.www.schema.ctcc.common.v2_1.PolicyException

e) {

System.out.println("MessageId:" + e.getMessageId());

System.out.println("Text:" + e.getText());

String[] variables = e.getVariables();

for (int i = 0; i < variables.length; i++) {

System.out.println("Variable:" + variables[i]);

}

}

Page 32: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

32

5)查询短信状态

查询短信状态有多种方式,这个章节只讨论如何通过SendSms接口的getSmsDeliverStatus方式来实现,

在开始的例子中的main方法的结束前增加如下几行。

代码如下:

//等待10秒以后再查询

try {

Thread.sleep(10000);

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

DeliveryInformation[] delvierinfo = sendSms

.getSmsDeliveryStatus(result);

for (int i = 0; i < delvierinfo.length; i++) {

System.out.println("发送号码:" + delvierinfo[i].getAddress());

System.out

.println("发送状态:" +

delvierinfo[i].getDeliveryStatus());

}

6)封装短信发送

SendSmsTest.java演示了如何发送短信,但是如果每个程序都这样调用的过于繁琐。我们需要把短信方

法封装成一个方便使用的类。

类:cn.com.chinatelecom.sms.SMS

package cn.com.chinatelecom.sms;

import java.math.BigDecimal;

import java.net.MalformedURLException;

import java.text.SimpleDateFormat;

import java.util.Date;

import javax.xml.rpc.ServiceException;

import org.apache.axis.message.SOAPHeaderElement;

import cn.com.chinatelecom.sms.bean.Result;

Page 33: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

33

import cn.com.chinatelecom.util.MD5;

import cn.com.chinatelecom.www.schema.ctcc.common.v2_1.ChargingInformation;

import cn.com.chinatelecom.www.schema.ctcc.common.v2_1.SimpleReference;

import cn.com.chinatelecom.www.wsdl.ctcc.sms.send.v2_1._interface.SendSms;

import

cn.com.chinatelecom.www.wsdl.ctcc.sms.send.v2_1.service.SendSmsBindingStub;

import

cn.com.chinatelecom.www.wsdl.ctcc.sms.send.v2_1.service.SendSmsServiceLocator;

public class SMS {

private java.lang.String SendSms_address =

"http://localhost:9080/SendSmsService/services/SendSms";

private String SPID = "";

private String Token = "";

private SendSmsServiceLocator ssl;

private SendSms sendSms;

private String receiptRequestUrl = "";

private String receiptRequestInterfaceName = "notifySmsDeliveryReception";

private ChargingInformation chargingInformation = new ChargingInformation();

public SMS(String spid, String token, String ws_address,

String receiptrequestUrl) throws MalformedURLException,

ServiceException {

this.SendSms_address = ws_address;

this.ssl = new SendSmsServiceLocator();

this.receiptRequestUrl = receiptrequestUrl;

this.sendSms = ssl.getSendSms(new java.net.URL(this.SendSms_address));

this.SPID = spid;

this.Token = token;

this.chargingInformation.setDescription("Free");

this.chargingInformation.setAmount(new BigDecimal(1));// 扣费数目

this.chargingInformation.setCurrency("0");

}

public void SetChargingInformation(ChargingInformation charginginformation){

//设置计费规则

this.chargingInformation=charginginformation;

}

public Result SendSms(String sendnum, String destnum, String message,

String productid, String serviceid) {

String[] destnumarray = new String[1];

destnumarray[0] = destnum;

return SendSms(sendnum, destnumarray, message, productid, serviceid);

Page 34: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

34

}

public Result SendSms(String sendnum, String[] destnum, String message,

String productid, String serviceid) {

try {

// 设置SoapHeader

SOAPHeaderElement SoapHeader = new SOAPHeaderElement(

"http://www.chinatelecom.com.cn/schema/ctcc/common/v2_1",

"RequestSOAPHeader");

String TimeStamp = dateString();

SoapHeader.addChildElement("spId").addTextNode(this.SPID); // SpID

SoapHeader.addChildElement("timeStamp").addTextNode(TimeStamp);

SoapHeader.addChildElement("spPassword").addTextNode(

MD5.compile(SPID + Token + TimeStamp).toUpperCase());// MD5加

SoapHeader.addChildElement("productId").addTextNode(productid);

if (destnum.length == 1) {

SoapHeader.addChildElement("OA").addTextNode(

FormatMsisdn(destnum[0]));

SoapHeader.addChildElement("FA").addTextNode(

FormatMsisdn(destnum[0]));

SoapHeader.addChildElement("multicastMessaging").addTextNode(

"false"); // 是否群发

} else {

SoapHeader.addChildElement("multicastMessaging").addTextNode(

"true"); // 是否群发

}

((SendSmsBindingStub) this.sendSms).setHeader(SoapHeader); // 添加

SOAP头

/* 设置被叫号码 */

org.apache.axis.types.URI[] addresses = new

org.apache.axis.types.URI[destnum.length];

for (int i = 0; i < destnum.length; i++) {

destnum[i] = FormatMsisdn(destnum[i]);

addresses[i] = new org.apache.axis.types.URI(destnum[i]);

}

/* 设置ChargingInformation */

Page 35: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

35

this.chargingInformation.setCode(serviceid);// 业务代码

/* 设置SimpleReference */

SimpleReference receiptRequest = new SimpleReference();

String Correlator = getCorrelator();

receiptRequest.setCorrelator(Correlator); // 序号自己随机增加

receiptRequest.setInterfaceName(this.receiptRequestInterfaceName);

receiptRequest.setEndpoint(new org.apache.axis.types.URI(

this.receiptRequestUrl));

Result result = new Result();

result.RequestIdentifier = this.sendSms.sendSms(addresses, sendnum,

this.chargingInformation, message, receiptRequest);

result.Correlator=Correlator;

return result;

} catch

(cn.com.chinatelecom.www.schema.ctcc.common.v2_1.ServiceException e) {

// 鉴权失败

Result result = new Result();

result.MessageId = e.getMessageId();

result.Text = e.getText();

result.Variables = e.getVariables();

return result;

} catch (cn.com.chinatelecom.www.schema.ctcc.common.v2_1.PolicyException

e) {

// 规则健权失败

Result result = new Result();

result.MessageId = e.getMessageId();

result.Text = e.getText();

result.Variables = e.getVariables();

return result;

} catch (Exception e) {

// 其他错误

e.printStackTrace();

Result result = new Result();

result.MessageId = "-1";

result.Text = "Unkonw error";

result.Variables = new String[1];

result.Variables[0] = "Unkonw error";

return result;

}

Page 36: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

36

}

private static String FormatMsisdn(String msisdn) {

msisdn = msisdn.toLowerCase();

if ((msisdn.indexOf("+86") < 0) && (msisdn.indexOf("tel:") != 0)

&& (msisdn.length() == 11)) {

// 不带+86,不带tel:的手机号码

return "tel:+86" + msisdn;

}

if ((msisdn.indexOf("86") == 0) && (msisdn.indexOf("tel:") != 0)

&& (msisdn.length() == 13)) {

// 带86,不带+号,不带tel:的手机号码

return "tel:+" + msisdn;

}

return msisdn;

}

private static String getCorrelator() {

SimpleDateFormat sdf = new SimpleDateFormat("MMddHHmmss");

return sdf.format(new Date());

}

private static String dateString() {

SimpleDateFormat sdf = new SimpleDateFormat("MMddHHmmss");

return sdf.format(new Date());

}

}

为了方便返回数据,还需要构建一个类cn.com.chinatelecom.sms.bean.Result:

package cn.com.chinatelecom.sms.bean;

public class Result {

public String RequestIdentifier=null;

public String Correlator="";

public String MessageId="";

public String Text="";

public String[] Variables;

}

Page 37: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

37

另外加密用的的cn.com.chinatelecom.util.MD5

package cn.com.chinatelecom.util;

import java.security.*;

public class MD5 {

private final static String[] hexDigits = {

"0", "1", "2", "3", "4", "5", "6", "7",

"8", "9", "a", "b", "c", "d", "e", "f"};

/**

* 转换字节数组为16进制字串

* @param b 字节数组

* @return 16进制字串

*/

public static String byteArrayToHexString(byte[] b) {

StringBuffer resultSb = new StringBuffer();

for (int i = 0; i < b.length; i++) {

resultSb.append(byteToHexString(b[i]));

}

return resultSb.toString();

}

private static String byteToHexString(byte b) {

int n = b;

if (n < 0)

n = 256 + n;

int d1 = n / 16;

int d2 = n % 16;

return hexDigits[d1] + hexDigits[d2];

}

public static String compile(String origin) {

String resultString = null;

try {

resultString=new String(origin);

MessageDigest md = MessageDigest.getInstance("MD5");

resultString=byteArrayToHexString(md.digest(resultString.getBytes()));

}

catch (Exception ex) {

}

Page 38: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

38

return resultString;

}

}

生成这3个类以后,我们可以非常方便的使用这个接口:

cn.com.chinatelecom.sms.SMS

方法 说明

public SMS(String spid, String token, String

ws_address,

String receiptrequestUrl) throws

MalformedURLException,

SMS构造函数,参数分别为:SPID,密钥,发

送短信服务器端地址,发送短信回执地址

public void

SetChargingInformation(ChargingInformation

charginginformation)

设置计费规则,可选

public Result SendSms(String sendnum, String

destnum, String message,

String productid, String

serviceid)

发送单条短信。参数为:发送号码,目的号

码,消息内容,产品编号,服务编号

返回值为:Result。

其中Result.RequestIdentifier不为null值,表

示短信已经提交给网关,如果为null,表示

提交失败

public Result SendSms(String sendnum,

String[] destnum, String message,

String productid, String

serviceid)

群发短信。参数为:发送号码,目的号码数

组,消息内容,产品编号,服务编号。返回

值为:Result。

其中Result.RequestIdentifier不为null值,表

示短信已经提交给网关,如果为null,表示

提交失败

我们可以测试下上述这个类,代码如下:

SMS sms=new SMS("spid","linlu","ISAG接口地址","短信回执接口地址");

Result result = sms.SendSms("主叫号码", "用户号码", "短信内容", "产品编号", "

Page 39: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

39

服务编号");

if (result.RequestIdentifier!=null){

System.out.println("短信发送成功!");

System.out.println("RequestIdentifier:"+result.RequestIdentifier);

System.out.println("Correlator:"+result.Correlator);

}else {

System.out.println("短信发送失败!");

System.out.println("MessageId:"+result.MessageId);

System.out.println("Text:"+result.Text);

}

注:红色部分替换成实际数据

2.3 SendSMS短信上行,回执接口开发

短信上行的接口需要SP端作为服务器端,并实现SmsNotification接口,其中方法

notifySmsReception方法实现短信上行接受,notifySmsDeliveryReception实现短信回执接

收。

方法同样,找到我们先前导入的wsdl文件:ctcc_sms_notification_service_2_1.wsdl

按右键,选择Web Services->Generate Java bean skeleton

在出现的界面里面,选择Finish按钮即可:

Page 40: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

40

Eclipse会自动生成如下文件:

Page 41: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

41

获取SoapHeader:

由于在点播业务中,我们还需要换取LinkID,在下发的时候需要带上LinkID,由于

LinkID存在于服务器的请求的SoapHeader中,我们还需要获取SoapHeader相应的数值。

代码如下:

NotifySOAPHeader.java

package cn.com.chinatelecom.ws;

import java.util.Iterator;

import javax.xml.soap.SOAPElement;

import org.apache.axis.AxisFault;

import org.apache.axis.message.SOAPEnvelope;

import org.apache.axis.message.SOAPHeaderElement;

public class NotifySOAPHeader {

private String spRevId = "";

private String spRevpassword = "";

private String spId = "";

private String SAN = "";

private String linkId = "";

public String getSpRevId() {

return spRevId;

}

public NotifySOAPHeader(SOAPEnvelope envelope) {

try {

SOAPHeaderElement soapHeaderElement = envelope.getHeaderByName(

"http://www.chinatelecom.com.cn/schema/ctcc/common/v2_1",

"NotifySOAPHeader");

Iterator iterator = soapHeaderElement.getChildElements();

while (iterator.hasNext()) {

SOAPElement element = (SOAPElement) iterator.next();

String elementName = element.getElementName().getLocalName();

if (elementName.equals("spRevId")) {

this.spRevId = element.getValue();

} else if (elementName.equals("spRevpassword")) {

this.spRevpassword = element.getValue();

Page 42: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

42

} else if (elementName.equals("spId")) {

this.spId = element.getValue();

} else if (elementName.equals("SAN")) {

this.SAN = element.getValue();

} else if (elementName.equals("linkId")) {

this.linkId = element.getValue();

}

}

} catch (AxisFault e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

public void setSpRevId(String spRevId) {

this.spRevId = spRevId;

}

public String getSpRevpassword() {

return spRevpassword;

}

public void setSpRevpassword(String spRevpassword) {

this.spRevpassword = spRevpassword;

}

public String getSpId() {

return spId;

}

public void setSpId(String spId) {

this.spId = spId;

}

public String getSAN() {

return SAN;

}

public void setSAN(String sAN) {

SAN = sAN;

}

public String getLinkId() {

Page 43: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

43

return linkId;

}

public void setLinkId(String linkId) {

this.linkId = linkId;

}

}

说明:public NotifySOAPHeader(SOAPEnvelope envelope)是该类的构造函数,通过传入

SOAPEnvelope,程序从SOAPEnvelope分析出需要用到的SoapHeader里面的具体数值。 其中

SOAPEnvelope可以在WebService的具体实现代码里面:XXXXXXImpl.java里面通过以下代码获取:

// 获取soapheader

NotifySOAPHeader notifySOAPHeader = new NotifySOAPHeader(

MessageContext.getCurrentContext().getRequestMessage()

.getSOAPEnvelope());

System.out.println("LinkId:"+notifySOAPHeader.getLinkId());

System.out.println("SAN:"+notifySOAPHeader.getSAN());

System.out.println("SpId:"+notifySOAPHeader.getSpId());

System.out.println("RevId:"+notifySOAPHeader.getSpRevId());

System.out.println("SpRevpassword:"+notifySOAPHeader.getSpRevpassword());

其中SmsNotificationBindingImpl.java是我们需要继续增加处理逻辑的文件。

package cn.com.chinatelecom.www.wsdl.ctcc.sms.notification.v2_1.service;

import java.io.FileWriter;

import java.io.IOException;

import cn.com.chinatelecom.ws.NotifySOAPHeader;

public class SmsNotificationBindingImpl implements

cn.com.chinatelecom.www.wsdl.ctcc.sms.notification.v2_1._interface.SmsNotification{

public void notifySmsReception(java.lang.String registrationIdentifier,

cn.com.chinatelecom.www.schema.ctcc.sms.v2_1.SmsMessage message) throws

java.rmi.RemoteException {

// 获取soapheader

NotifySOAPHeader notifySOAPHeader = new NotifySOAPHeader(

MessageContext.getCurrentContext().getRequestMessage()

.getSOAPEnvelope());

Page 44: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

44

System.out.println("LinkId:"+notifySOAPHeader.getLinkId());

System.out.println("SAN:"+notifySOAPHeader.getSAN());

System.out.println("SpId:"+notifySOAPHeader.getSpId());

System.out.println("RevId:"+notifySOAPHeader.getSpRevId());

System.out.println("SpRevpassword:"+notifySOAPHeader.getSpRevpassword());

try {

FileWriter fw=new FileWriter("c:\\sms.log",true);

fw.write("registrationIdentifier:"+registrationIdentifier + "\r\n");

fw.write("Sender:"+message.getSenderAddress()+ "\r\n");

fw.write("Message:"+message.getMessage()+ "\r\n");

fw.write("LinkId:"+notifySOAPHeader.getLinkId()+ "\r\n");

fw.close();

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

public void notifySmsDeliveryReceipt(java.lang.String correlator,

cn.com.chinatelecom.www.schema.ctcc.sms.v2_1.DeliveryInformation deliveryStatus)

throws java.rmi.RemoteException {

try {

FileWriter fw = new FileWriter("c:\\deliver.log",true);

fw.write("correlator:"+correlator + "\r\n");

fw.write("Address:"+deliveryStatus.getAddress()+ "\r\n");

fw.write("DeliveryStatus:"+deliveryStatus.getDeliveryStatus()+ "\r\n");

fw.close();

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

绿底部分会在自动生成代码基础上增加的代码,作用是将短信上行写到文件

Page 45: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

45

c:\sms.log,状态报告写在:c:\ deliver.log。当然实际的程序远比这个复杂,或者入库或者

触发相应的程序。

代码完成后,由于接收上行消息和接收状态报告是Web Service服务端程序,需要部署

到Web服务器上;因此需要生成war文件,方法如下,选中sms项目,按右键选择

Export->WAR file,生成的文件拷贝到Tomcat安装目录的webapps目录下,tomcat会自动部

署该文件。

Page 46: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

46

3. WAP PUSH

3.1 WAP PUSH简介

WAP业务发展初期基本上采用的是PULL的方式,也就是说你需要访问某个WAP网站,

那么需要打开 WAP浏览器,并且输入网址比如:http://wap.vnet.mobi,访问中国电信

的WAP门户网站。由于手机输入网址不方便,而且用户并非无时无刻连接在WAP网站上,

所以鉴于这样的原因,OMA提出了WAP PUSH的概念。其主要思想是通过某种特殊短信,

来触发用户使用某种WAP业务。

WAP PUSH目前主要分成两种,一种 Service Indication方式(简称 SI),通过短

信方式PUSH相关WAP网址以及简要的说明,至于用户是否访问这个网址,需要用户点击

确认。当然手机厂家对于 SI方式的 WAP PUSH展示方式不一样,显示的界面有很大的区

别,所以那个按钮可能叫确认也有可能叫转向。这个也就是窄义上的 WAP PUSH,也是本

章节讲解的WAP PUSH。

另外一种WAP PUSH类型叫 Service Loading(简称 SL),通过短信Push一个网

址,手机主动联网并且下载该文件并且保存,最终以某种形式展示给用户。SL的种类非

常多,最典型的就是彩信,当用户手机收到SL消息以后,自动到彩信中心下载彩信内容,

中间不需要用户干预;当然还有一些OTA下载,某些Push Mail也采用类似SL的方式,

这里不再详述。

3.2 WAP PUSH网络结构和业务实现

我们看下目前WAP PUSH网络的结构:

Page 47: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

47

在电信网内一个正常的Web Push(SI)下发如上图蓝绿色箭头表示。整个PUSH的实

现从 SP发起,并经过 ISAG,Push代理网关(PPG)以及短信中心,最后以一种特殊短信

的方式下发给用户。其具体流程和接口协议如下:

1. SP使用ISAG的Web Service接口提交SI。

2. ISAG将SP Web Service提交的数据,分装成XML文件,并通过PAP协议(Push

Access Protocol)协议,将SI消息提交给Push代理网关(PPG)。

3. PPG收到 SI消息以后,将 SI消息按照 Push-WSP协议,把 XML文件压缩成

WBXML(WAP Binary XML,一种SI的压缩方式)变成一条或者多条短信,提交给短

信中心。

4. 短信中心下发这条或者这组特殊短信到用户的手机。

当然如果不通过ISAG接入,理论上可以直接通过PAP协议接入PPG代理网关,但

协议相对于Web Service协议要复杂一些。

在GSM的网络中,当然还存在一种特殊的方式,SP侧直接将SI根据WBXML的方式

进行压缩成一条或者多条短信,直接通过短信网关,由短信网关提交给短信中心,从而

实现WAP PUSH的下发,如上图棕色部分箭头。需要说明的是CDMA网络里面的WAP PUSH

封装与GSM存在相当大的差异,如果你通过GSM方式封装的WAP PUSH,用户实际收到的短

信很有可能是一段乱码。虽然我查询了TIA-EIA-637-A,以及OMA相关的WAP的协议,具

体地址:http://www.openmobilealliance.org/Technical/wapindex.aspx,并未找到直

Page 48: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

48

接通过短信网关下发实际有效的 WAP PUSH。比较靠谱的消息是:CDMA网络里面是通过

TeleserviceId来区分普通的短信还是WAP PUSH亦或彩信,而WAP PUSH和彩信都需要设

置TeleserviceId为65002,可惜的是TeleserviceId并不是SMPP协议能够传输的,需

要短信中心对于不同的短信实体进行强制设置,也意味着你无法通过SMGP协议发送WAP

PUSH。当然如果您研究出来在CDMA的网络里面如何通过短信网关发送WAP PUSH,欢迎您

发邮件到我的邮箱:[email protected]

3.3 WAP PUSH是什么?WAP PUSH不是什么?

在您开发Wap Push应用的时候,我觉得有必要跟你说明白“WAP PUSH是什么?WAP

PUSH不是什么?” 这个问题。

首先Wap Push并不是所有的手机都支持,虽然它本身属于OMA标准协议的一部分,

我手头上的CoolPad D18就不支持,当有个Push送达手机的时候,屏幕会一亮,然后就

无下文了。

其次Wap Push在终端上显示有很大的差别,Windows Mobile的系统显示相对比较正

常,而 CoolPad N900一条 Push跟一条文字后面跟着网站地址的普通短信显示上没本质

的区别。

再次Wap Push无法显示主叫号码,在多普达S900C显示的主叫号码为:未验证用户,

而三星的i329显示为系统管理员。这估计也是为什么一度GSM网络的手机收到大量的垃

圾Wap Push短信的一个原因,因为用户都不知道谁发的。

还有需要说明的是,在某些情况下,一条WAP PUSH和一条文字加网站链接构成的普

通的短信在用户的体验上没特别的差别,因为部分手机会自动识别普通短信中带了

http://开头的地址,用户点击这个地址也会自动关联相应的浏览器,唯一的区别 WAP

Push用户点击的时候肯定会通过CTWAP方式连接,而普通短信里面的地址有可能用户连

接的CTWAP,也有可能连接的是CTNET。

当然WAP PUSH跟普通短信还有一个很大的区别是,用户至少直接拿终端(不通过直

接控制通讯模块)是无法发送WAP PUSH的,也意味着无法转发WAP PUSH,这对于某些安

全级别要求较高的业务由于不能转发而不容易泄密,比如可以push你一个通过加密生成

的Url串,Url串里面含有免登录数据,由于无法转发,用户泄漏数据的可能性大大降低。

Page 49: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

49

3.4 WAP Push接口开发环境搭建

为了完成WAP Push开发,我们需要实现以下接口:

接口名称 接口方法 作用 谁是服务器端

SendMessage SendMessage 发送Wap Push

ISAG getMessageDeliverStatus 查询发送状态

MessageNotification notifyMessageDeliveryReception 接收状态报告 SP

你会发现 Wap Push远比短信接口要少,很大的原因是在用户不通过某种特殊途径是

无法直接在终端上发送Wap Push上行的,所以他的MessageNotification里面就没有接

受上行的接口。

1) 新建项目

因为接受状态报告需要SP端作为服务器端,所以我们同样需要新建一个Dynamic Web

Project。

选择File->New->Dynamic Web Project

在弹出的确认窗口我这里起名叫WapPush,你也可以起其他的项目名称

Page 50: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

50

点击Finish,一个项目就新建成功。

2) 导入WSDL文件

开发WAP Push需要用到的WSDL文件如下:

与ISAG接口相关的wsdl

ctcc_wap_push_notification_interface_1_0.wsdl

ctcc_wap_push_notification_service_1_0.wsdl

ctcc_wap_push_send_interface_1_0.wsdl

ctcc_wap_push_send_service_1_0.wsdl

ctcc_wap_push_types_1_0.xsd

ctcc_common_types_2_1.xsd

ctcc_common_faults_2_0.wsdl

与ISMP相关的wsdl

IsmpSpEngine.wsdl (该接口实现不在这个章节讨论)

Page 51: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

51

a)同样我们为了不与其他程序混在一块,为 WSDL文件新建一个目录 wsdl,用户导入

wsdl文件。

b)选择建立的wsdl目录,按右键选择Import

c)选择File System

Page 52: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

52

d)选中我们需要的WSDL文件:

Page 53: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

53

导入完成后我们应该看到Project Explorer有如下文件

3.5 WAP Push发送接口开发

Wap Push 发送的接口过程中,SP 侧的软件是作为 Web Service Client,而 ISAG 作为

Server,所以我们这里需要生成发送接口的客户端软件。

方法非常简单,选中 wsdl目录下的ctcc_wap_push_send_service_1_0.wsdl文件,

按右键选择Web Services->Generate Client。

Page 54: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

54

生成以后,系统会多了如下代码:

其中:SendMessageServiceLocator.java就是我们需要的客户端程序。

我们编写个测试代码:

import java.math.BigDecimal;

import java.net.MalformedURLException;

import java.rmi.RemoteException;

import java.text.SimpleDateFormat;

import java.util.Date;

import javax.xml.rpc.ServiceException;

import javax.xml.soap.SOAPException;

import org.apache.axis.message.SOAPHeaderElement;

import org.apache.axis.types.URI.MalformedURIException;

import cn.com.chinatelecom.util.MD5;

Page 55: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

55

import cn.com.chinatelecom.www.schema.ctcc.common.v2_1.ChargingInformation;

import cn.com.chinatelecom.www.schema.ctcc.common.v2_1.SimpleReference;

import cn.com.chinatelecom.www.schema.ctcc.wap_push.v1_0.MessagePriority;

import cn.com.chinatelecom.www.wsdl.ctcc.wap_push.send.v1_0._interface.SendMessage;

import

cn.com.chinatelecom.www.wsdl.ctcc.wap_push.send.v1_0.service.SendMessageBindingStub;

import

cn.com.chinatelecom.www.wsdl.ctcc.wap_push.send.v1_0.service.SendMessageServiceLocator;

public class SendWapPush {

public static void main(String[] args) {

String webserviceUrl = "ISAG WAP Push地址(非WSDL地址)";

String SPID = "SPID"; // SPID

String Token = "密码"; // 密钥

String DestNum = "tel:+8615305711873"; // 发送号码

String ProductID = "112000000000000004112"; // 产品编号

String ServiceID = "212000000000000002488"; // 业务编号

String TimeStamp = dateString(); // 当前时间

String senderName = "10620068"; // WAP Push主叫号码,在浙江ISAG实际无效,ISAG会读取业

务部署时候的接入号

String targetURL="http://wap.189.cn";

String subject ="189邮箱";

SOAPHeaderElement SoapHeader = new SOAPHeaderElement(

"http://www.chinatelecom.com.cn/schema/ctcc/common/v2_1",

"RequestSOAPHeader");

try {

/* 初始化WebService Client */

SendMessageServiceLocator ssl = new SendMessageServiceLocator();

SendMessage sendMessage = ssl.getSendMessage(new java.net.URL(webserviceUrl));

/* 设置SOAP Header */

SoapHeader.addChildElement("spId").addTextNode(SPID); // SpID

SoapHeader.addChildElement("timeStamp").addTextNode(TimeStamp);

SoapHeader.addChildElement("spPassword").addTextNode(

MD5.compile(SPID + Token + TimeStamp).toUpperCase());// MD5加密

SoapHeader.addChildElement("productId").addTextNode(ProductID);

SoapHeader.addChildElement("OA").addTextNode(DestNum);

Page 56: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

56

SoapHeader.addChildElement("FA").addTextNode(DestNum);

SoapHeader.addChildElement("multicastMessaging").addTextNode(

"false"); // 是否群发

((SendMessageBindingStub) sendMessage).setHeader(SoapHeader); // 添加SOAP头

/* 设置被叫号码 */

org.apache.axis.types.URI[] addresses = new org.apache.axis.types.URI[1];

addresses[0] = new org.apache.axis.types.URI(DestNum);

/* 设置ChargingInformation */

ChargingInformation charging = new ChargingInformation();

charging.setDescription("gm");// 描述

charging.setAmount(new BigDecimal(1));// 扣费数目

charging.setCode(ServiceID);// 业务代码

charging.setCurrency("0");

/* 设置SimpleReference */

SimpleReference receiptRequest = new SimpleReference();

receiptRequest.setCorrelator("1001559"); // 序号自己随机增加

receiptRequest.setInterfaceName("notifyMessageDeliveryReception");

receiptRequest.setEndpoint(new org.apache.axis.types.URI(

"http://202.101.166.196:8080/WebPush/services/notifyMessageDeliveryReception"));

/* 发送WAP PUSH */

String result = sendMessage.sendMessage(addresses, new

org.apache.axis.types.URI(targetURL), senderName, subject, MessagePriority.Normal,

charging, receiptRequest);

System.out.println("result:" + result);

} catch (cn.com.chinatelecom.www.schema.ctcc.common.v2_1.ServiceException e) {

// 鉴权失败

System.out.println("MessageId:" + e.getMessageId());

System.out.println("Text:" + e.getText());

String[] variables = e.getVariables();

for (int i = 0; i < variables.length; i++) {

System.out.println("Variable:" + variables[i]);

}

} catch (cn.com.chinatelecom.www.schema.ctcc.common.v2_1.PolicyException e) {

Page 57: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

57

System.out.println("MessageId:" + e.getMessageId());

System.out.println("Text:" + e.getText());

String[] variables = e.getVariables();

for (int i = 0; i < variables.length; i++) {

System.out.println("Variable:" + variables[i]);

}

} catch (MalformedURLException e) {

e.printStackTrace();

} catch (ServiceException e) {

e.printStackTrace();

} catch (SOAPException e) {

e.printStackTrace();

} catch (MalformedURIException e) {

e.printStackTrace();

} catch (RemoteException e) {

e.printStackTrace();

}

}

private static String dateString() {

SimpleDateFormat sdf = new SimpleDateFormat("MMddHHmmss");

return sdf.format(new Date());

}

}

注:红色部分替换成自己相应的数据。

关于 SOAP Header的设置请查看短信章节里面的说明,设置完全一样。

类似短信方式我们需要初始化一个 SendMessage的 Client,获得一个 SendMessage的对象

/* 初始化WebService Client */

SendMessageServiceLocator ssl = new SendMessageServiceLocator();

SendMessage sendMessage = ssl.getSendMessage(new

java.net.URL(webserviceUrl));

其中 SendMessage有如下方法:

public java.lang.String sendMessage(

org.apache.axis.types.URI[] addresses, //用户号码

org.apache.axis.types.URI targetURL, //WAP PUSH地址

java.lang.String senderAddress, //发送方地址

Page 58: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

58

java.lang.String subject, //WAP PUSH标题

cn.com.chinatelecom.www.schema.ctcc.wap_push.v1_0.MessagePriority priority,

cn.com.chinatelecom.www.schema.ctcc.common.v2_1.ChargingInformation charging,

cn.com.chinatelecom.www.schema.ctcc.common.v2_1.SimpleReference receiptRequest

) throws java.rmi.RemoteException,

cn.com.chinatelecom.www.schema.ctcc.common.v2_1.PolicyException,

cn.com.chinatelecom.www.schema.ctcc.common.v2_1.ServiceException;

说明:这是实际发送WAP Push的方法,多数参数与发送短信类似。

其中 targetURL表示推送给用户的Web地址

subject表示 wap push的标题,也就是 wap push的正文

同样该函数还会返回一个 String字段表示的 requestIdentifier用以说明这条Wap Push的标识

public cn.com.chinatelecom.www.schema.ctcc.wap_push.v1_0.DeliveryInformation[]

getMessageDeliveryStatus(java.lang.String requestIdentifier) throws

java.rmi.RemoteException,

cn.com.chinatelecom.www.schema.ctcc.common.v2_1.PolicyException,

cn.com.chinatelecom.www.schema.ctcc.common.v2_1.ServiceException;

说明:发送结果查询

3.6 WAP Push 状态报告接口开发

Wap Push状态报告接收接口 ,SP侧的软件是作为Web Service Server,而 ISAG作为

Client,所以我们这里需要生成发送接口的服务器端软件。

方法非常简单,选中wsdl目录下的ctcc_wap_push_notification_service_1_0.wsdl

文件,按右键选择Web Services->Generate Java bean skeleton。

生成以后,在Project Explore项目的Java Resources:src分项里面会看到如下文件:

Page 59: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

59

MessageNotificationBindingImpl.java是我们需要完成的代码。代码如下:

package cn.com.chinatelecom.www.wsdl.ctcc.wap_push.notification.v1_0.service;

import java.io.FileWriter;

import java.io.IOException;

public class MessageNotificationBindingImpl

implements

cn.com.chinatelecom.www.wsdl.ctcc.wap_push.notification.v1_0._interface.MessageNotification

{

public void notifyMessageDeliveryReceipt(

java.lang.String correlator,

cn.com.chinatelecom.www.schema.ctcc.wap_push.v1_0.DeliveryInformation

deliveryStatus)

throws java.rmi.RemoteException {

try {

FileWriter fw = new FileWriter("c:\\wappushdeliver.log");

fw.write("correlator:" + correlator);

fw.write("Address:" + deliveryStatus.getAddress());

fw.write("DeliveryStatus:" + deliveryStatus.getStatus());

fw.close();

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

绿底部分会在自动生成代码基础上增加的代码,作用是将短信上行写到文件

c:\sms.log,状态报告写在:c:\ wappushdeliver.log。当然实际的程序远比这个复杂,或者

入库或者触发相应的程序。

代码完成后,由于接收上行消息和接收状态报告是Web Service服务端程序,需要部署

到Web服务器上;因此需要生成war文件,方法如下,选中wappush项目,按右键选择

Page 60: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

60

Export->WAR file,生成的文件拷贝到Tomcat安装目录的webapps目录下,tomcat会自动部

署该文件。

以上是WAP Push的开发文档,相关报文数据可以参考附件二。

Page 61: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

61

4. 彩信

需要先说明的是,CDMA 终端在联通运营期间是不支持彩信的,CDMA 用户可用

的是与彩信相对应的业务 “彩 E”。这意味了原先联通的老 CDMA终端是无法支持彩信

的。彩信是多媒体短信(MMS)的一种,同样“彩 E”也是多媒体彩信的一种,那个技术

更先进或者用户体验更好,这里不讨论。

电信接手 CDMA以后面临的一个现实问题是如何推广多媒体短信。如果选择“彩 E”

那么意味着CDMA的用户无法与已经存在大量的移动用户和联通的G网用户无法互发多

媒体彩信,当然理论上可以通过特殊的网关进行数据转换可以实现互通,但是“彩 E”

允许的文件大小远大于彩信,另外格式上也存在一定的区别,这是互通最大的障碍。

4.1彩信的构成

一条彩信可以由一个标题加一个图片构成,这个其实是目前使用最广泛的,因为彩

信初期,其最大的用处就是用来发照片,手机拍一张照片,然后作为附件发送。毕竟在

手机上要编辑个彩信,需要用户有足够的耐心和一定的美术功底,才能完成一件不错的

彩信作品。

而随着 SP 的引入,制作更加精良的彩信成为可能。而且彩信采用了 SMIL 作为标

记语言,一种类似 HTML的语言,相对来说可扩展性不错。

SMIL是同步多媒体集成语言(Synchronized Multimedia Integration Language)的缩

写,他并不是因彩信而生。它是由W3C指定的规范。关于 SMIL文件的规范可以查看W3c

的上的规范,网址是:http://www.w3.org/AudioVideo/。RealPlay,QuikTime 都能很好的播

放 SMIL文件,自然成为了彩信不错的调试工具。

我们看下一个简单的 SMIL文件:

<smil>

<head>

</head>

<body>

<seq>

<img src="1.jpg" dur="2s"/>

<img src="2.jpg" dur="2s"/>

Page 62: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

62

</seq>

</body>

</smil>

你会发现这个跟 html的语法非常类似,这个 SMIL在红字部分定义了两张图片,1.jpg

和 2.jpg,并且设置了播放时间,分别等待两秒以后播放下一张图片。你可以用写字板把它

保存为 first.smil,并且在 smil文件的保存目录下分别放置 1.jpg和 2.jpg,然后使用 RealPlay

播放,就可以看到效果。

从某种意义上来说彩信就是一条带附件的WAP PUSH。更复杂的 SMIL语法请查看

网上的相关文档,应该有非常全的资料。

4.2 彩信在电信网络里面的网络结构

一个正常的彩信 SP业务如上图所示:

1)SP测发起Web Service请求,彩信内容以Web Service附件的形式发送,彩信内容可

以是一个 SMIL文件+若干图片,亦或就一个图片,亦或就一个文本文件。

2)ISAG收到请求以后会通过MM7协议将彩信提交给彩信中心。

3)彩信中心将附件保存,并且根据 Push协议,将一条 SL(Service Loading)形式的 push

下发给短信中心。

4)短信中心将这条 SL下发给用户

Page 63: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

63

5)用户手机接收到 SL消息以后,自动拨 CTWAP,建立网络连接

6)用户到通过WAP网关到彩信中心的服务器上下载彩信内容。

当然目前有部分接入是直接通过MM7接入彩信中心的,这种接入方式目前不推荐。

另外由于目前用户终端不一定支持彩信,彩信如果无法送达,彩信内容会保存在

http://mmbox.vnet.cn/,用户可以通过 PC到该网站上下载。

4.3 彩信开发接口开发环境搭建

为了完成彩信开发,我们需要实现以下接口:

接口名称 接口方法 作用 谁是服务

器端

SendMessage SendMessage 发送彩信

ISAG getMessageDeliverStatus 查询发送状态

MessageNotification notifyMessageReception 接收彩信上行 SP

notifyMessageDeliveryReception 接收彩信发送状态报告

其中 SendMessage用于彩信发送。MessageNotification用于接收用户的上行彩信和接收状态报告。

1) 新建项目

同样由于 MessageNotification需要 SP作为客户端,所以我们需要新建一个 Dynamic Web Project

我们这里定义这个project名字为mms(可以根据自己实际情况定义)

Page 64: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

64

点击Finish按钮,生成Project

2) 导入WSDL

与彩信信接入相关的wsdl文件有如下:

Page 65: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

65

与ISAG接口相关的wsdl

ctcc_mm_notification_interface_2_2.wsdl

ctcc_mm_notification_service_2_2.wsdl

ctcc_mm_send_interface_2_2.wsdl

ctcc_mm_send_service_2_2.wsdl

ctcc_mm_types_2_2.xsd

ctcc_common_types_2_1.xsd

ctcc_common_faults_2_0.wsdl

与ISMP相关的wsdl

IsmpSpEngine.wsdl (这个章节不做讨论)

同样为了区分其他程序我们新建一个目录wsdl

然后倒入wsdl目录相关的wsdl文件:

Page 66: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

66

完成以后看到mms项目project Explorer下展开如下:

Page 67: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

67

3) 导入AXIS需要的第三方类库

由于彩信接口需要使用到 AXIS的附件功能,需要 JavaBeans(TM) Activation

Framework(JAF)的支持,下载地址:

http://java.sun.com/javase/technologies/desktop/javabeans/jaf/downloads/

另外还需要JavaMail API支持,下载地址:

http://java.sun.com/products/javamail/

以上两个地址下载的zip文件解开以后分别得到一个activation.jar和mail.jar。我

们需要将这两个文件导入到工程里面,方法如下:

展开WebContent->WEB-INF->lib如下图

选中lib目录,按右键,选择Import

Page 68: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

68

选中mail.jar和activation.jar。

Page 69: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

69

彩信发送接口开发

彩信发送接口SP端需要实现的是Web Service客户端代码。同样选中

ctcc_mm_send_service_2_2.wsdl文件,按右键,选择:Web Services->Generate Client:

生成以后在Java Resources:src会产生如下文件:

其中SendMessageServiceLocator.java就是我们需要调用的客户端程序

我们写个测试程序如下:

1)sendmms.java:彩信发送程序

import java.io.File;

import java.math.BigDecimal;

Page 70: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

70

import java.net.MalformedURLException;

import java.rmi.RemoteException;

import java.text.SimpleDateFormat;

import java.util.Date;

import javax.activation.DataHandler;

import javax.activation.FileDataSource;

import javax.xml.rpc.ServiceException;

import javax.xml.soap.SOAPException;

import org.apache.axis.attachments.AttachmentPart;

import org.apache.axis.message.SOAPHeaderElement;

import org.apache.axis.types.URI.MalformedURIException;

import cn.com.chinatelecom.util.MD5;

import cn.com.chinatelecom.www.schema.ctcc.common.v2_1.ChargingInformation;

import cn.com.chinatelecom.www.schema.ctcc.common.v2_1.SimpleReference;

import cn.com.chinatelecom.www.schema.ctcc.multimedia_messaging.v2_2.MessagePriority;

import cn.com.chinatelecom.www.wsdl.ctcc.multimedia_messaging.send.v2_2._interface.SendMessage;

import

cn.com.chinatelecom.www.wsdl.ctcc.multimedia_messaging.send.v2_2.service.SendMessageBindingStub;

import

cn.com.chinatelecom.www.wsdl.ctcc.multimedia_messaging.send.v2_2.service.SendMessageServiceLocator;

public class sendmms {

public static void main(String[] args) {

String SendMessageUrl = "彩信WebService发送地址(非wsdl地址)"; // 彩信接口地址

String spid = "spid"; // SPID

String token = "token"; // 登录密码

String senderAddress = "10620068"; // SP主叫号码

String subject = "彩信标题!"; // 彩信标题

String num = "tel:+8615305711873"; // 目的号码

String productid = "112000000000000004136"; // 产品编号

String serviceid = "212000000000000002500"; // 业务编号

SOAPHeaderElement header = new SOAPHeaderElement(

"http://www.chinatelecom.com.cn/schema/ctcc/common/v2_1",

"RequestSOAPHeader");

String[] mmsFileList = { "c:\\test.smil", "c:\\1.jpg", "c:\\2.jpg" }; // 彩信附件列表

try {

// 初始化彩信WebService客户端软件

SendMessageServiceLocator sml = new SendMessageServiceLocator();

Page 71: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

71

SendMessage sendMms = sml.getSendMessage(new java.net.URL(

SendMessageUrl));

// 设置SoapHeader

header.addChildElement("spId").addTextNode(spid); // SpID

String dateStamp = dateString(); // 当前时间

header.addChildElement("timeStamp").addTextNode(dateStamp);// 设置时间戳

header.addChildElement("spPassword").addTextNode(

MD5.compile(spid + token + dateStamp).toUpperCase());// MD5加密

header.addChildElement("productId").addTextNode(productid); // 设置产品编

// 号

header.addChildElement("OA").addTextNode(num);

header.addChildElement("multicastMessaging").addTextNode("false");

((SendMessageBindingStub) sendMms).setHeader(header); // 添加SOAP头

// 设置被叫号码

org.apache.axis.types.URI[] addresses = new org.apache.axis.types.URI[1];

addresses[0] = new org.apache.axis.types.URI(num);

// 设置优先级

MessagePriority priority = MessagePriority.Normal;

// 设置计费信息

ChargingInformation charging = new ChargingInformation();

charging.setDescription("Test");// 描述

charging.setAmount(new BigDecimal(1));// 扣费数目

charging.setCode(serviceid);// 业务代码

charging.setCurrency("0");

// 设置回执信息

cn.com.chinatelecom.www.schema.ctcc.common.v2_1.SimpleReference receiptRequest

= new SimpleReference();

receiptRequest.setCorrelator("100155");

receiptRequest.setInterfaceName("notifyMessageDeliveryReception");

receiptRequest

.setEndpoint(new org.apache.axis.types.URI(

"http://202.101.166.196:8080/mms/services/MessageNotification"));

// 添加附件

for (int i = 0; i < mmsFileList.length; i++) {

File file = new File(mmsFileList[i]);

AttachmentPart ap=new AttachmentPart(new DataHandler(new FileDataSource(file)));

ap.setContentId(file.getName());

ap.setContentLocation(file.getName());

Page 72: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

72

if (file.getName().indexOf("smil")>0){

ap.setContentType("application/smil");

}

((SendMessageBindingStub) sendMms)

.addAttachment(ap);

}

// 发送彩信

String result = sendMms.sendMessage(addresses, senderAddress,

subject, priority, charging, receiptRequest);

System.out.println(result);

} catch (cn.com.chinatelecom.www.schema.ctcc.common.v2_1.ServiceException e) {

// 鉴权失败

System.out.println("MessageId:" + e.getMessageId());

System.out.println("Text:" + e.getText());

String[] variables = e.getVariables();

for (int i = 0; i < variables.length; i++) {

System.out.println("Variable:" + variables[i]);

}

} catch (cn.com.chinatelecom.www.schema.ctcc.common.v2_1.PolicyException e) {

// 规则失败

System.out.println("MessageId:" + e.getMessageId());

System.out.println("Text:" + e.getText());

String[] variables = e.getVariables();

for (int i = 0; i < variables.length; i++) {

System.out.println("Variable:" + variables[i]);

}

} catch (MalformedURLException e) {

e.printStackTrace();

} catch (ServiceException e) {

e.printStackTrace();

} catch (SOAPException e) {

e.printStackTrace();

} catch (MalformedURIException e) {

e.printStackTrace();

} catch (RemoteException e) {

e.printStackTrace();

}

}

Page 73: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

73

/*

* 生成当前时间戳

*/

private static String dateString() {

SimpleDateFormat sdf = new SimpleDateFormat("MMddHHmmss");

return sdf.format(new Date());

}

}

2)cn.com.chinatelecom.util.MD5.java : soapheader需要的加密类

package cn.com.chinatelecom.util;

import java.security.*;

public class MD5 {

private final static String[] hexDigits = {

"0", "1", "2", "3", "4", "5", "6", "7",

"8", "9", "a", "b", "c", "d", "e", "f"};

/**

* 转换字节数组为16进制字串

* @param b 字节数组

* @return 16进制字串

*/

public static String byteArrayToHexString(byte[] b) {

StringBuffer resultSb = new StringBuffer();

for (int i = 0; i < b.length; i++) {

resultSb.append(byteToHexString(b[i]));

}

return resultSb.toString();

}

private static String byteToHexString(byte b) {

int n = b;

if (n < 0)

n = 256 + n;

int d1 = n / 16;

int d2 = n % 16;

return hexDigits[d1] + hexDigits[d2];

}

public static String compile(String origin) {

String resultString = null;

try {

Page 74: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

74

resultString=new String(origin);

MessageDigest md = MessageDigest.getInstance("MD5");

resultString=byteArrayToHexString(md.digest(resultString.getBytes()));

}

catch (Exception ex) {

}

return resultString;

}

}

注:程序红色部分替换成实际参数

程序说明:

1) 获取彩信发送对象SendMessgage

// 初始化彩信WebService客户端软件

SendMessageServiceLocator sml = new SendMessageServiceLocator();

SendMessage sendMms = sml.getSendMessage(new java.net.URL(

SendMessageUrl));

2) 设置SoapHeader

// 设置SoapHeader

header.addChildElement("spId").addTextNode(spid); // SpID

String dateStamp = dateString(); // 当前时间

header.addChildElement("timeStamp").addTextNode(dateStamp);// 设置时间戳

header.addChildElement("spPassword").addTextNode(

MD5.compile(spid + token + dateStamp).toUpperCase());// MD5加密

header.addChildElement("productId").addTextNode(productid); // 设置产品编号

header.addChildElement("OA").addTextNode(num);

header.addChildElement("multicastMessaging").addTextNode("false");

((SendMessageBindingStub) sendMms).setHeader(header); // 添加SOAP头

3) 设置附件

// 添加附件

Page 75: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

75

for (int i = 0; i < mmsFileList.length; i++) {

File file = new File(mmsFileList[i]);

AttachmentPart ap=new AttachmentPart(new DataHandler(new

FileDataSource(file)));

ap.setContentId(file.getName());

ap.setContentLocation(file.getName());

if (file.getName().indexOf("smil")>0){

ap.setContentType("application/smil");

}

((SendMessageBindingStub) sendMms)

.addAttachment(ap);

}

4) 发送彩信

String result = sendMms.sendMessage(addresses, senderAddress,

subject, priority, charging, receiptRequest);

System.out.println(result);

为了便于其他程序调用,我们需要对这个进行封装。MMS.java

package cn.com.chinatelecom.mms;

import java.io.File;

import java.math.BigDecimal;

import java.rmi.RemoteException;

import java.text.SimpleDateFormat;

import java.util.Date;

import javax.activation.DataHandler;

import javax.activation.FileDataSource;

import javax.xml.rpc.ServiceException;

import javax.xml.soap.SOAPException;

import org.apache.axis.attachments.AttachmentPart;

import org.apache.axis.message.SOAPHeaderElement;

import cn.com.chinatelecom.isag.bean.Result;

import cn.com.chinatelecom.util.MD5;

import cn.com.chinatelecom.www.schema.ctcc.common.v2_1.ChargingInformation;

Page 76: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

76

import cn.com.chinatelecom.www.schema.ctcc.common.v2_1.SimpleReference;

import cn.com.chinatelecom.www.schema.ctcc.multimedia_messaging.v2_2.MessagePriority;

import cn.com.chinatelecom.www.wsdl.ctcc.multimedia_messaging.send.v2_2._interface.SendMessage;

import

cn.com.chinatelecom.www.wsdl.ctcc.multimedia_messaging.send.v2_2.service.SendMessageBindingStub;

import

cn.com.chinatelecom.www.wsdl.ctcc.multimedia_messaging.send.v2_2.service.SendMessageServiceLocator;

public class MMS {

private String endPointUrl = "http://10.10.10.10:8080/isag/North/MMS/SendMessage";

private String spid="";

private String token="";

private String spnum="";

public MMS() {

}

public MMS(String Url,String spid,String token,String spnum) {

this.endPointUrl = Url;

this.spid =spid;

this.token =token;

this.spnum = spnum;

}

private static String dateString() {

SimpleDateFormat sdf = new SimpleDateFormat("MMddHHmmss");

return sdf.format(new Date());

}

private SOAPHeaderElement setSoapHeader(String productid, String num)

throws SOAPException {

SOAPHeaderElement header = new SOAPHeaderElement(

"http://www.chinatelecom.com.cn/schema/ctcc/common/v2_1",

"RequestSOAPHeader");

header.addChildElement("spId").addTextNode(this.spid);

String dateStamp = dateString();

header.addChildElement("timeStamp").addTextNode(dateStamp);

header.addChildElement("spPassword").addTextNode(

MD5.compile(this.spid + this.token + dateStamp).toUpperCase());

header.addChildElement("productId").addTextNode(productid);

header.addChildElement("OA").addTextNode(num);

header.addChildElement("multicastMessaging").addTextNode("false");

return header;

Page 77: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

77

}

private static String getCorrelator() {

SimpleDateFormat sdf = new SimpleDateFormat("MMddHHmmss");

return sdf.format(new Date());

}

private static String [] getFileList(String path){

File dir = new File(path);

String[] filelist = dir.list();

for (int i = 0; i < filelist.length; i++) {

filelist[i]=path+"/"+filelist[i];

if (filelist[i].indexOf(".smil".toLowerCase())>0 && i!=0) {

String tempstr=filelist[i];

filelist[i]=filelist[0];

filelist[0]=tempstr;

}

System.out.println(filelist[i]);

}

return filelist;

}

public Result sendMMS(String productid, String serviceid, String num, String subject,

String mmspath) {

try {

SendMessageServiceLocator sml = new SendMessageServiceLocator();

SendMessage sendMms = sml.getSendMessage(new java.net.URL(

this.endPointUrl));

SOAPHeaderElement header = this.setSoapHeader(productid,

num);

((SendMessageBindingStub) sendMms).setHeader(header);

org.apache.axis.types.URI[] addresses = new org.apache.axis.types.URI[1];

addresses[0] = new org.apache.axis.types.URI(num);

MessagePriority priority = MessagePriority.Normal;

String [] mmsFileList = getFileList(mmspath);

for (int i = 0; i < mmsFileList.length; i++) {

File file = new File(mmsFileList[i]);

Page 78: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

78

AttachmentPart ap=new AttachmentPart(new DataHandler(new FileDataSource(file)));

ap.setContentId(file.getName());

ap.setContentLocation(file.getName());

if (file.getName().indexOf("smil")>0){

ap.setContentType("application/smil");

} else {

ap.setContentType(ap.getContentType());

}

((SendMessageBindingStub) sendMms)

.addAttachment(ap);

}

ChargingInformation charging = new ChargingInformation();

charging.setDescription("Free");

charging.setAmount(new BigDecimal(1));

charging.setCode(serviceid);

charging.setCurrency("0");

cn.com.chinatelecom.www.schema.ctcc.common.v2_1.SimpleReference receiptRequest

= new SimpleReference();

String Correlator=getCorrelator();

receiptRequest.setCorrelator(getCorrelator());

receiptRequest.setInterfaceName("notifyMessageDeliveryReception");

receiptRequest

.setEndpoint(new org.apache.axis.types.URI(

"http://202.101.165.154:8080/mms/services/MessageNotification"));

Result result = new Result();

result.RequestIdentifier = sendMms.sendMessage(addresses, this.spnum,

subject, priority, charging, receiptRequest);

result.Correlator=Correlator;

return result;

} catch (cn.com.chinatelecom.www.schema.ctcc.common.v2_1.ServiceException e) {

Result result = new Result();

result.MessageId = e.getMessageId();

result.Text = e.getText();

result.Variables = e.getVariables();

return result;

Page 79: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

79

} catch (cn.com.chinatelecom.www.schema.ctcc.common.v2_1.PolicyException e) {

Result result = new Result();

result.MessageId = e.getMessageId();

result.Text = e.getText();

result.Variables = e.getVariables();

return result;

} catch (SOAPException e) {

e.printStackTrace();

Result result = new Result();

result.MessageId = "-1";

result.Text = "ISAG SERVER ERROR(SOAPException)";

result.Variables = new String[1];

result.Variables[0] = "ISAG SERVER ERROR";

return result;

} catch (RemoteException e){

e.printStackTrace();

Result result = new Result();

result.MessageId = "-2";

result.Text = "ISAG SERVER ERROR(RemoteException)";

result.Variables = new String[1];

result.Variables[0] = "ISAG SERVER ERROR";

return result;

} catch (ServiceException e) {

e.printStackTrace();

Result result = new Result();

result.MessageId = "-3";

result.Text = "ISAG SERVER ERROR(ServiceException)";

result.Variables = new String[1];

result.Variables[0] = "ServiceException";

return result;

} catch (Exception e) {

e.printStackTrace();

Result result = new Result();

result.MessageId = "-1";

result.Text = "Unkonw error";

result.Variables = new String[1];

result.Variables[0] = "Unkonw error";

return result;

}

}

Page 80: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

80

}

说明:

构造函数:public MMS(String Url,String spid,String token,String spnum)

其中:Url为 ISAG彩信发送 URL

spid为企业代码

token 为 ISAG接入密码

spnum 为短信接入号

发送彩信:public Result sendMMS(String productid, String serviceid, String num, String

subject,String mmspath)

其中:productid为在ismp申请的产品编号

Serviceid为在ismp申请的业务编号

num为用户号码

subject是彩信标题

mmspath为当前彩信目录,程序会遍历该目录,将目录下所有的文件作为彩信附件发送

这样的话我们就可以使用如下代码进行发送

String

SendMessageUrl="http://10.10.10.10:8080/isag/North/MMS/SendMessage";

String spid="12188888";

String token="password";

String senderAddress="10620068";

String productid="112000000000000004136";

String serviceid="212000000000000002500";

String subject="彩信测试";

String num="1890571888888";

String mmspath="c:\\caixing\\res\\1";

MMS mms = new MMS(SendMessageUrl, spid, token, senderAddress);

Result result = mms.sendMMS(productid, serviceid, num, subject,

mmspath);

if (result.RequestIdentifier != null) {

System.out.println("彩信发送成功!" + "\r\nRequestIdentifier:"

Page 81: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

81

+ result.RequestIdentifier + "\r\n<BR/>Correlator:"

+ result.Correlator);

} else {

System.out.println("彩信发送失败!\r\n<BR/>" + "MessageId:"

+ result.MessageId + "\r\n<BR/>Text:\r\n<BR/>"

+ result.Text);

}

4.5 彩信上行

彩 信 上 行 的 接 口 SP 端 需 要 实 现 Web Service 的 服 务 器 端 代 码 。 选 中

ctcc_mm_notification_service_2_2.wsdl文件,按右键,选择Web Service->Generate Java bean skeleton.

出现窗口以后,点击 Finish即可。

Page 82: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

82

生成以后 Java Resources:src会生成如下文件:

为了获取彩信上行的 LinkID 和其他数据我们还需要引入一个类:cn.com.chinatelecom.ws.

NotifySOAPHeader以处理彩信上行的 SoapHeader,代码如下:

NotifySOAPHeader.java

Page 83: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

83

package cn.com.chinatelecom.ws;

import java.util.Iterator;

import javax.xml.soap.SOAPElement;

import org.apache.axis.AxisFault;

import org.apache.axis.message.SOAPEnvelope;

import org.apache.axis.message.SOAPHeaderElement;

public class NotifySOAPHeader {

private String spRevId = "";

private String spRevpassword = "";

private String spId = "";

private String SAN = "";

private String linkId = "";

public String getSpRevId() {

return spRevId;

}

public NotifySOAPHeader(SOAPEnvelope envelope) {

try {

SOAPHeaderElement soapHeaderElement = envelope.getHeaderByName(

"http://www.chinatelecom.com.cn/schema/ctcc/common/v2_1",

"NotifySOAPHeader");

Iterator iterator = soapHeaderElement.getChildElements();

while (iterator.hasNext()) {

SOAPElement element = (SOAPElement) iterator.next();

String elementName = element.getElementName().getLocalName();

if (elementName.equals("spRevId")) {

this.spRevId = element.getValue();

} else if (elementName.equals("spRevpassword")) {

this.spRevpassword = element.getValue();

} else if (elementName.equals("spId")) {

this.spId = element.getValue();

} else if (elementName.equals("SAN")) {

this.SAN = element.getValue();

} else if (elementName.equals("linkId")) {

this.linkId = element.getValue();

}

}

} catch (AxisFault e) {

Page 84: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

84

// TODO Auto-generated catch block

e.printStackTrace();

}

}

public void setSpRevId(String spRevId) {

this.spRevId = spRevId;

}

public String getSpRevpassword() {

return spRevpassword;

}

public void setSpRevpassword(String spRevpassword) {

this.spRevpassword = spRevpassword;

}

public String getSpId() {

return spId;

}

public void setSpId(String spId) {

this.spId = spId;

}

public String getSAN() {

return SAN;

}

public void setSAN(String sAN) {

SAN = sAN;

}

public String getLinkId() {

return linkId;

}

public void setLinkId(String linkId) {

this.linkId = linkId;

}

}

Page 85: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

85

其中MessageNotificationBindingImpl.java就是我们需要的彩信接收程序。我们需要完成这个框架下的

代码:

MessageNotificationBindingImpl.java

/**

* MessageNotificationBindingImpl.java

*

* This file was auto-generated from WSDL

* by the Apache Axis 1.4 Apr 22, 2006 (06:55:48 PDT) WSDL2Java emitter.

*/

package cn.com.chinatelecom.www.wsdl.ctcc.multimedia_messaging.notification.v2_2.service;

import java.io.File;

import java.io.FileOutputStream;

import java.io.FileWriter;

import java.io.IOException;

import java.text.SimpleDateFormat;

import java.util.Collection;

import java.util.Date;

import java.util.Iterator;

import javax.activation.DataHandler;

import javax.xml.soap.SOAPException;

import org.apache.axis.MessageContext;

import org.apache.axis.attachments.AttachmentPart;

import cn.com.chinatelecom.ws.NotifySOAPHeader;

public class MessageNotificationBindingImpl

implements

cn.com.chinatelecom.www.wsdl.ctcc.multimedia_messaging.notification.v2_2._interface.MessageNotification

{

//彩信保存目录

private String uploadFileDir = "c:\\caixing\\";

public void notifyMessageReception(

java.lang.String registrationIdentifier,

cn.com.chinatelecom.www.schema.ctcc.multimedia_messaging.v2_2.MessageReference message)

throws java.rmi.RemoteException {

Page 86: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

86

try {

//获取SoapHeader

NotifySOAPHeader notifySOAPHeader = new NotifySOAPHeader(

MessageContext.getCurrentContext().getRequestMessage()

.getSOAPEnvelope());

System.out.println("LinkId:"+notifySOAPHeader.getLinkId());

System.out.println("SAN:"+notifySOAPHeader.getSAN());

System.out.println("SpId:"+notifySOAPHeader.getSpId());

System.out.println("RevId:"+notifySOAPHeader.getSpRevId());

System.out.println("SpRevpassword:"+notifySOAPHeader.getSpRevpassword());

FileWriter fw = new FileWriter(uploadFileDir + "mms.log", true);

fw.write("registrationIdentifier:" + registrationIdentifier

+ "\r\n");

fw.write("message:" + message.getMessage() + "\r\n");

fw.write("messageIdentifier:" + message.getMessageIdentifier()

+ "\r\n");

fw.write("subject:" + message.getSubject() + "\r\n");

fw.write("ServiceActivationNumber:"

+ message.getMessageServiceActivationNumber() + "\r\n");

fw.write("SenderAddress:" + message.getSenderAddress() + "\r\n");

fw.write("LinkId:" + notifySOAPHeader.getLinkId() + "\r\n");

fw.close();

MessageContext messageContext = MessageContext.getCurrentContext();

Collection attachments = messageContext.getRequestMessage()

.getAttachmentsImpl().getAttachments();

Iterator it = attachments.iterator();

while (it.hasNext()) {

AttachmentPart attachmentPart = (AttachmentPart) it.next();

// 创建目录

String usernum = message.getSenderAddress().getPath();

String uploadpath = uploadFileDir + usernum + "-"

+ dateString();

File dir = new File(uploadpath);

if (!dir.exists())

dir.mkdir();

Page 87: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

87

String file = uploadpath + "//"

+ attachmentPart.getContentLocation();

try {

DataHandler dataHandler = attachmentPart.getDataHandler();

FileOutputStream fileOutputStream = new FileOutputStream(

file);

dataHandler.writeTo(fileOutputStream);

fileOutputStream.flush();

fileOutputStream.close();

} catch (SOAPException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

} catch (IOException e) {

// TODO Auto-generated catch block

System.out.println(e.getMessage());

e.printStackTrace();

}

}

public void notifyMessageDeliveryReceipt(

java.lang.String correlator,

cn.com.chinatelecom.www.schema.ctcc.multimedia_messaging.v2_2.DeliveryInformation

deliveryStatus)

throws java.rmi.RemoteException {

try {

FileWriter fw = new FileWriter(uploadFileDir + "deliver.log", true);

fw.append("---------------------" + dateString()

+ "---------------------\r\n");

fw.append("correlator:" + correlator + "\r\n");

fw.append("deliveryStatus:" + deliveryStatus.getDeliveryStatus()

+ "\r\n");

fw.append("Address:" + deliveryStatus.getAddress() + "\r\n");

fw.close();

} catch (IOException e) {

Page 88: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

88

// TODO Auto-generated catch block

e.printStackTrace();

}

}

private static String dateString() {

SimpleDateFormat sdf = new SimpleDateFormat("MMddHHmmss");

return sdf.format(new Date());

}

}

蓝底部分为增加部分,其中方法:notifyMessageReception 是处理彩信上行,程序将用户的彩

信上行记录在 mms.log 目录下,然后将彩信的附件保存在“用户目录-时间”的目录下。方法:

notifyMessageDeliveryReceipt是处理彩信下发的回执,接收到回执以后保存在 deliver.log文件中。

程序相对比较简单就不再详述。

程序开发完成后,Export成WAR文件,部署到 Tomcat的 webapps目录下。

特别注意,在某些 ISAG接入的时候,你会发现一旦有彩信上行,你会发现 mms.log根本没有记

录,而检测WebService会发现返回:

<?xml version="1.0" encoding="UTF-8"?>

<soapenv:Envelope

xmlns:soapenv=http://schemas.xmlsoap.org/soap/envelope/

xmlns:xsd="http://www.w3.org/2001/XMLSchema"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

Page 89: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

89

<soapenv:Body>

<soapenv:Fault>

<faultcode>soapenv:Server.userException</faultcode>

<faultstring>

org.xml.sax.SAXParseException: Content is not allowed in prolog.

</faultstring>

<detail>

<ns1:hostname xmlns:ns1="http://xml.apache.org/axis/">dowell-PC</ns1:hostname>

</detail>

</soapenv:Fault></soapenv:Body></soapenv:Envelope>

“org.xml.sax.SAXParseException: Content is not allowed in prolog. ”这个错误一般是由于Mime类型不

对引起的,作为带附件的 Web Service 请求,可能的 mime 类型是:multipart/mixed,或者

multipart/related。对于这两个 MIME类型有啥不同,可以参考相应的规范。由于 ISAG上行部分省份

的系统为 multipart/mixed,导致 AIXS 的程序处理异常直接报错。我们需要将请求中的 Content-Type

由 multipart/mixed转换为 multipart/multipart/related以便 AIXS能够正常处理。

幸好 Servlet 有很好的 Filter 机制对请求进行处理。我们需要完成两个程序。扩展

HttpServletRequestWrapper,对 Content-Type进行特殊处理,另外还需实现一个 Filter。

程序如下:

IsagHttpServletRequestWrapper.java

package cn.com.chinatelecom.ws;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletRequestWrapper;

import javax.servlet.Filter;

import javax.servlet.FilterChain;

import javax.servlet.FilterConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

public class IsagHttpServletRequestWrapper extends HttpServletRequestWrapper {

public IsagHttpServletRequestWrapper(HttpServletRequest request) {

super(request);

// TODO Auto-generated constructor stub

}

public String getHeader(String name) {

if ("SOAPAction".equalsIgnoreCase(name)) {

if (null == super.getHeader(name)) {

Page 90: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

90

return "";

}

}

if ("Content-Type".equalsIgnoreCase(name)) {

if (super.getHeader(name).startsWith("multipart/mixed")) {

return super.getHeader(name).replace("multipart/mixed",

"multipart/related");

}

}

return super.getHeader(name);

}

}

IsagFilter.java

package cn.com.chinatelecom.ws;

import java.io.IOException;

import javax.servlet.Filter;

import javax.servlet.FilterChain;

import javax.servlet.FilterConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletRequest;

public class IsagFilter implements Filter {

@Override

public void destroy() {

// TODO Auto-generated method stub

}

@Override

public void doFilter(ServletRequest request, ServletResponse response,

FilterChain chain) throws IOException, ServletException {

System.out.println("do filter...................");

chain.doFilter(new IsagHttpServletRequestWrapper((HttpServletRequest)

request), response);

}

Page 91: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

91

@Override

public void init(FilterConfig arg0) throws ServletException {

// TODO Auto-generated method stub

}

}

当然开发的过程中可能报 javax.servlet.*下的文件不存在,这个只需要到 Tomcat的安装目录下

common目录 lib目录下 servlet-api.jar的文件设置到项目的外部引用 jar下即可。

选中项目以后按右键,选择 Properties

出现的窗口以后,在左侧点:Java Build Path,左边选择 Libraries,然后点按钮 Add External Jars,找到

servlet-api.jar文件,增加即可。

做好这些我们还需要将这个 Filter 增加到项目 WebContent/Web-INF下的 web.xml文件里面增加 Filter

的配置。

Page 92: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

92

<?xml version="1.0" encoding="UTF-8"?>

<web-app id="WebApp_ID" version="2.4"

xmlns="http://java.sun.com/xml/ns/j2ee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee

http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

<display-name>mms</display-name>

<filter>

<filter-name>IsagFilter</filter-name>

<filter-class>cn.com.chinatelecom.ws.IsagFilter</filter-class>

</filter>

<filter-mapping>

<filter-name>IsagFilter</filter-name>

<Url-pattern>/services/*</Url-pattern>

</filter-mapping>

<servlet>

<display-name>Apache-Axis Servlet</display-name>

<servlet-name>AxisServlet</servlet-name>

<servlet-class>org.apache.axis.transport.http.AxisServlet</servlet-class>

</servlet>

<servlet>

<display-name>Axis Admin Servlet</display-name>

<servlet-name>AdminServlet</servlet-name>

<servlet-class>org.apache.axis.transport.http.AdminServlet</servlet-class>

<load-on-startup>100</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>AxisServlet</servlet-name>

<Url-pattern>/servlet/AxisServlet</Url-pattern>

</servlet-mapping>

<servlet-mapping>

<servlet-name>AxisServlet</servlet-name>

<Url-pattern>*.jws</Url-pattern>

</servlet-mapping>

<servlet-mapping>

<servlet-name>AxisServlet</servlet-name>

Page 93: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

93

<Url-pattern>/services/*</Url-pattern>

</servlet-mapping>

<servlet-mapping>

<servlet-name>AdminServlet</servlet-name>

<Url-pattern>/servlet/AdminServlet</Url-pattern>

</servlet-mapping>

<welcome-file-list>

<welcome-file>index.html</welcome-file>

<welcome-file>index.htm</welcome-file>

<welcome-file>index.jsp</welcome-file>

<welcome-file>default.html</welcome-file>

<welcome-file>default.htm</welcome-file>

<welcome-file>default.jsp</welcome-file>

</welcome-file-list>

</web-app>

绿底部分为增加部分,重新部署应该就可以正常使用了,当然有部分省份的 ISAG不存在此问题,无

需这样处理。

Page 94: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

94

5.0 ISAG接口调试

5.1 TcpMon使用

AXIS 其 实 带 了 一 个 非 常 不 错 的 调 试 器 叫 TcpMon, 具 体 的 类 名 是 :

org.apache.axis.utils.tcpmon。当然由于 axis自带的 TcpMon编码有时候存在问题,导致现

实为乱码,你可以用 https://tcpmon.dev.java.net/作为替代。

你可以通过两种方式启动 TcpMon,一种是命令行方式:

java –classpath aixs.jar的路径 org.apache.axis.utils.tcpmon

还有一种方式是通过 Eclipse启动 TcpMon, 在 Project Explorer 找到一个我们先前的开

发 项 目 。 选 择 Java Resources:src –> Libraries ->Web App Libraries->axis.jar->

org.apache.axis.utils ->tcpmon.class,然后按右键选择:Run As -> Java Application:

TCPMON严格意义上说是一个 HTTP的代理,负责将 HTTP请求转发到另外的服务

器上,由于数据流经过 TCPMON,TCPMON 能够显示交互的数据,自然成了非常好用的

WebService 调试工具。其中 Axis1.4 提供了一个类:org.apache.axis.utils.tcpmon,具体调

用方法: java –classpath aixs.jar的路径 org.apache.axis.utils.tcpmon。由于 Axis自带的

TCPMON 编码上存在问题,可能部分机器上使用会出现乱码(我自己的 Vista 机器上就

乱码),也可以使用 https://tcpmon.dev.java.net/上的软件。两个 tcpmon的软件界面和参数

设置基本一致。

Page 95: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

95

启动 TCPMON 以后有三个参数需要设置。分别为:Local Port,表示本地监听的端

口号;Server Name:请求转发的服务器地址,这里特别说明的是只要填服务器的域名或

者 IP即可,不要填写具体的 URL地址;Server Port,服务器监听的端口号。

我们假设我们需要调试 ISAG 的彩信接口,ISAG 服务器的 WebService 地址为:

"http://10.10.10.10:8080/isag/North/MMS/SendMessage,而在 ISMP上申请的本地接收彩信

和回执的 webservice地址是:"http://20.20.20.20:8080//mms/services/MessageNotification。

一、跟踪彩信下行(即:给用户发送彩信)

在彩信下行的流程中,ISAG是作为WebService的服务器端,SP侧的程序是作为客

户端。我们需要做的是让程序先请求 TCPMON监听的端口,然后让 TCPMON将请求转

发给 ISAG.我们设置 TCPMON的三个参数分别为:Local Port:5080(本地监听端口),

Server Name:10.10.10.10(ISAG服务器 ip地址),Server Port:8080(ISAG服务器端口),

然 后 更 改 程 序 中 的 ISAG 彩 信 发 送 地 址 为 :

http://127.0.0.1:5080/isag/North/MMS/SendMessage,这样我们就可以看到如图所示的结果

了。

配置:

Page 96: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

96

跟踪:

二、跟踪彩信上行(即:用户发送彩信到 sp接入号)

在彩信上行的流程中,ISAG是作为WebService的客户端,SP侧的程序是作为服务

器端。我们需要调整Web服务器的端口号,如果是 Tomcat,我们需要找到 Tomcat安装目

Page 97: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

97

录下的 conf目录,修改 Server.xml中的 Connector选项的 port 为 5080(缺省为 8080,也可

以是其他端口号),然后重启 Tomcat。同样我们还需要设置 TCPMON的三个参数,分别

为:Local Port:8080(本地监听端口),Server Name:127.0.0.0(本地 Tomcat服务器),

Server Port:5080(本地 Tomcat端口)。

配置:

跟踪:

当然需要说明的是 ISAG 在发送短信、彩信或者 WapPush的时候,由于需要使用在

Page 98: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

98

ISMP申报的 ip地址,所以以上调试需要在服务器端完成。

5.2 Web Services Explorer

另外只是简单的检查下自己写的Web Service是否可用,可以使用 EclipseEE自带的

Web Services Explorer,在菜单 Project里面选择 Run->Luanch Web Services Explorer。

选中WSDL按钮:

点击左边WSDL Main,在右边的WSDL URL,输入你要测试地址的WSDL就可以进行

测试了

Page 99: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

99

Page 100: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

100

附录一: SendSms短信发送接口报文数据:

1.1请求(SP发起):

<?xml version="1.0" encoding="UTF-8"?>

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"

xmlns:xsd="http://www.w3.org/2001/XMLSchema"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

<soapenv:Header>

<ns1:RequestSOAPHeader

soapenv:actor="http://schemas.xmlsoap.org/soap/actor/next"

soapenv:mustUnderstand="0"

xmlns:ns1="http://www.chinatelecom.com.cn/schema/ctcc/common/v2_1">

<ns1:spId>spId</ns1:spId>

<ns1:timeStamp>0828122129</ns1:timeStamp>

<ns1:spPassword>密钥</ns1:spPassword>

<ns1:productId>112000000000000004079</ns1:productId>

<ns1:OA>tel:+8615305711873</ns1:OA>

<ns1:FA>tel:+8615305711873</ns1:FA>

<ns1:multicastMessaging>false</ns1:multicastMessaging>

</ns1:RequestSOAPHeader>

</soapenv:Header>

<soapenv:Body>

<sendSms

xmlns="http://www.chinatelecom.com.cn/schema/ctcc/sms/send/v2_1/local">

<addresses>tel:+8615305711873</addresses>

<senderName>10620068</senderName>

<charging>

<description xmlns="">Free</description>

<currency xmlns="">0</currency>

<amount xmlns="">1</amount>

<code xmlns="">212100000000000002485</code>

</charging>

<message>短信内容</message>

<receiptRequest>

<endpoint

xmlns="">http://202.101.166.196:8080/sms/services/SmsNotification</endpoint>

<interfaceName

xmlns="">notifySmsDeliveryReception</interfaceName>

<correlator xmlns="">0828122129</correlator>

</receiptRequest>

Page 101: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

101

</sendSms>

</soapenv:Body>

</soapenv:Envelope>

注:红色部分替换成实际的参数

2.2应答(ISAG应答)

<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"

xmlns:xsd="http://www.w3.org/2001/XMLSchema"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

<env:Body>

<sendSmsResponse

xmlns="http://www.chinatelecom.com.cn/schema/ctcc/sms/send/v2_1/local">

<result>19120004a975e2d01596</result>

</sendSmsResponse>

</env:Body>

</env:Envelope>

Page 102: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

102

附录二: WAP PUSH发送报文数据:

1.1请求(SP发起):

<?xml version="1.0" encoding="UTF-8"?>

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"

xmlns:xsd="http://www.w3.org/2001/XMLSchema"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

<soapenv:Header>

<ns1:RequestSOAPHeader

soapenv:actor="http://schemas.xmlsoap.org/soap/actor/next"

soapenv:mustUnderstand="0"

xmlns:ns1="http://www.chinatelecom.com.cn/schema/ctcc/common/v2_1">

<ns1:spId>spId</ns1:spId>

<ns1:timeStamp>0828123841</ns1:timeStamp>

<ns1:spPassword>密钥</ns1:spPassword>

<ns1:productId>112000000000000004112</ns1:productId>

<ns1:OA>tel:+8615305711873</ns1:OA>

</ns1:RequestSOAPHeader>

</soapenv:Header>

<soapenv:Body>

<sendMessage

xmlns="http://www.chinatelecom.com.cn/schema/ctcc/wap_push/send/v1_0/local">

<addresses>tel:+8615305711873</addresses>

<targetURL>http://wap.vnet.mobi</targetURL>

<senderAddress>10620068</senderAddress>

<subject>WAP PUSH 标题</subject>

<priority>Normal</priority>

<charging>

<description xmlns="">gm</description>

<currency xmlns="">0</currency>

<amount xmlns="">1</amount>

<code xmlns="">212000000000000002488</code>

</charging>

<receiptRequest>

<endpoint xmlns="">http://202.101.166.196:8080/isag/services/

MessageNotification</endpoint>

<interfaceName xmlns="">notifyMessageDeliveryReceipt

</interfaceName>

<correlator xmlns="">100155</correlator>

</receiptRequest>

Page 103: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

103

</sendMessage>

</soapenv:Body>

</soapenv:Envelope>

注:红色部分替换成实际数据

2.2应答(ISAG应答)

<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"

xmlns:xsd="http://www.w3.org/2001/XMLSchema"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

<env:Body>

<sendMessageResponse

xmlns="http://www.chinatelecom.com.cn/schema/ctcc/wap_push/send/v1_0/local">

<requestIdentifier>19120004a976152061c2</requestIdentifier>

</sendMessageResponse>

</env:Body>

</env:Envelope>

Page 104: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

104

附录三: SendMessage彩信发送接口报文数据:

1.1请求(SP发起):

------=_Part_0_7958910.1251702844109

Content-Type: text/xml; charset=UTF-8

Content-Transfer-Encoding: binary

Content-Id: <E9224266F378E60C85E832977A6D50CA>

<?xml version="1.0" encoding="UTF-8"?>

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"

xmlns:xsd="http://www.w3.org/2001/XMLSchema"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

<soapenv:Header>

<ns1:RequestSOAPHeader soapenv:actor="http://schemas.xmlsoap.org/soap/actor/next"

soapenv:mustUnderstand="0"

xmlns:ns1="http://www.chinatelecom.com.cn/schema/ctcc/common/v2_1">

<ns1:spId>spid</ns1:spId>

<ns1:timeStamp>0831151402</ns1:timeStamp>

<ns1:spPassword>330D86FFF49332FD8FF454E59164432A</ns1:spPassword>

<ns1:productId>112000000000000004136</ns1:productId>

<ns1:OA>tel:+8615305711873</ns1:OA>

<ns1:multicastMessaging>false</ns1:multicastMessaging>

</ns1:RequestSOAPHeader>

</soapenv:Header>

<soapenv:Body>

<sendMessage

xmlns="http://www.chinatelecom.com.cn/schema/ctcc/multimedia_messaging/send/v2_2/local">

<addresses>tel:+8615305711873</addresses>

<senderAddress>10620068</senderAddress>

<subject>彩信标题!</subject>

<priority>Normal</priority>

<charging>

<description xmlns="">Test</description>

<currency xmlns="">0</currency>

<amount xmlns="">1</amount>

<code xmlns="">212000000000000002500</code>

</charging>

<receiptRequest>

<endpoint

xmlns="">http://202.101.166.196:8080/mms/services/MessageNotification

Page 105: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

105

</endpoint>

<interfaceName xmlns="">notifyMessageDeliveryReception

</interfaceName>

<correlator xmlns="">100155</correlator>

</receiptRequest>

</sendMessage>

</soapenv:Body>

</soapenv:Envelope>

Content-Type: application/smil

Content-Transfer-Encoding: binary

Content-Id: <test.smil>

Content-Location: test.smil

<smil>

<head>

</head>

<body>

<seq>

<img src="1.jpg" dur="5s"/>

<img src="2.jpg" dur="6s"/>

<img src="3.jpg" dur="100s"/>

</seq>

</body>

</smil>

------=_Part_0_7958910.1251702844109

Content-Type: image/jpeg

Content-Transfer-Encoding: binary

Content-Id: <1.jpg>

Content-Location: 1.jpg

1.jpg数据

------=_Part_0_7958910.1251702844109

Content-Type: image/jpeg

Page 106: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

106

Content-Transfer-Encoding: binary

Content-Id: <2.jpg>

Content-Location: 2.jpg

2.jpg数据

------=_Part_0_7958910.1251702844109--

1.2 ISAG响应

<?xml version="1.0" encoding="UTF-8"?>

<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"

xmlns:xsd="http://www.w3.org/2001/XMLSchema"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

<env:Body>

<sendMessageResponse

xmlns="http://www.chinatelecom.com.cn/schema/ctcc/multimedia_messaging/send/v2_2/local">

<result>19120004a9b784a03505</result>

</sendMessageResponse>

</env:Body>

</env:Envelope>

Page 107: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

107

二. 与 ISMP接口实现

1.ISMP简介

ISMP 中文全称为:综合业务管理平台 。是英文:Integrated Services Management

Platform的首字母缩写。

在移动增值业务发展的前期,业务都以短信为主,所以作为业务的管理平台都只有

短信业务管理平台(SPMS)。随着彩信,WAP等业务的引入电信运营商需要有一个统一

的增值业务管理平台,中移动引入了MISC平台,而中电信相应的平台叫 ISMP。

SP需要在 ISMP上进行 SP接入资格的申请;业务能力的申请;业务和产品的申请。

相应的申请完成审批以后,ISMP会根据需要将业务数据同步给 ISAG和相关的引擎。

由于业务的订购关系是由 ISMP保存,所以在程序接口上还需要与 ISMP完成订购关

系同步的Web Service接口,本章节主要围绕这个接口开发进行讲解。

1.1与 ISMP相关的术语

在进行接口开发前,我们有比较就 ISMP的一些术语进行说明,我们先来看下在 ISMP

上的相关业务的一个层次结构:对于 1个 SP来说,他在申请完成接入资格以后,需要先

申请业务能力,比如短信能力或彩信能力;申请了短信能力意味着他可以进行短信相关

的业务开发,下一步就是申请一个短信业务;对于某个短信业务进行不同的批价以后产

生相应的产品。

特别说明的正如上面所述是在 ISMP的规范下一个移动增值业务是分成两级结构,即

业务和产品。业务指的是某个产品的大类,业务并不关联价格; 在业务下进行价格策略的

绑定并设置指令规则进变成了产品。打个比方,我们有个大盘信息业务,这个业务有两

种收费策略,一是 2 元包月,只在股票闭市后发送 1 条,另一种是,在中午和股票闭市

的时候分别发两条,收费是 3元/月。在这样的情况下,我们申请一个大盘信息的业务,

并且在这个大盘信息下申请 2个产品,一个 2元/月,一个 3元/月。

整个增值业务体系结构如下图:

Page 108: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

108

具体名词说明:

SPID:

SP的编号,长度为 8位,用以区分不同的 SP,编号前面两位表示省份,第三位表

示接入类型,1表示 CDMA。

比如一个标号 12100135,表示浙江电信 CDMA的某一 SP.

业务能力:

能够进行某种业务接入资格,比如短信能力,彩信能力等。一个业务能力根据接

入的网元不同可以分成 ISAG接入,以及 SE 接入。ISAG接入第一章节有说明,

SE表示是通过引擎直接接入,也就是说如果是短信直接接入短信网关,如果是彩

信表示直接接入彩信中心。

业务:

某一产品的大类。在 ISMP 里面一个业务有一个独立的业务编号,他为长度为 21

为,以数字 2开头的编号,编号的后两位为省份编号,12表示浙江,35表示全国

比如:

Page 109: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

109

212000000000061090527表示浙江接入的某个业务编号。

235000000000000022122表示集团接入的某个业务编号。

产品:

业务下具体的某个产品,一个业务下可以有一个或者多个产品。挂在相同业务下

的产品是互斥的,比如上述提到的一个大盘信息的业务下有 3元包月和 2元包月,

当用户先订购 2元包月以后再订购 3元,ISMP会自动退订 2元的业务,并订购 3

元的业务,当月包月按原先产品的包月费用进行扣费,也就按 2 元收取,到次月

如果用户没有退订,就按新的包月费用收取,即 3元/月。

与业务一样每个产品都有 21位长的数字作为产品的识别,他是以数字 1开头,第

二和第三位表示省份。

比如:

112000000000034333222表示浙江接入的某个产品编号。

135000000000000022122表示集团接入的某个产品编号。

内容:

对于一些下载类的业务,需要将内容上传到 ISMP平台,如需要上传相应图片或者

软件的时候,申请一个内容。这个不常用。

SLA:

SLA是 Service Level Agreement的缩写,也就是服务品质协议。在 ISMP里面特

指一些业务能力和业务的事先约定,比如短信的下行速度等。一般申请业务以后

ISMP都会给 SP配置一个缺省的 SLA,当需要申请与缺省的约定有不同的要求可以

申请单独的 SLA。

其他业务申请相关的在这个章节不做详述,请参考当地电信提供的帮助文档。

2.业务流程

2.1点播业务流程

1)用户发送点播指令到相应接入号

Page 110: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

110

2)短信网关或 ISAG接收到相应指令以后送 ISMP鉴权,ISMP匹配为点播业务后生成

LinkID

3) 相应点播指令增加 LinkID后,通过 ISAG或者短信网关送给 SP

4) SP进行相关逻辑的处理后,匹配自身的产品和业务,增加相应的产品 ID(ISAG下

发的时候还需要带上业务 ID)下发给用户。

5)用户收到短信后,ISMP产生话单,并计费。

可以看出点播业务在整个过程中,ISMP并不与 SP的系统进行直接的交互,除了WAP

门户上进行点播以外,ISMP会通过服务使用通知接口通知 SP , 普通的点播业务并不

需要与 ISMP实现接口。

2.2包月业务流程

1)用户发送订购指令到相应接入号

2)短信网关或 ISAG接收到相应指令以后送 ISMP鉴权,ISMP匹配为包月业务,下发

二次确认消息,提示用户相应的服务名称,服务费用等。

3) 用户回复二次确认,ISMP 收到二次确认以后通过订购关系通知的 WebService 接口

通知 SP。

4) SP 根据接口的进行进行相应处理,如果允许用户订购,回复 Result 为 0,产生订

购关系。

5) 订购关系建立后,ISMP会下发给用户订购成功语言,完成业务订购。

6) SP 提供服务,下发短信或者彩信,下发的时候需要带上产品 ID, 如果是 ISAG接

入下发的时候还需要带上业务 ID。

这里特别需要说明的是,如果 ISMP调用 SP提供的订购关系通知接口时候出现异常(比

如超时,或者 SP侧Web Service服务异常)或者 SP回复 Result非 0的时候,都无法建

立订购关系,同时用户测会收到一条 ISMP订购失败的提示语,比如:你订购某某公司

的业务失败,失败原因 SP处理失败。

Page 111: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

111

3. ISMP接口开发环境搭建

在开发前我们先看下接口的大致结构

服务名称 方法 作用 谁是服务器

IsmpSpEngineService

orderRelationUpdateNotify 订购关系同步 SP

serviceConsumeNotify 服务使用通知 SP

notifyManagementInfo 管理信息同步 SP

spWithdrawSubscription 反向退订接口 ISMP

可以看出与 ISMP的接口就一个服务名,该服务有 4个方法,其中:

orderRelationUpdateNotify 为订购关系同步的接口,当订购关系发生变化的时候

(包括:订购,取消以及替换等),ISMP使用该方法通知 SP。

serviceConsumeNotify为服务使用通知接口,当用户在WAP门户使用某一点播业

务,ISMP会通过该方法通知 SP。

notifyManagementInfo 为管理系统同步接口,当某一产品业务发生状态变更的时

候,ISMP 会通过该接口通知 SP,该方法如不实现并不影响业务的正常使用,但是这个

接口在业务审批阶段就会被调用,实现该方法可以很好的在业务上线前测试你的 Web

Services接口是否正常。

spWithdrawSubscription是后续增加的接口,该方法与另外三个方法最大的区别,

该方法是 ISMP作为 web service的服务器端,SP作为客户端。当 SP需要主动取消某个

业务的时候,通过该接口请求 ISMP进行订购关系注销,不同的省份对于反向取消的处理

可能有不同,有些可能需要用户进行二次确认后再取消,有些省份直接取消,目前浙江

电信是直接取消。由于该接口 ISMP是作为服务器端,开发该接口的时候需要联系当地电

信获取接口地址。

1)由于 SP侧需要作为Web Service的服务器端,所以同样我们需要新建一个 Dynamic Web

Project,如下图:

Page 112: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

112

2)我们这里起名叫 ismp,你也可以根据你的喜好设置不同的项目名称:

Page 113: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

113

3)点击 Finish按钮完成项目创建。

4) 创建一个WSDL目录

5) 选中 wsdl目录按右键选择 Import

Page 114: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

114

6) 选择 File System

7) 选中 IsmpSpEngineService.wsdl

Page 115: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

115

8) 选中 ISmpSpEngine.wsdl按右键选择Web Services->Generate Java bean skeleton

9) 由于 spWithdrawSubscription方法还需要 SP端作为客户端,所以我们还需要生成客户

端代码,所以在 Client部分滑动 Develop client, 并点击 Finish按钮,如下图:

Page 116: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

116

10)生成以后看到在 Project Explorer里面看到如下图的文件结构:

Page 117: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

117

其中:IsmpSpEngineSoapBindingImpl.java 是我们需要完善的服务器端代码文件,其中

IsmpSpEngineServiceLocator.java是我们可调用的客户端代码。

4. 订购关系同步 orderRelationUpdateNotify方法实现

开发前我们先看下 orderRelationUpdateNotify的参数:

输入参数(发起方 ISMP):

参数名 类型 说明 是否可能为空

streamingNo String 流水号,长度为 60 位,应答的时

候需要原样返回

不会为空

userID String 用户号码,浙江的 ISMP 平台用户

号码前会加 86

不会为空

userIDType Integer 用户类型,0表示 cdma手机;1表 不会为空

Page 118: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

118

示小灵通;2 表示固定电话;3 表

示伪码

目前来说这里固定为 0,因为小灵

通和固定电话并不接入 ISMP

productID String 产品编号,长度 21位 不会为空

oldProductID String 被替换的产品编号 当Optype不为 6的时候

为空

packageID String 套餐标识 普通业务不加入套餐,

所以该字段一般为空

oldPackageID Sting 被替换的套餐标识 当产品加入套餐而且发

生产品替换的时候才不

为空

OPType Integer 操作类型。0:订购,1:暂停;2:

暂停恢复 3:退订,4:退订该 SP

的所有产品和套餐 5:暂停该 SP

所有的产品和套餐 6:替换。

注:替换发生在用户已经订购相同

业务下的产品,用户再订购该业务

下的另外一个产品就发生替换。

不会为空

VerUserID String[] 智能短信会用到该字段,普通业务

不会有该字段。类似一号双机的业

务,这里会传送用户其他的号码。

一般为空

输出参数:

参数名 类型 说明 是否可以为空

streamingNo String 流水号,长度为 60位,

需要原样返回输入参数

的 streamingNo

不能

resultCode Integer 0:表示成功

其他参考《中国电信

CDMA业务网络接口协

议技术规范-SP 管理接

口协议规范》的附录 A

不能

对于输入的参数,有些字段可能为空,所以需要确认下该字段是否是 Null以后再进行

相应的操作,不让 Java会抛出 Null Pointer错误,导致Web Service出现异常,最终导致

订购关系无法产生。

上述章节我们已经生成了框架代码,我们只要把相应的代码在

IsmpSpEngineSoapBindingImpl.java的程序中实现即可。

Page 119: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

119

package com.chinatelecom.ismp.sp;

import java.io.FileWriter;

import java.io.IOException;

import java.text.SimpleDateFormat;

import java.util.Date;

import com.chinatelecom.ismp.sp.rsp.Response;

public class IsmpSpEngineSoapBindingImpl implements

com.chinatelecom.ismp.sp.IsmpSpEngine {

private SimpleDateFormat simpleDateFormat = new SimpleDateFormat(

"yyyy年MM月dd日 HH:mm:ss");

public com.chinatelecom.ismp.sp.rsp.Response orderRelationUpdateNotify(

com.chinatelecom.ismp.sp.req.OrderRelationUpdateNotifyReq

orderRelationUpdateNotifyReq)

throws java.rmi.RemoteException {

int oPType = orderRelationUpdateNotifyReq.getOPType();

String streamingNo = orderRelationUpdateNotifyReq.getStreamingNo();

String productID = orderRelationUpdateNotifyReq.getProductID();

String userID = orderRelationUpdateNotifyReq.getUserID();

int userIDType = orderRelationUpdateNotifyReq.getUserIDType();

String[] verUserID = orderRelationUpdateNotifyReq.getVerUserID();

String packageID = orderRelationUpdateNotifyReq.getPackageID();

String oldPackageID = orderRelationUpdateNotifyReq.getOldPackageID();

String oldProductID = orderRelationUpdateNotifyReq.getOldProductID();

try {

FileWriter fileWriter = new FileWriter("c:\\ismp.log", true);

fileWriter.append(simpleDateFormat.format(new Date())

+ "收到订购关系同步\r\n");

// 0:订购,1:暂停;2:暂停恢复3:退订,4:退订该SP的所有产品和套餐 5:暂停

该SP所有的产品和套餐 6:替换。

switch (oPType) {

case 0:

fileWriter.append("操作类型:订购\r\n");

break;

case 1:

fileWriter.append("操作类型:暂停\r\n");

Page 120: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

120

break;

case 2:

fileWriter.append("操作类型:暂停恢复\r\n");

break;

case 3:

fileWriter.append("操作类型:退订\r\n");

case 4:

fileWriter.append("操作类型:退订该SP的所有产品和套餐\r\n");

case 5:

fileWriter.append("操作类型:暂停该SP所有的产品和套餐\r\n");

case 6:

fileWriter.append("操作类型:替换\r\n");

break;

}

fileWriter.append("流水号:" + streamingNo + "\r\n");

fileWriter.append("手机号码:" + userID + "\r\n");

fileWriter.append("号码类型:" + userIDType + "\r\n");

fileWriter.append("产品编号:" + productID + "\r\n");

if (packageID != null)

fileWriter.append("套餐编号:" + productID + "\r\n");

if (oPType == 6) {

fileWriter.append("老产品编号:" + oldProductID + "\r\n");

if (oldPackageID != null)

fileWriter.append("老套餐编号:" + oldPackageID + "\r\n");

}

if (verUserID != null) {

fileWriter.append("用户其他号码:");

for (int i = 0; i < verUserID.length; i++) {

fileWriter.append(verUserID[i] + ",");

}

fileWriter.append("\r\n");

}

fileWriter.append("\r\n");

fileWriter.close();

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

Response response = new Response();

Page 121: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

121

response.setResultCode(0); // 返回0表示成功

response.setStreamingNo(streamingNo);// StreamingNo原样返回

return response;

}

public com.chinatelecom.ismp.sp.rsp.Response spWithdrawSubscription(

com.chinatelecom.ismp.sp.req.SPWithdrawSubscriptionReq

spWithdrawSubscriptionReqPara)

throws java.rmi.RemoteException {

return null;

}

public com.chinatelecom.ismp.sp.rsp.Response serviceConsumeNotify(

com.chinatelecom.ismp.sp.req.ServiceConsumeNotifyReq

serviceConsumeNotifyReqPara)

throws java.rmi.RemoteException {

return null;

}

public com.chinatelecom.ismp.sp.rsp.NotifyManagementInfoRsp

notifyManagementInfo(

com.chinatelecom.ismp.sp.req.NotifyManagementInfoReq

notifyManagementInfoReq)

throws java.rmi.RemoteException {

return null;

}

}

注:蓝底部分为新增加的代码。

程序说明:

程序通过请求的参数 orderRelationUpdateNotifyReq这个 javabean获取具体的参数值,

读取以后写如 c:\ismp.log文件。程序完成以后还需要返回一个 Response,设置 Response

的 resultcode为 0表示订购成功,让后将请求的 streamingNo原样返回给接口。

Response response = new Response();

response.setResultCode(0); // 返回0表示成功

response.setStreamingNo(streamingNo);// StreamingNo原样返回

Page 122: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

122

这个例子代码比较简单,不再详细说明,实际的程序会更复杂,比如收到请求以后

将订购关系入库,并触发相应的程序。

5. 管理信息同步 notifyManagementInfo方法实现

管理信息同步是指你在 ISMP的业务和产品发生状态变更的时候就会触发,比如业务被

审批或者注销等。我们看下规范:

请求参数:

参数名 类型 说明 是否可能为空

streamingNo String 流水号,长度为 60位,

应答的时候需要原样返

不会为空

IDType Integer 0:产品编号 1:套餐编号

2:服务编号 3:服务能力

编号

不会为空

ID String 编号,根据 IDType 填

写,比如 IDType为 0的

时候,这里的 ID表示产

品编号

不会为空

status Integer 状态;0:正常 1:申请

2:暂停 3:预注销 4:

注销

不会为空

输出参数:

参数名 类型 说明 是否可以为空

streamingNo String 流水号,长度为 60位,

需要原样返回输入参数

的 streamingNo

不能

resultCode Integer 0:表示成功

其他参考《中国电信

CDMA业务网络接口协

议技术规范-SP 管理接

口协议规范》的附录 A

不能

Page 123: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

123

同样我们只要在 SpEngineSoapBindingImpl.java的程序中将 notifyManagementInfo 方法

完成相应的逻辑处理即可。

public com.chinatelecom.ismp.sp.rsp.NotifyManagementInfoRsp

notifyManagementInfo(

com.chinatelecom.ismp.sp.req.NotifyManagementInfoReq

notifyManagementInfoReq)

throws java.rmi.RemoteException {

String ID = notifyManagementInfoReq.getID();

int IDType = notifyManagementInfoReq.getIDType();

int status = notifyManagementInfoReq.getStatus();

String streamingNo = notifyManagementInfoReq.getStreamingNo();

try {

FileWriter fileWriter = new FileWriter("c://ismp.log", true);

// 0:产品编号 1:套餐编号 2:服务编号 3:服务能力编号

fileWriter.append(simpleDateFormat.format(new Date())

+ "收到管理信息同步\r\n");

switch (IDType) {

case 0:

fileWriter.append("产品编号:");

break;

case 1:

fileWriter.append("套餐编号:");

break;

case 2:

fileWriter.append("服务编号:");

break;

case 3:

fileWriter.append("服务能力编号:");

break;

}

fileWriter.append(ID);

fileWriter.append("当前状态变为");

// 0:正常 1:申请 2:暂停 3:预注销 4:注销

switch (status) {

case 0:

fileWriter.append("正常 ");

Page 124: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

124

break;

case 1:

fileWriter.append("申请 ");

break;

case 2:

fileWriter.append("暂停 ");

break;

case 3:

fileWriter.append("预注销 ");

break;

case 4:

fileWriter.append("注销 ");

break;

}

fileWriter.append("\r\n\r\n");

fileWriter.close();

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

NotifyManagementInfoRsp notifyManagementInfoRsp = new

NotifyManagementInfoRsp();

notifyManagementInfoRsp.setResultCode(0); // 返回0表示成功

notifyManagementInfoRsp.setStreamingNo(streamingNo);// StreamingNo原

样返回

return notifyManagementInfoRsp;

}

注:蓝色的为增加的代码

程序将收到的请求写到 C:\ismp.log文件上,程序比较简单,不再说明。

6. 服务使用通知接口 serviceConsumeNotify方法实现

这个方法相对于 1.4、1.5的两个方法不大常用,当一个点播业务申请成对门户用户开

放的时候,并且用户在WAP门户点播这个业务才会调用这个方法。

我们看下规范:

请求参数:

Page 125: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

125

参数名 类型 说明 是否可能为空

streamingNo String 流水号,长度为 60位,

应答的时候需要原样返

不会为空

userID String 用户号码,浙江的 ISMP

平台用户号码前会加 86

不会为空

userIDType Integer 用户类型,0表示 cdma

手机;1表示小灵通;2

表示固定电话;3 表示

伪码

目前来说这里固定为 0,

因为小灵通和固定电话

并不接入 ISMP

不会为空

productID String 产品编号 不会为空

linkID String Linkid,点播业务 mo 与

mt连接编号

不会为空

featureStr String 使用特征码 不会为空

输出参数:

参数名 类型 说明 是否可以为空

streamingNo String 流水号,长度为 60位,

需要原样返回输入参数

的 streamingNo

不能

resultCode Integer 0:表示成功

其他参考《中国电信

CDMA业务网络接口协

议技术规范-SP 管理接

口协议规范》的附录 A

不能

为 了 实 现 该 方 法 我 们 只 要 在 IsmpSpEngineSoapBindingImpl.java 的

serviceConsumeNotify方法里面增加相应的代码即可。

public com.chinatelecom.ismp.sp.rsp.Response serviceConsumeNotify(

com.chinatelecom.ismp.sp.req.ServiceConsumeNotifyReq

serviceConsumeNotifyReqPara) {

String featureStr = serviceConsumeNotifyReqPara.getFeatureStr();

String linkID = serviceConsumeNotifyReqPara.getLinkID();

String productID = serviceConsumeNotifyReqPara.getProductID();

String streamingNo = serviceConsumeNotifyReqPara.getStreamingNo();

String userID = serviceConsumeNotifyReqPara.getUserID();

int userIDType = serviceConsumeNotifyReqPara.getUserIDType();

try {

Page 126: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

126

FileWriter fileWriter = new FileWriter("c:\\ismp.log", true);

fileWriter.append(simpleDateFormat.format(new Date())

+ "收到服务使用通知\r\n");

fileWriter.append("序列号:" + streamingNo + "\r\n");

fileWriter.append("用户号码:" + userID + "\r\n");

fileWriter.append("用户号码类型:" + userIDType + "\r\n");

fileWriter.append("产品编号:" + productID + "\r\n");

fileWriter.append("linkID:" + linkID + "\r\n");

fileWriter.append("特征码:" + featureStr + "\r\n\r\n");

fileWriter.close();

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

//这里省略点播下行消息的处理,根据实际情况在这里增加

Response response = new Response();

response.setResultCode(0); // 返回0表示成功

response.setStreamingNo(streamingNo);// StreamingNo原样返回

return response;

}

程序说明:程序将收到的请求数据写到 c:/ismp.log,实际的程序中还应该给用户下行消息,

这段代码根据自己的实际情况增加。

7.反向取消接口 spWithdrawSubscription接口实现

spWithdrawSubscription与另外三个方法不一样,这个方法是 ISMP作为服务器端程序,

所以你开发的时候需要跟当地电信联系获取 ISMP的反向接口地址。

我们看下规范:

请求参数:

参数名 类型 说明 是否可能为空

streamingNo String 流水号,长度为 60位,

应答的时候需要原样返

不会为空

Page 127: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

127

SPID String SP编号,长度为 8位 不会为空

Spadmin String SP操作员帐号 不会为空

SpadminPwd String SP操作员密码 不会为空

userID String 用户号码 不会为空

userIDType Integer 0:手机号码 1:小灵通

2:固定电话

注:目前 ISMP 只能处

理 CDMA的业务,所以

这里肯定送 0

不会为空

IDType Integer 0:产品

1:套餐

不会为空

ID String 当 IDType为 0时这里表

示产品编号。

当 IDType为 1时这里表

示套餐编号

不会为空

输出参数:

参数名 类型 说明 是否可以为空

streamingNo String 流水号,长度为 60位,

需要原样返回输入参数

的 streamingNo

不能

resultCode Integer 0:表示成功

其他参考《中国电信

CDMA业务网络接口协

议技术规范-SP 管理接

口协议规范》的附录 A

不能

请求参数里面的 Spadmin, SpadminPwd是登录 ISMP SP门户的操作员和操作员密码,

为了确保这个帐号密码无误,开发程序前先登录一遍 SP门户。另外华为的 ISMP平台操

作员帐号有有效期,如果长时间未登陆该帐号就失效了,失效的时候报的 resultCode 为

99,当然用户已经退订也会是 99 错误,如果大批的请求都是 99 错误,那么你要检查下

你的操作员帐号是否过期了。

我们看下程序:

import java.net.MalformedURLException;

import java.net.URL;

import java.rmi.RemoteException;

import javax.xml.rpc.ServiceException;

import com.chinatelecom.ismp.sp.IsmpSpEngine;

Page 128: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

128

import com.chinatelecom.ismp.sp.IsmpSpEngineServiceLocator;

import com.chinatelecom.ismp.sp.req.SPWithdrawSubscriptionReq;

import com.chinatelecom.ismp.sp.rsp.Response;

public class SPWithdrawSubscriptionTest {

public static void main(String[] args) throws MalformedURLException,

ServiceException {

//初始化web service接口

IsmpSpEngineServiceLocator ismpSpEngineServiceLocator=new

IsmpSpEngineServiceLocator();

IsmpSpEngine ismpSpEngine=

ismpSpEngineServiceLocator.getIsmpSpEngine(new

URL("http://61.60.61.60:8080/services/IsmpSpEngine")); //ismp web service接口

地址

//设置参数

SPWithdrawSubscriptionReq spWithdrawSubscriptionReqPara=new

SPWithdrawSubscriptionReq();

spWithdrawSubscriptionReqPara.setID("112000000000000004079");

spWithdrawSubscriptionReqPara.setIDType(0);

spWithdrawSubscriptionReqPara.setSPAdmin("account");

spWithdrawSubscriptionReqPara.setSPAdminPwd("passwd");

spWithdrawSubscriptionReqPara.setSPID("12100135");

spWithdrawSubscriptionReqPara.setStreamingNo(

"000000000000000000000000000000000000000000000000000000002349");

spWithdrawSubscriptionReqPara.setUserID("15305711873");

spWithdrawSubscriptionReqPara.setUserIDType(0);

//发送退订请求

try {

Response response = ismpSpEngine.spWithdrawSubscription(

spWithdrawSubscriptionReqPara);

System.out.println("ResultCode:"+response.getResultCode());

System.out.println("StreamingNo:"+response.getStreamingNo());

} catch (RemoteException e) {

e.printStackTrace();

}

}

}

注:红色部分替换成实际的参数

Page 129: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

129

8. 程序部署

1)我们选中 ISMP这个工程,按右键选择 Export-> WAR file

2)设置下 Destination的路径,我们这里先保存在桌面

Page 130: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

130

3)我们将生成的 ISMP.war文件,拷贝到 tomcat的安装的 webapps目录下。

4)进入 tomcat的安装目录的 bin目录下,运行 startup.bat启动 Tomcat.

在 Tomcat启动以后会显示如下信息,就说明 ISMP.war已经部署

2009-9-14 9:09:36 org.apache.catalina.startup.HostConfig deployWAR

信息: Deploying web application archive ISMP.war

2009-9-14 9:09:37 org.apache.axis.utils.JavaUtils isAttachmentSupported

警告: Unable to find required classes (javax.activation.DataHandler and javax.ma

il.internet.MimeMultipart). Attachment support is disabled.

由于我们没有添加 activation.jar 和 mail.jar,部署的时候会有警告,因为与 ISMP 的接口

不会用到 axis的附件功能,该警告可以忽略。

5)测试下程序是否部署完成,访问下 http://ip地址:端口/ISMP/services, 当然我是部署在

本机上可以用 http://127.0.0.1:8080/ISMP/services,打开浏览器输入地址,如果看到如下界

面说明程序已经部署完成。

Page 131: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

131

9. 接口测试

提交地址给 ISMP前我们可以先自行测试一下,方法很简单,我们使用 EclipseEE自带的Web Services

Explorer, 选择菜单:Run -> Launch the Web Services Explorer.

出现如下界面:

缺省是 UDDI的模式我们需要切换成WSDL模式,

Page 132: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

132

点击右上的倒数第二个按钮

点击WSDL Main,在WSDL URL填入wsdl地址:http://127.0.0.1:8080/ISMP/services/IsmpSpEngine?wsdl

点击 Go按钮,出现如下菜单,我们可以分别测试这 3个方法

1) 测试订购关系同步接口:orderRelationUpdateNotify

点击左边的 orderRelationUpdateNotify,然后在右边输入参数:

Page 133: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

133

如上图:

在 OPType填入 0表示订购,

productID填入一个产品编号:112000000000061090527,该字段在 wsdl定义为可选字段,所以需要点

击边上的 Add增加。

在 streamingNo填入一个 60位长的序号:

000000000000000000000000000000000000000000000000000000000194

在 userID填入用户号码,前面带 86

在 userIDType填入 0,表示 CDMA用户

其他字段在进行产品开通的时候都为空,所以这里不填。

点击最下面的 Go按钮,在右下的 status里面会看到如下返回,说明接口能够正常使用。

Page 134: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

134

如果出现其他异常,可以点击 status左边的 Source看下原始数据。

另外我们例子中我们将请求写入到 C:\ismp.log的,我们打开下这个文件会看到如下数据:

2009年 09月 14日 09:29:36收到订购关系同步

操作类型:订购

流水号:000000000000000000000000000000000000000000000000000000000194

手机号码:8615305711873

号码类型:0

产品编号:112000000000061090527

这说明这个接口已经正常。

另外:serviceConsumeNotify,notifyManagementInfo这两个接口测试方法类似,这里不再说明。

spWithdrawSubscription为 SP端作为客户端,所以不是直接在Web Service Explore测试,我们可以选

中 SPWithdrawSubscriptionTest.java文件按右键选择 Run As -> Java Application 来运行。

如果返回:

ResultCode:0

StreamingNo:000000000000000000000000000000000000000000000000000000002349

就说明程序已经能够正常了,如果返回 99可能是当前取消的订购关系不存在。

当然为了程序能够比较方便的调用,你可以将 SPWithdrawSubscriptionTest修改成方便调用的一个类,

然后将它输出成一个 jar,在其他程序需要调用这个接口的时候引用。

Page 135: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

135

三.WAP业务开发

1 WAP业务简介

传统意义上的WAP业务是指WAP Pull业务,Pull与 Push相对,一拉一推。本章节并不讨

论Wap Push的接口,该接口请参考第一章节的 ISAG接口中的Wap Push相关文档。

在 ISMP的架构中,ISAG独立成接入网关,而具体的业务实现分别由引擎完成。相对于 ISAG,

短信的接入引擎为短信网关;彩信的接入引擎为彩信中心;SP 在申请业务能力的时候,理论上

可以选择业务能力类型(短信、彩信以及 LBS等),而接入的设备可以是综合接入网关 ISAG 或

者引擎(SE),接入何种接入设备只是接口协议不同,具体的业务逻辑和业务实现是完全相同的。

而WAP业务却不是这样,如果你选择业务类型为WAP业务,接入设备为 ISAG,那么意味着你

这个业务是WAP Push业务,因为WAP Pull业务无法通过 ISAG接入来实现;而如果你选择的业

务类型为WAP业务,接入设备为引擎(SE)的时候,往往意味着你的业务是WAP Pull业务,也

即传统意义上的WAP业务,当然WAP加 SE的组合理论上也可以接入 Push业务,不过多数省份

Push的引擎 PPG并不提供公网的接入方式。

那什么是WAP Pull业务呢。我们看下Wap Pull的使用场景,当用户拨 CTWAP上网访问你

的WAP网站,而你的WAP网站需要用户付费才能使用的时候,你需要考虑接入WAP业务。而

你的网站本身并不对直接使用的用户进行收费,比如通过广告方式赚取收益,那你无须接入WAP

业务。

虽然中国移动由于一些特殊的原因,对WAP网站进行了分成三个级别,对于紧密合作的 SP

提供用户号码、UA(即 user-agent,即用户手机的浏览器版本,在手机里面的浏览器版本往往带着

机型的特征串,所以也就可以通过 UA来获取用户的机型);对于普通的 SP仅提供伪码(在 http

header 里面不添加实际的号码,而采用某一统一的另外的编码方式)和 UA;而对于与中国移动

没有合作的网站,不提供用户号码及屏蔽 UA。这方面中国电信相对开放,至少多数情况下你能

获取到用户的 UA。即便屏蔽了用户号码和 UA信息,你的WAP网站仍然能够被用户访问(当然

你的网站提供一些不合法的信息除外)。之所以写这一段,因为经常有人问我,我的WAP网站是

否只有接入运营商才能被用户访问,答案肯定是无需接入运营商也能被访问。

Page 136: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

136

1.1 WAP业务的网络结构

在开发WAP业务前我们有必要了解整个WAP业务在电信网络里面的架构。理解这样的网络模型

有利于你理解整个业务逻辑。

从图中我们需要了解:

1) 用户接入移动网络的时候需要使用账号 [email protected](以下简称 CTWAP)或者

[email protected](以下简称 CTNET),密码都为 vnet.mobi 进行拨号认证。当用户使用

CTWAP进行拨号的时候,PDSN(移动接入设备)其实分配给你的是 010.XXX.XXX.XXX

的WAP内网地址,当你需要访问放置在公网的网站的时候你都需要通过WAP网关(IP

统一为:10.0.0.200),多数情况下WAP网关仅仅是一个 HTTP的代理服务器,你无法使

用 QQ或者电驴这样的业务。当你使用 CTNET作为账号进行拨号的时候,PDSN实际给

你分配的是公网真实的地址,所以你不仅能访问WEB网站,也能使用 QQ这些非 HTTP

协议的业务。

2) 在使用账号 CTWAP进行拨号的时候,你访问任何网站都会经过WAP网关,WAP网关自身

与 Radius 服务器(拨号认证服务器)有接口,他可以根据你的 ip 地址进行反查,查询

到你的手机号码。同时由于你的任何请求都会经过WAP网关,WAP网关就有能力在你

Page 137: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

137

的 HTTP请求的 HTTP Header数据上加上一些数据,这些数据就包含你的手机号码。

3) 获取用户信息

在 jsp中可以通过:

request.getHeader("user-agent"); 来获取用户的浏览器版本,一般来说浏览器版本会携带

用户手机的特征串,可以通过该特征串获取用户的手机机型。

request.getHeader("x-up-calling-line-id"); 来获取用户的手机号码,只有在使用 CTWAP拨

号的情况下才能获取到号码。

4) 由于你的访问请求经过WAP网关,在你访问某些需要计费的网站的时候,某些特定 URL就

会被WAP网关拦截,并且重定向到WAP门户进行计费确认,比如显示你当前需要扣费

的业务是什么,费用如何,用户点击确认后才会被允许访问这些特定 URL,这个就是整

个WAP业务流程的基础。

5) WAP 门户在某些时候扮演者业务展示和门户入口的角色,比如中国电信的 WAP 门户为

wap.vnet.mobi。但它还扮演者正如第三条所说的用户计费确认的角色,在中国电信的

WAP 门户架构中,分为集团级别的 WAP 门户以及分省 WAP 门户,如果你的业务是全

国性业务,确认的页面在集团WAP门户完成,如果是省内的业务,在省内的WAP门户

完成,各省的WAP门户相对独立建设,可能显示的页面不尽相同。

1.2 WAP业务实现原理

正如 1.1中第三条所说,在整个WAP业务流程中,依赖于WAP网关针对特定 Url进行拦截,并将

用户重定向到WAP门户进行计费确认。这三个特定 Url指:入口 Url 、确认 Url、计费 Url。在进行

开发前我们有比较详细了解三个 Url具体的含义。

1) 入口 Url

这个名称很容易引起误解,尤其在阅读相关规范的时候,这个 Url 的名称会让你非常困惑。

我们可以更通俗的来说明这个 Url 的作用,你可以理解这个 Url 地址是你的业务介绍页面。

当用户不是通过你的WAP网站接触你的业务,而是到WAP门户查询或者浏览某业务的时候,

WAP门户合理的做法不是让用户直接订购,而是让用户去查看这个业务介绍页面,当然为了

让用户有冲动订购你的这个业务能够快速完成订购,最好你在这个页面上放置订购的链接,

也就是确认 Url,或者计费 Url,让用户能够点击。

Page 138: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

138

2) 确认 Url

确认 Url和计费 Url是最容易混淆的两个地址。

你需要知道 ISMP 里面一个能够使用的业务分成两级结构,即业务和产品两个级别(其实

我一直觉得应该叫产品、子产品,这样两者之间的从属关系就更加明确),业务或许可以说

是某一类业务,该业务没有价格,只是说明业务提供的类型,用户无法直接订购业务。而

对业务进行批价以后就形成产品,产品是用户能够直接订购的。一个业务下可以有一个或

者多个产品,如果是包月的业务,那么相同业务下的产品是互斥的。举个例子:189 邮箱

有 4个规格,分别为免费、5元邮箱、10元邮箱、20元邮箱。这个时候如果用户已经定购

了 5元邮箱,这个时候再订购 10元的邮箱,正常的逻辑应该是用户退订掉 5元邮箱,然后

订购 10元的邮箱,以避免同时订购了 5元和 10元的邮箱(因为是同一个邮箱、只有空间

大小不同)。这个时候就可以把 4个产品挂在同一个业务之下,同样的例子,用户在已经定

购了 5元邮箱,这个时候再订购 10元的邮箱,系统会自动进行替换,而无须用户自行退订

5元的邮箱。

确认 Url 是业务级别的地址,如果该业务下有包月的产品。当用户点击确认 Url 会被定向

到Wap门户,Wap门户会分别显示这个业务下的一个或者多个产品让用户订购包月。而如

果该业务下只有点播业务,那么只是会显示这个产品下点播的资费,而无法进行其他操作

(集团的 wap门户是这样处理的,分省的 wap门户可能不一样)。

在用户第一次订购的时候,对于包月业务来说、在确认 Url 上也能完成包月订购,但如果

用户已经包月,被错误的定向到确认 Url 的时候,会有一些令人迷惑的情况发生,会显示

用户已经订购该业务,而无法返回原先使用的地址,而如果是访问的计费 Url 那么会出现

使用按钮(而不是包月),这样用户还可以返回到原先的页面。所以在使用 Url的时候尽量

使用计费 Url。

还有需要明确的是,确认 Url在使用的时候还需要带上必要参数 SPID和 SID。其中 SPID

表示你在 ISMP上的企业代码,SID表示业务代码。

比如你在 ISMP申请的确认 Url为:

http://www.abc.com/queren.jsp

我们假设 SPID为 12100135,业务代码为:212000000000000003466;那么实际让用

户使用的时候需要让用户点击:

http://www.abc.com/queren.jsp?SPID=12100135&SID=212000000000000003466

你需要在 ISMP查询你申请并被审核通过的业务的编号,如果为了程序实现还需要增加增

Page 139: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

139

加其他参数直接可以在 url 里面增加,比如购买铃音,我需要在 Url 上传递

ringID,ringID为 300,那么你的 Url可以申请为

http://www.abc.com/queren.jsp?SPID=12100135&SID=212000000000000003466&rin

gID=300

3) 计费 Url

计费 Url 是产品级别的,一般情况下需要让用户付费,让用户点击计费 Url 即可,同确认

Url你还需要带上必要参数 SPID和 PID。其中 SPID表示你在 ISMP上的企业代码,SID表

示业务代码。

比如你在 ISMP申请的计费 Url为:

http://www.abc.com/jifei.jsp

我们假设 SPID为 12100135,产品代码为:112000000000000005410;那么实际让用户使

用的时候需要让用户点击:

http://www.abc.com/jifei.jsp? SPID=12100135&PID=112000000000000005410

你需要在 ISMP查询你申请并被审核通过的产品的编号,如果为了程序实现还需要增加增

加其他参数直接可以在 url 里面增加,比如购买铃音,我需要在 Url 上传递

ringID,ringID为 300,那么你的 Url可以填写为

http://www.abc.com/jifei.jsp?SPID=12100135&PID=112000000000000005410&ringID=300

特别说明:为了防止 SP欺诈,WAP网关会不允许通过 HTTP状态码为 302方式的来跳转进入所

有的计费 Url和确认 Url(普通的请求,状态码为 200),类似 jsp这样的代码

或者这样的代码

这两段代码实际都为通过 HTTP状态代码为 302的方式进行跳转,是不被允许的。

你可以直接使用 HTML的 A标记,比如

response.sendRedirect(“http://www.abc.com/jifei.jsp?SPID=12100135&PID=112000000000000

005410&ringID=300”);

response.setHeader("Location","

http://www.abc.com/jifei.jsp?SPID=12100135&PID=112000000000000005410&ringID=300");

<a href=“http://www.abc.com/jifei.jsp?SPID=12100135&PID=112000000000000005410”>购买

</a>

Page 140: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

140

这样的方式,让用户点击。

2 点播实现

我们先看下点播业务的线框图:

整个业务流程如下

1) 用户点击计费 Url,我们可以在某个页面上放置如下代码:

Page 141: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

141

2)WAP网关会检查用户点击的 Url,并会发现 Url为计费 Url。

3)WAP网关会将用户重定向去WAP门户,如果是全网的业务就定向去全网门户,如果是本省门户,

重定向去分省门户。

4)WAP门户会显示给用户产品名称,SP门户以及相关资费、客服电话等比较信息。

5)用户点击确认按钮。

6)WAP门户会将用户确认的信息送 ISMP进行鉴权。

7)ISMP生成话单。

8)ISMP返回WAP门户扣费成功。

9)用户被重定向到第一步点击的计费 Url :

http://www.abc.com/jifei.jsp?SPID=12100135&PID=112000000000000005410

10) SP需要记录用户的购买信息,也就是 jifei.jsp这个程序需要记录相应的数据,比如用户购买了某

本书,需要记录用户购买的书籍 ID,以及通过 request.getHeader("user-agent")获取用户的手机号码,并

记录到数据库中,以便下次用户访问的时候还能使用(可选)

11)SP提供用户服务。

虽然从线框图看,多达 11步流程之多,但从第二步至第八步,SP的程序都不参与业务逻辑的处理,

在业务申请完成,并完成一次测试,确认用户访问相关的计费 Url 能被重定向会 WAP 门户进行确认

以后,以确保业务申请和相关网元的同步没有问题以后。可以简单的理解流程只有 1,9,10,11 四

个流程;即用户一次访问成功过一次计费 Url就说明用户付费一次。

<a href=“http://www.abc.com/jifei.jsp?SPID=12100135&PID=112000000000000005410”>点播

</a>

Page 142: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

142

3 包月实现

Page 143: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

143

整个业务流程如下

2) 用户点击计费 Url,我们可以在某个页面上放置如下代码:

2)WAP网关会检查用户点击的 Url,并会发现 Url为计费 Url。

3)WAP网关会将用户重定向去WAP门户,如果是全网的业务就定向去全网门户,如果是本省门户,

重定向去分省门户。

4)WAP门户会显示给用户产品名称,SP门户以及相关资费、客服电话等比较信息。

5)用户点击确认按钮。

6)WAP门户会将用户确认的信息送 ISMP进行鉴权。

7)ISMP往 SP发起订购关系同步。

8)SP记录订购关系(接口同第二章 ISMP订购关系同步接口)

9)SP响应订购关系同步(接口同第二章 ISMP订购关系同步接口)

10)ISMP鉴权返回

11)用户被重定向到第一步点击的计费 Url :

http://www.abc.com/jifei.jsp?SPID=12100135&PID=112000000000000005410

12) SP需要记录用户的购买信息,也就是 jifei.jsp这个程序需要记录相应的数据,比如用户购买了某

本书,需要记录用户购买的书籍 ID,以及通过 request.getHeader("user-agent")获取用户的手机号码,并

记录到数据库中,以便下次用户访问的时候还能使用(可选)

13)SP提供用户服务。

从线框图来看,包月业务与点播业务最大的差别在于WAP门户送 ISMP鉴权的流程。当 ISMP收

到包月业务的订购的时候,会往 SP的订购关系同步接口(Web Service),只有 SP响应,并在接口返

回 ResultCode为 0才能形成订购关系,该接口参考文档前面的 ISMP订购关系同步部分。

包月业务由于有订购关系,所以需要 SP侧与 ISMP侧的订购关系保持一致。可能存在一个特殊的

情况:SP侧没有该用户的订购关系;而 ISMP侧有订购关系,在这种场景下,SP侧的WAP网站会显

示该包月的计费 Url 显示给用户,让用户点击。用户被定向到 WAP 门户确认的时候,并不显示用户

“订购”的按钮,而是显示“使用按钮”,这个时候用户点击“使用”的时候,ISMP并不往 SP侧订

购关系同步接口发起新的订购请求。而直接将用户定向到计费 Url。对于 SP侧的程序来说,似乎是没

<a href=“http://www.abc.com/jifei.jsp?SPID=12100135&PID=112000000000000005410”>购买

</a>

Page 144: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

144

有收到订购关系同步,用户就访问到计费 Url。这样的情况,需要 SP侧在线框图第 12步骤做特殊的

处理。

4. WAP 1.0还是WAP 2.0

讨论这个问题前我们可以先看个表格

WAP 1.0 WAP 2.0

标记语言 WML XHTML MP

脚本语言 WMLScript 无,部分智能手机能运行 JavaScript

支持可编程序软

支持 不支持

图片支持 WBMP,以及有限图片支持

(看浏览器类型)

Gif,png,jpg等都支持

支持事件 支持四个事件:

ontimer,onenterbackward,

onenterforward,onpick

不支持

对于熟悉开发Web程序的程序员来说,开发基于 XHTML MP的WAP2.0几乎没有任何难度,虽

然WAP 2.0的规范是最近发布的规范,但可喜的是新发布的手机基本上已经对 XHTML MP版本有了

非常好的支持。不过在我看来 WAP 2.0 的规范只是一个过渡的规范。因为相比 WAP 1.0 的 WML,

XHTML MP并没有与WMLScript相对应的脚本语言支持。因为实现完整的 JavaScript解析和运行目

前对于非智能手机来说还有小船重负的嫌疑。而与之相反,iPhone、Android手机由于采用了 WebKit

的内核,不但可以完整的支持 JavaScript脚本,并且在 HTML5的支持上走在了 PC的前面(占 PC浏

览器最大份额的 IE浏览器并不支持 HTML5)。

由于不支持脚本语言,使用WAP 2.0标准开发网站并不见得比WAP 1.0方便。很多需要依赖客户

端脚本语言运行的WAP1.0网站,更换为WAP 2.0还需要修改页面流程,而很多数据的判断都需要后

移到服务器端。况且还有大量在WAP 2.0标准发布前的手机存在,这些手机无法很好的对 XHTML MP

规范进行解析。与之相反,很多智能手机却无法对 WML 做很好的解析,比如 IE Mobile,Chrome

Lite,Safari 并不支持 WML 文件的浏览。为了兼顾这些不同手机,需要做好 UA 适配工作,针对不同

Page 145: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

145

机型的提供不同格式的页面。

5.UA适配

在以上讨论中,对于WAP网站来说我们知道手机的机型适配变得异常重要。在讨论 UA前我们看

下典型的几个 UA的例子:

1)SCH-M609/1.0 POLARIS/5.30.WAP CTC/1.0

2)Mozilla/5.0 (LG-KV755/CH755V05;U;REX/1.0;BREW/3.1.5;240*320;CTC/1.0) Polaris/6.15

3)K-Touch/Windows CE 5.2,Pocket PC Profile/MIDP-2.0 Configuration/CLDC-1.1 /1.0

第一个 UA我们通过第一个字串 SCH-M609可以知道该手机为三星M609;而第二个 UA说明这个

浏览器兼容Mozilla 5.0标准,而能识别机型的特征字串 LG-KV755并不在 UA前面几个字节,这里知

道用户的机型为 LG KV755。而第三个 UA我们只能从 K-Touch知道这个是天宇朗通的某款手机,并

且操作系统为Windows CE 5.2,不过我们还是无法知道具体是哪个机型。

前面两个 UA 是我们需要程序识别的,而第三个 UA 要求程序来识别过于苛刻。即便是为了识别

以上两个 UA我们面临一个非常大的问题是,从第一、第二两个例子当中我们可以看出,我们无法简单

的取第一个斜杠前的数据作为判断的依据。当然我们可以考虑全字符串匹配,但这面临着另外一个问

题,尤其针对智能机来说,用户可以安装不同的浏览器,或者这些机器出厂的时候就带了多个浏览器。

下图是我们在WAP网关取到的一些天语 E61机型留下的 UA数据,例子中我们就选取了个别数据

作为说明,实际 E61的 UA可能多达十几种。我们可以看到基本上如果按全字符串匹配会需要维护非

常庞大的数据,并且随着浏览器版本升级,这样的数据会越来越多。

在WAP网关发现天语 E61的 UA

Mozilla/4.0 (compatible; MSIE 6.0; Windows CE; IEMobile 7.11) TY-E61/V2116 MSIEMobile/6.0 CTC/1.0

Mozilla/5.0 (TY-E61/810118_2190_V3018;U;Windows Mobile/6.1;Profile/MIDP-2.0

Mozilla/4.0 (compatible; MSIE 6.0; Windows CE; IEMobile 8.12; MSIEMobile 6.0) TY-E61/V0809 MSIEMobile/6.0 CTC/1.0

Mozilla/5.0_(TY-E61/V2116;U;Windows Mobile/6.1;Profile/MIDP-2.0_Configuration/CLDC1.1;;CTC/2.0)_OPERA/9.51

Mozilla/5.0_(TY-E61/V2111;U;WindowsMobile/6.1;Profile/MIDP-2.0_Configuration/CLDC1.1;;CTC/2.0)_OPERA/9.51-

……………………..

当然你觉得这可能是智能机由于安装软件方便,是导致一款机型,不同 UA 众多的主要原因,不

是个例。不过我们同样可以取到三星的非智能手机 F539这款机器的 UA也接近 10种。

Page 146: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

146

在WAP网关发现三星 F539的 UA

SCH-F539 Infraware/5.30.CU (GUI)/WAP2.0 Profile/MIDP-2.0 Configuration/CLDC-1.1

SCH-F539/1.0 POLARIS/5.30.WAP CTC/1.0 Profile/MIDP-2.1 Configuration/CLDC-1.1

SCH-F539/1.0 POLARIS/5.30.WAP CTC/1.0 Profile/MIDP-2.1 Configuration/CLDC-1.1,Profile/MIDP-2.0

Configuration/CLDC-1.1,UNTRUSTED/1.0

SCH-F539/1.0 POLARIS/5.30.WAP CTC/1.0 Profile/MIDP-2.1 Configuration/CLDC-1.1,UNTRUSTED/1.0

SCH-F539/1.0 POLARIS/5.30.WAP CTC/1.0 Profile/MIDP-2.1 Configuration/CLDC-1.1,Nokia6610/1.0 (5.52)

Profile/MIDP-1.0 Configuration/CLDC-1.0,UNTRUSTED/1.0

SCH-F539/1.0 POLARIS/5.30.WAP CTC/1.0 Profile/MIDP-2.1 Configuration/CLDC-1.1,Profile/MIDP-2.0

Configuration/CLDC-1.0,UNTRUSTED/1.0

SCH-F539/F539CG17 POLARIS/5.30 CTC/1.0 Profile/MIDP-2.1 Configuration/CLDC-1.1

SCH-F539/F539CG17 POLARIS/5.30 CTC/1.0 Profile/MIDP-2.1 Configuration/CLDC-1.1,Profile/MIDP-2.0

Configuration/CLDC-1.1,UNTRUSTED/1.0

SCH-F539/F539CG17 POLARIS/5.30 CTC/1.0 Profile/MIDP-2.1 Configuration/CLDC-1.1,UNTRUSTED/1.0

……………………..

所以我们需要考虑使用某种算法来识别机型,可以明显看出 TY-E61,和 SCH-F539是最识别机型

的最重要数据。我们可能会考虑是否可以用正则表达式进行匹配。不过熟悉程序开发的人很快会想到

如果有 1000款手机,1000个正则表达式下来,是相当耗费资源的事情。

解决办法其实也相当简单,我们需要的是找出机型的特征串,整理出来如下图表格的数据:

手机型

号 特征串

生产厂

家 操作系统 分辨率 上市时间

三星F309 SCH-F309 三星 REX 176×220 200812

三星F319 SCH-F319 三星 REX 128×160 200511

三星F539 SCH-F539 三星 REX 240×320 200904

天语E61 TY-E61 天语 Windows Mobile 240×320 200905

酷派N900+ YL-Coolpad_N900+ 宇龙酷派 Windows CE 320×480 200912

酷派N900C YL-COOLPAD_N900C 宇龙酷派 Windows CE 320×480 200911

华为C7100 HUAWEI-C7100 华为 REX 240×320 200809

华为C7168 HUAWEI-C7168 华为 REX 176×220 200609

华为C7188 HUAWEI-C7188 华为 REX 240×320 200712

Page 147: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

147

华为C7189 HUAWEI-C7189 华为 REX 240×320 201004

华为C7200 HUAWEI-C7200 华为 REX 240×320 200802

1) 我们可以考虑将特征串作为某个 Hash的 key,而 Value设置为手机机型,以上数据转换为

Hash:Mobile {

"SCH-F309" => "三星 F309";

"SCH-F319" => "三星 F319";

"SCH-F539" => "三星 F539";

"TY-E61" => "天语 E61";

"YL-Coolpad_N900+" => "酷派 N900+";

"YL-COOLPAD_N900C" => "酷派 N900C";

"HUAWEI-C7100" => "华为 C7100";

"HUAWEI-C7168" => "华为 C7168";

"HUAWEI-C7188" => "华为 C7188";

"HUAWEI-C7189" => "华为 C7189";

"HUAWEI-C7200" => "华为 C7200";

}

2)将这样的数据可以使用 Java Servlet,或者 MemCached预先放入内存里面。这个时候就可以对于

用户访问WAP网站的 UA进行判断。

比如以下 UA:

SCH-F539/1.0 POLARIS/5.30.WAP CTC/1.0 Profile/MIDP-2.1 Configuration/CLDC-1.1

我们可以使用空格,斜杠等作为分隔符将以上 UA拆分成字符串数组为

{“SCH-F539”,“1.0”,“POLARIS”,“5.30.WAP”,“CTC”,“1.0”,“Profile”,“MIDP-2.1”,

“Configuration”,“CLDC-1.1”}

3)这些字串分别作为 Key到内存 Hash里面取值,我们会发现仅仅字串“SCH-F539”会取到数据

为:“三星 F539”,而其他 Key取到的都为 Null;这样我们就可以判断机型为:三星 F539

4 ) 同样的我们也可以将生产厂家,操作系统,分辨率,上市日期这些值作为内存 Hash,这样我们就

可以取到其他该机型的其他必要数据。

6.WAP 业务测试

对于WAP业务来说,手机一定不是最好的测试工具,使用手机输入一个网址,尤其是很长的网址

往往是灾难性的,调试的过程中也很难查看具体访问的那个 Url,这对于发现 Bug时候,希望能够 Bug

Page 148: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

148

重现,这个 Url 往往非常重要。如果你的 WAP 网站不需要测试 UA 适配的时候,其他流程完全可以

使用 PC代劳。

你能够使用 PC 进行测试的时候,你需要安装手机 Morden 的驱动,具体的手机安装驱动不仅相

同你可以考虑参考下手机的使用手册,并设置:

号码: #777

账号: [email protected]

密码: vnet.mobi

然后下载 Opera浏览器,选 Opera浏览器的原因因为其能够正常解析WML和 XHTML MP的文

件。然后你还需要设置代理服务器。

设置方法:打开 Opera浏览器,选择工具->首选项,如下图:

出现的窗口点击高级分页,然后在左边选择网络

Page 149: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

149

然后点击代理服务器,设置 HTTP:10.0.0.200 端口:80,如下图

这样基本上就可以测试了,为了能否可用,可以在地址栏试着访问 http://wap.vnet.mobi,如果能

够正常显示页面,就说明已经配置完成。

Page 150: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

150

四. 短信网关 SMGP接入篇

1. 短信网关接口协议 SMGP概述

SMGP协议并非由于 CDMA的引入而出现的新的接口协议,SMGP协议可以追溯到

在 2001年 11月前后的固网短信“家家 E”的年代, SMGP协议最初的 1.0版本完全是

为了在固定电话上实现短信业务而制定的规范,而为了实现在”家家 E”话机上实现动态菜

单功能,在”家家 E”正式商用的时候,协议变更成 1.3版本。当然由于固网终端先天不支

持短信功能,用户需要更换话机为家家 E 话机才能正常使用;短信实现又有别于移动业

务,短信的传输并不通过信令而采用类似传真的模式,通过语音信道进行模数转换,如

果用户终端不支持收到短信后会莫名振铃,该业务并未大规模商用就夭折了。

虽然小灵通发展比较早,但是小灵通开通短信功能应该在 2004年。由于小灵通短信

的引入,而 SMGP 协议也自然作为了小灵通短信的接入网关协议,版本也从 1.3 升级到

2.0版本,目前多数小灵通的短信接入仍然采用的 2.0协议。除了协议区别以外,小灵通

支持的短信长度并不是标准的 140个字节,早期发布的小灵通仅支持 90个字节(45个汉

字),而后期的的小灵通也只支持 116个字节(58个汉字)。

在 CDMA引入前,SMGP协议已经升级为 3.0协议,3.0协议相比 2.0协议增加了 Tlv

(Tag Length Value)可选字段,同时对于点播业务需要上下行短信关联 LinkID,LinkID在

Tlv字段里面增加。随着 2008年 10月电信接受 CDMA网络,SMGP3.0协议自然替换了

SGIP成为了 CDMA的短信接入协议。

与中国移动的 CMPP 以及联通的 SGIP 类似,SMGP 协议也是在 SMPP 协议上进行

了扩展,主要为了短信增值业务扩展了计费和鉴权字段,而这三种协议在程序在接口逻

辑上也存在一定的区别:CMPP和 SMGP在接入以后,SP端一直作为客户端,不管是下

行短信还是用户上行亦或状态报告,都是短信网关通过 SP作为客户端的连接中返回;而

SGIP 协议中 SP 与短信网关互为客户端和服务器端,当下行消息的时候,短信网关作为

服务器端,而上行消息的时候,短信网关作为客户端,以意味着 SP端需要开启一个服务

以监听是否有短信达到。

当然你接入电信的短信业务目前可能存在两种方式,一种是 ISMP 加短信网关方式

Page 151: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

151

(也有成为 ISMP加 SE方式);另外一种是 ISMP加 ISAG模式,如果是后者请看 ISAG

接入部分。

与短信网关的连接还分成长连接和短连接:短链接方式就是提交完以后就断开连接,

如果需要再发就重新建立连接;长连接方式就是一直保持连接,为了保持连接当服务器

发起 Active_Test封包检测连接的时候你需要应答 Active_Test。长连接和短链接方式都可

以用来发送短信,而接收上行消息和状态报告就需要保持长连接。

2. SMGP封包结构

一个 SMGP的封包都由 12个字节固定长度的 SMGP Header和 非固定长度的 SMGP Body

组成,如下图:

A)SMGP Header又分别由 4个字节的 PacketLength,4个字节的 Request ID,4个字节的

SequenceID组成。

PacketLength表示整个封包的长度,这里特别说明的是这个长度包含自身的 4个字节的

长度。

Request ID表示封包的类型,比如 0x00000001表示登录请求 Login,0x00000002表示

提交一条短信 Submit等。

Sequence ID表示消息流水号,这个主要是关联一个请求和一个应答,比如一个发送请

求 Submit,网关处理以后会回复一个发送应答 submit_resp,在 submit_resp 封包里面会将

Submit的 Sequence ID原样返回。

B)SMGP Body是消息体的具体内容,根据 Request ID不同,Body的数据也不尽相同,

当然有些封包比如链路检测 Active_Test,连接检测应答 Active_Test_Resp 以及退出请求

Exit,退出请求应答 Exit_Resp都是没有 SMGP Body。

Page 152: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

152

1) 我们看一下实际的一个 Submit封包转换成 16进制表示如下:

0000011200000002000000e3060100000000000000000000003030303030000000303030000000080000000

000000000000000000000000000000000000000000000000000000000000031303632303036380000000000

000000000000000031383936373434313131380000000000000000000001313839363734343131313800000

000000000000000507b2c4e00676177ed4fe17b2c4e00676177ed4fe17b2c4e00676177ed4fe17b2c4e0067617

7ed4fe17b2c4e00676177ed4fe17b2c4e00676177ed4fe17b2c4e00676177ed4fe17b2c4e00676177ed4fe10000

000000000000001000083132313030313335001200153131323030303030303030303036343039303538380

009000103000a000101

其中:

00000112表示封包长度转成成 10进制以后为 274,表示整个封包含自身 4个字节为 274

个字节

00000002表示包类型为 Submit

000000e3 表示包的流水号为 227

SMGP Body内容为具体的一条短信,这个后续章节会有说明,这里了解下结构就可以了

2) 发出以后,服务器会返回一个 Submit_Resp请求,封包如下:

0000001a80000002000000e35710610427135548252500000000

其中:

0000001a 表示封包长度含自身 4个字节长度为 26字节

80000002 表示包类型为 Submit_resp

000000e3 表示流水号为 227

SMGP Body内容为一个MsgID为:57106104271355482525和发送结果为 0表示提交成功。

为了能够保存一个封包,我们可以建一个公共的类Message

package cn.com.zjtelecom.smgp.message;

import cn.com.zjtelecom.util.TypeConvert;

public class Message {

protected byte buf[];

protected int sequence_Id;

public Message() {

Page 153: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

153

}

public void setSequenceId(int sequence_Id) {

this.sequence_Id = sequence_Id;

}

public byte[] getBuf() {

return buf;

}

public int getSequence_Id() {

return sequence_Id;

}

public void setSequence_Id(int sequence_Id) {

this.sequence_Id = sequence_Id;

}

}

注:buf[]这个 byte串用于存放 smgp body。这个定义了一个基础的 smgp封包,后续的封包都在此类里面扩展,在不同

的封包里面 RequestID 都是固定的,比如 Submit 封包肯定是 0x00000002,所以这个类里面就不定义 RequestID,

PackectLength根据 buf串的长度给出,所以也没有定义。

与短信网关一个长连接的交互如下:

1)与短信网关建立 Sockect连接

2)发送登录请求包 Login,如果帐号密码以及 IP 地址通过认证,短信网关回复

Login_Resp

3)如果有需要提交短信,SP侧发起 Submit封包,短信网关处理后答复 Submit_Resp

4)如果连接空闲状态,短信网关定期会发送连接侦测包 Active_Test,SP 侧应答

Active_Test_Resp

5)如果有上行消息或者状态报告,短信网关会发起 Deliver包,SP侧收取短信后回复

Deliver_Resp

整个过程如下图所示:

Page 154: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

154

所以我们程序需实现 Login、Submit、Active_Test_Resp、Deliver_Resp 封包的封装,

以及 Login_Resp、Submit_Resp、Deliver、Active_Test封包的解析,后续章节会逐一讲解。

3. SMGP相关术语解释

Octet String: 不强制以 0x00 结尾的定长字符串。当位数不足时,在不明确注明的情况

下,应左对齐,右补 0x00。在明确注明的情况下,以该字段的明确注明为准。

MSGID: 短信序列号,长度为 10位,该字段一般采用 BCD编号,也就是 4个 bit表示

一个数字,所以其转换为字串表示以后长度为 20位。

Page 155: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

155

4. 封包拼装和解析

为了确保封包能够 16 进制表示和 byte[]之间任意转换,方便开发和调试,我们定义了一

个类 cn.com.zjtelecom.util.Hex

package cn.com.zjtelecom.util;

import java.io.ByteArrayInputStream;

import java.io.DataInputStream;

public class Hex {

public static byte[] rstr(String hex) {

int length = hex.length();

byte[] bHex = new byte[length/2];

String temp = null;

int t = 0;

for (int i=0; i<length; i++) {

temp = "" + hex.charAt(i) + hex.charAt(++i);

bHex[t++] = (byte)Integer.parseInt(temp, 16);

}

return bHex;

}

public static String rhex(byte[] in) {

DataInputStream data = new DataInputStream(new

ByteArrayInputStream(in));

String str = "";

try {

for (int j = 0; j < in.length; j++) {

String tmp = Integer.toHexString(data.readUnsignedByte());

if (tmp.length() == 1) {

tmp = "0" + tmp;

}

str = str + tmp;

}

} catch (Exception ex) {

}

return str;

}

}

Page 156: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

156

这个类有两个方法,一个 rstr:将 String转换为 byte数组,一个 rhex:将 byte数组转换

为 16进制的表示的 String。

SMGP Header中 RequestId是区分不同封包的标识,为了程序便于阅读,我们可以事先

定义一个类 cn.com.zjtelecom.smgp.protocol.RequestId,代码如下:

package cn.com.zjtelecom.smgp.protocol;

public class RequestId {

public static int Login = 0x00000001; // 客户端登录

public static int Login_Resp = 0x80000001; // 客户端登录应答

public static int Submit = 0x00000002; // 提交短消息

public static int Submit_Resp= 0x80000002; // 提交短消息应答

public static int Deliver = 0x00000003; // 上行消息

public static int Deliver_Resp = 0x80000003; // 上行消息应答

public static int ActiveTest = 0x00000004; // 链路检测

public static int ActiveTest_Resp= 0x80000004; // 链路检测应答

public static int Exit = 0x00000006; // 退出请求

public static int Exit_Resp = 0x80000006; // 退出请求应答

}

这样我们就可以用 RequestId.Submit表示 0x00000002。

4.1登录请求相关封包:Login,Login_Resp

Login是 SP端发向服务器端注册作为合法客户端的请求,登录的时候需要传送帐号以

及按照约定加密的密码,同时客户端还需要传送给服务器端登录的方式以区分该连接是

作为收还是发。短信网关核实 Login请求以后,根据请求结果返回 Login_Resp,以确认是

否登录成功。

为了便于理解,我们将 SMGP Header也放在参数说明里面。参考 SMGP3.0协议,请

求参数如下:

字段 长度(字节) 数据类型 备注

PacketLength 4 Integer 由于 Login 包是固定长度 42 个字节,所以

PacketLength为:0x0000002A

Page 157: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

157

RequestID 4 Integer 由于是 Login封包,这里固定送 0x00000001

SequenceID 4 Integer SequenceID是关联请求和应答的参数,由于 Login

对于一个建立的连接只有一次,所以 SequenceID

没有特殊的取值要求

ClientID 8 Oct String 登录网关的帐号,这里特别强调下该帐号最大长

度为 8 位。虽然部分网关能够配置长于 8 位的帐

号,但是在 SMGP 协议里面是无法传送的,如果

一旦拿到的帐号大于 8 位,请联系电信方相关人

员修改长度为 8位以下。

AuthenticatorClient 16 Oct String 登录网关的客户端认证码,这里需要说明的是这

个字段并非密码,他是MD5(ClientID+7个字节的

二进制 0x00组成+password+TimeStamp)组成。

TimeStamp 需要跟参数 TimeStamp 取值相同,具

体程序实现看下文

LoginMode 1 Integer 客户端登录网关的模式

0=发送模式

1=接收模式

2=收发短信

TimeStamp 4 Integer 时间戳,这里也是容易出现误解的地方,时间按

MMDDHHmmss 取值后获得一个整数,然后将此

整数转换为二进制。比如 9月 28日 13时 06分 20

秒,按照 MMDDHHmmss 的规则转换后为

0928120620,转换为 16进制为 0x3751FF2C。

ClientVersion 1 Integer 客户端版本,这个字节的高位字段表示大版本号,

地位字节表示小版本好,如果是 1.3协议应该表示

为 0x13,如果是 3.0协议应表示为 0x30

根据规范我们需要先完成一个加密需要的类,代码如下:

Page 158: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

158

4.2发送短信相关封包:Submit,Submit_Resp

4.3 上行短信相关封包:Deliver,Deliver_Resp

4.4 退出请求相关封包:Exit,Exit_Resp

4.5 TLV字段封装

Page 159: 浙江电信移动业务接入实践20100513

浙江电信移动业务接入实践

159

五. 关于本文档

我就职于浙江电信互联网与信息事业部,网名:dowell

我的 Blog站点:

IT评论相关:http://blog.donews.com/dowell

http://blog.sina.com.cn/dowellhz

http://dowell.blog.techweb.com.cn

技术相关: http://blog.csdn.net/dowellhz

SMGP开源项目:http://smgp.googlecode.com

新浪微博: http://t.sina.com.cn/dowellhz