跳到导航
BEA Dev2Dev Oracle and BEA
首页 资源中心 dev2dev学堂 在线技术论坛 User Group CodeShare
dev2dev 首页 > 资源中心 > 技术文章
基于SIP Servlets和VoiceXML的IMS应用程序示例

时间:2006-08-15
作者:David BurkeDarragh O'Flanagan
浏览次数:
本文关键字:voicexmlSIPSIP server IMSWebLogic Communications PlatformDavid Burke
文章工具
推荐给朋友 推荐给朋友
打印文章 打印文章

摘要

  IP多媒体子系统(IP Multimedia Subsystem,IMS)是3rd Generation Partnership Project(3GPP)组织提出的聚合电信架构,用于合并移动电话和Internet技术,以便在单一网络中以统一的方式交付语音、视频和数据。作为目前电信领域中的热点话题,IMS迅速成为希望升级现有移动电话和固网网络的运营商的架构选择。

  本文将介绍IMS架构的要点,并提供一个基于Java SIP servlets与VoiceXML(与此架构兼容)的完整应用程序。此应用程序名为Personal Assistant,它提供一个服务,该服务代表其所有者回答呼叫,并试图通过呼叫所有者的工作电话、家中电话或者移动电话找到所有者。当找到所有者以后,所有者可以选择是否接听电话并与呼叫方联系。

  读者可以获得该应用程序的完整源代码,此外,还提供了运行应用程序所需的BEA WebLogic SIP Server与Voxpilot Media Resource Function的评估版本的下载链接。阅读本文并分析代码之后,读者应该懂得如何通过简单地利用VoiceXML中即时可用的特性创建易于扩展的应用程序,以便融入更高级的特性,例如,语音识别、语音合成以及视频交互。

简介

  首先,我们将介绍一下IMS架构和VoiceXML,然后关注如何使用VoiceXML和SIP为IMS创建交互式应用程序。本文余下的部分将描述示例应用程序。

什么是IMS?

  IMS是一个标准化架构,它采用基于3GPP的SIP协议的语音与Video-over-IP技术,在基于标准分组的IP网络上运行。图1展示了IMS架构的简化图;我们已经把网络分为三部分:服务层、控制层以及接入网络。

图1. IMS架构简化视图

  图1. IMS架构简化视图

  IMS应用程序处于服务层。这一层包括:SIP应用服务器(AS)(例如:WebLogic SIP Server),它操纵SIP信令并与其它系统接合,从而执行IMS应用程序和服务。应用服务器可能还包括HTTP功能,这使其也可用作资源(如:媒体文件与VoiceXML应用程序脚本)的内容服务器。通常,应用服务器将为创建新的服务提供编程语言与框架,例如:Java SIP与HTTP Servlets。请参见An Introduction to SIP, Part 2: SIP Servlets(中文版,Dev2Dev,2006年4月)。

  IMS网络的控制层包括管理呼叫的建立、管理和释放的节点。控制层的核心是一个专用SIP服务器,称为Call Session Control Function (CSCF);所有的SIP信令都要通过这个必要的节点。CSCF检查每条SIP消息,并确定信令是否应该访问在到达其最终目的地途中的一个或多个应用服务器。CSCF与Home Subscriber Service (HSS)交互,后者提供了一个用户相关信息的中央储存库。当CSCF或AS需要媒体功能时,它将信令发送给Media Resource Function (MRF)(比如Voxpilot MRF)。MRF提供集中式的媒体处理功能(将在下面进行介绍)。最后,媒体网关(Media Gateway,MGW)及其控制节点Media Gateway Control Function (MGCF)与电路交换网连接。

  接入网络由IP路由器和传统的PSTN交换机组成,它们分别提供从现代IP电话设备和更古老的电路交换设备到IMS网络的访问。兼容IMS的IP设备结合了一个用于将语音和视频呼叫置于网络上的SIP用户代理。

什么是VoiceXML?

  VoiceXML是一种基于标记的声明式编程语言,由W3C标准化,用于创建基于语音的电话应用程序。VoiceXML支持以同步语音为特征的对话、数字化音频、语音和DTMF键输入的识别、音频录音、基本的电话呼叫控制,以及混合的交互式会话。近来,由于音频可被简单地视为一种额外的VoiceXML输入/输出渠道,所以VoiceXML成为一种用于创建视频交互式服务的理想语言。未来的VoiceXML版本将包括专门用于视频服务的新特性。

  VoiceXML允许对交互式语音和视频响应应用程序进行基于Web的开发和使用内容交付范例。Web或应用服务器驻留VoiceXML文档,此类文档指定语音用户界面(Voice User Interface,VUI)的方式与HTML文档定义图形化用户界面(GUI)的方式几乎相同。VoiceXML解释器通过HTTP从Web或应用服务器获取VoiceXML文档,对其进行解释,并将指令发送到适于呈现语音用户界面的媒体处理资源。

  VoiceXML从根本上说是一种对话语言:它被开发出来以管理人与计算机之间的语音或视频对话。虽然VoiceXML确实包括了呼叫传输的一些基本特性(通过其<transfer>元素),但是该功能实际上只在没有SIP AS出现时才适用。而AS则完全适合于执行各种呼叫控制和SIP信令操纵。

为IMS创建交互式应用程序

  IMS架构由许多节点组成,这些节点是交付诸如移动性、计费、互通和服务质量之类的核心电信功能所必需的。然而,要交付交互式服务,主要需要三个节点的相互作用,即一个用户代理(UA)、AS和MRF。图2展示了一个用于交付基于SIP的交互式服务的规范IMS架构。

图2. 用于交付基于SIP的交互式服务的规范架构

  图2. 用于交付基于SIP的交互式服务的规范架构

  UA或者是SIP电话,或者是到传统移动电话或固网电话的网关。SIP信令的发送与媒体无关。媒体通过实时传输协议(Real-Time Transport Protocol,RTP)直接在UA和MRF之间运行。AS负责呼叫控制(即,SIP信令操纵)和应用程序管理功能。可以将AS视为一个聚合容器,它具有支持HTTP协议的能力。AS驻留描述用户通过语音和视频交互的部分的VoiceXML文档。当运行在AS上的服务逻辑需要与用户交互时,它转发SIP信令到MRF,并随之提供一个HTTP URI以运行VoiceXML应用程序。该URI通常指向一个AS上的VoiceXML应用程序,随后MRF从AS或通过HTTP获取VoiceXML文档。MRF执行VoiceXML文档,该文档驱动与用户的交互。MRF不断地从AS请求新的VoiceXML文档,直到应用程序结束,或者AS终止它。MRF可能通过HTTP或SIP将数据发送回AS(例如,通过将其编码在SIP BYE请求中)。

  请注意,当前3GPP尚未完全定义到MRF的接口(称为Mr接口),只定义了它是基于SIP的。虽然IMS规范详细定义了如何通过AS创建呼叫控制应用程序,但是它们基本未对如何对媒体交互进行编程做出说明。同时,VoiceXML已经成为对电话交互式应用程序进行编程的业界标准,它是IMS的自然选择。IETF的一个新的Internet草案“SIP Interface to VoiceXML Media Services”(请参见参考资料)描述了SIP和VoiceXML如何一起工作。最后,请注意图2中所示的通用架构在IMS网络外同样适用,而且是在IMS前网络、企业和呼叫中心部署应用程序的理想选择。

应用程序描述

  本文描述的Personal Assistant应用程序是一个全特性示例,它用于演示如何使用SIP servlets和 VoiceXML创建与IMS架构一致的应用程序。图3图解了应用程序流。

图3. Personal Assistant应用程序流

   图3. Personal Assistant应用程序流

  此应用程序从呼叫方向AS发送INVITE(指示被叫方的SIP URI)开始。被叫方的Personal Assistant应答并播放问候。呼叫方被要求记录一个名称。然后,Personal Assistant获得先前配置的用于被叫方的设备SIP URI清单。以一次一个的方式向每个SIP URI进行呼叫。如果被叫方未应答,则尝试下一个URI。如果Personal Assistant试完了所有URI,它会向呼叫方播放结束消息,然后终止。如果被叫方应答,则会被询问是否希望接受此呼叫。被叫方通过DTMF按键方式响应。如果被叫方接受呼叫,他/她会被连接到呼叫方,否则此呼叫方会被终止。在播放问候时,可以通过按下“*”键的方式更改个人问候。

  应用程序的SIP流稍后将作为代码讨论的一部分在本文中给出。

  提供了一个简单的Web页面(http://<app_server_host>:7001/PersonalAssistant/)以配置用户公共地址(记录地址)到一个或多个设备URI的映射,这些URI对应于不同终端,例如,家庭电话或办公室电话(参见图4)。

图 4. 添加SIP地址

  图 4. 添加SIP地址

运行应用程序

  要运行应用程序,需要执行以下步骤:

  1. 下载并安装BEA WebLogic SIP Server 2.1评估版。
  2. 下载并安装用于Windows的Voxpilot MRF评估版(产品名称:VoiceXML Media Server)。
  3. 下载并安装SIP电话。SJ Labs提供免费下载;此外,CounterPath提供名为Eyebeam的低成本电话,它也支持视频。
  4. 下载应用程序ZIP文件,解压缩,然后按照docs\AppSetupInstructions.pdf中的说明操作。

应用程序代码

  本部分讨论应用程序代码中的一些具有重大架构意义的方面。这里我们假定读者基本理解SIP servlets和VoiceXML;请参见本文末尾的参考资料,其中有一些介绍性材料的链接。

概述

  Personal Assistant应用程序是一个SIP servlet和HTTP servlet聚合应用程序。SIP servlet代码负责执行SIP背对背用户代理(back-to-back user agent,B2BUA)功能。HTTP servlet代码通过向Voxpilot MRF提供VoiceXML文档的方式来管理VoiceXML对话。HTTP servlet代码也用于提供配置应用程序的HTML界面。图5概览了顶级assistant软件包。

图 5. assistant软件包(单击图片查看大图)

  图 5. assistant软件包(单击图片查看大图)

   Personal Assistant应用程序具有一个SIP servlet (AssistantSipServlet)和一个HTTP servlet (AssistantHttpServlet),它们用作到应用程序的SIP和HTTP入口点。以下描述了组成assistant软件包的重要类。

AssistantHttpServlet

  此servlet用作控制器,它响应来自MRF的HTTP请求,触发关联AssistedCall对象,以根据与(运行于MRF上的)VoiceXML应用程序交互的人给出的选项执行各种功能。根据HTTP请求参数,此servlet将使用适当的VoiceXML文档或音频字节响应MRF。此servlet还用作来自Web浏览器的HTTP请求的控制器。在任何情况下,servlet分派到包含VoiceXML或HTML的JSP文件,从而提供控制逻辑和呈现之间的清晰分区。

AssistantSipServlet

  AssistantSipServlet在部署描述符(sip.xml)中配置,以接收SIP INVITE请求。收到INVITE后,会创建一个SipApplicationSession和AssistedCall对象,如以下代码片段所示:

protected void doInvite(SipServletRequest req) throws
                  ServletException, IOException {

    List calleeContacts = new ContactDAO().getContactsForAor(aorURI);

    SipApplicationSession appSession = factory.createApplicationSession();

    AssistedCall call = AssistantService.createAssistedCall(req,
                    appSession, calleeContacts);
    call.startAssistedCall();
}

  AssistantSipServlet也接收SIP请求,并通过其doRequest()和doResponse()方法响应。这些方法将SIP消息传递到合适的目标(请参见接下来对ISipDialog接口的描述)。

ISipDialog

  此接口通过要接收SIP对话消息的类实现。ISipDialog 引用存储在SipSession中,可在以后检索它。ISipDialog 接口允许AssistantSipServlet 适当地分派SIP消息。SipServletResponse类上的getSession()方法返回与当前SIP对话关联的SIP会话。先前已被插入其中的SIP会话提供对ISipDialog的引用。请参见以下代码示例:

protected void doResponse(SipServletResponse arg0) throws
                 ServletException, IOException {

    super.doResponse(arg0);

    ISipDialog dialog = (ISipDialog) arg0.getSession().
        getAttribute(ISipDialog.SESSION_KEY);

    if (dialog != null)
        dialog.doSipServletMessage(arg0);
}
AssistedCall

  这是面向应用程序的中央类,包含完成应用程序主要流所需的控制逻辑和方法。AssistedCall对象存储在SipApplicationSession中。应用程序管理呼叫线路(call-leg)对(其中,应用服务器用作B2BUA)。呼叫线路对的管理被封装在ITPCC接口中,例如,CallerToVMSCall、CalleeToVMSCall、CallerCalleeTransfer和CallerValedictionTransfer。AssistedCall类实现ITPCCEventListener接口,这允许它接收正在运行的呼叫线路对进程的回调通知。如果呼叫方或被叫方过早结束呼叫会话,或由于某些原因未能连接呼叫,则AssistedCall实现将接收这些通知,并优雅地结束其他呼叫。

AssistantService

  此工具类提供管理AssistedCall对象实例、网络工具方法和SIP消息头日志记录服务的方法。AssistantService对象也可用于创建计时器,以便呼叫能够接收超时通知。这是使用servlet容器的TimerService实现的。AssistantTimerListener类在sip.xml文件中被声明为应用程序计时器侦听器。

ContactStoreDAO

  此数据访问对象包装对SQL数据库的访问,以存储从人的公共地址(记录地址)到其多个设备地址(例如其家庭电话或工作电话)的映射。ContactStoreDAO返回一个称为ContactStoreVO的值对象。通过两个称为Greetings和Contacts的表使用PointBase数据库。Greetings表有两个字段,aor_uri是用户的公共地址,它配置了Personal Assistant,greeting是包含用户的音频问候的二进制大对象。Contacts表具有aor_uri键,它与第二个字段device_uri关联。Contacts表保存每个用户设备的URI。

MRF控制

  现在有许多不同的协议都利用SIP来控制媒体服务器,这些媒体服务器作为IETF Internet Draft发布。流行的例子包括Media Sessions Markup Language (MSML)、Media Server Control Markup Language (MSCML)和到VoiceXML Media Services的SIP Interface (draft-burke-vxml)。为了使实现尽可能通用,我们选择使用IMediaController和IMediaListener类(com.voxpilot.mediacontrol软件包的一部分)来抽象接口。IMediaController接口提供方法,以开始和终止VoiceXML会话。IMediaListener接口用于将事件(例如VoiceXML会话的开始或终止)报告给SIP servlet应用程序。这两个接口应被视为可以在未来进行扩展的“骨架”,以提供对媒体服务器上的其他功能(例如播放公告的低级命令、收集来自用户的输入、开始会议和执行代码转换)的访问。

  我们已经提供了IMediaController的具体实现(称为SipVxmlMediaController),它封装了标准方法,以触发RFC 4240和draft-burke-vxml中描述的VoiceXML应用程序会话。简单地说,MRF是通过使用专门格式化的SIP URI将SIP INVITE发送到MRF的方式进行调用的:

sip:dialog@192.168.1.1:5060;voicexml=http://example.com/start.vxml;aai=123

  用户部分固定在dialog上,以指示VoiceXML对话服务。主机和端口指的是MRF的主机和端口。voicexml参数指定要执行的首个VoiceXML页面,在我们的例子中,它将引用应用服务器。aai参数用于保存应用程序到应用程序的信息,可在VoiceXML应用程序中根据会话变量session.connection.aai检索这些信息。aai参数对于将附加信息传递到VoiceXML应用程序非常有用。对于Personal Assistant应用程序,我们将传入SipApplicationSession ID,然后将此ID传回每个HTTP请求以实现相关性(下面将详细介绍)。

  以下代码片段显示如何创建媒体控制器,以及创建和连接VoiceXML会话:

// Create a media controller
IMediaController mediaController = MediaControllerFactory.create(
                 IMediaController.SIP_VXML_MEDIA_CONTROLLER,
                 sipFactory, sipAppSession);

// Register for events
mediaController.setMediaListener(this);

// Create a VoiceXML session
VxmlParam [] params = new VxmlParam[2];
params[0] = new VxmlParam(VxmlParam.VOICEXML, vxmlURL);
params[1] = new VxmlParam(VxmlParam.AAI, sipAppSession.getId());

mediaController.createVxmlSession(sipMrfUri, fromUri, toUri,
                                  sdpObject, sdpContentType,
                                  params);
// Connect
mediaController.connectVxmlSession();

/**
 The VoiceXML session can be started after the
 IMediaListener.vxmlSessionReady() event is received by calling
 the IMediaController.startVxmlSession() method.
**/

  IMediaController接口通过其doSipServletMessage()接收SIP消息(例如来自MRF的200 OK响应)。在Personal Assistant应用程序中,AssistantSipServlet将SIP消息分布到实现ISipDialog接口的对象。我们使用适配器模式,此模式使用一个称为SipMediaControllerAdapter的类,此类实现ISipDialog接口,并将引用聚合到一个IMediaController。这样,就通过其适配器将SIP消息传递到IMediaController。

维护应用程序状态

  在SIP端,应用程序状态通过SipApplicationSession对象维护,此对象是标准SIP servlet软件包的一部分。每当向Personal Assistant应用程序发送一个新INVITE请求时,就会创建一个新SipApplicationSession对象。SipApplicationSession对象依次包含对一个或多个SipSession对象的引用——每个SIP对话一个。对于Personal Assistant应用程序,我们将中央AssistedCall对象置于SipApplicationSession中。

  支持聚合应用程序的容器所需的关键功能(例如涉及HTTP和SIP协议的功能)是能够提供HTTP请求及其对应SIP对话之间的某种相关性。SIP servlet规范未提到SipApplicationSession对象能够聚合HttpSession对象,因此创建此关系的机制未被扩展。即将推出的WebLogic SIP Server 2.2通过引入一些用于相关HTTP和SIP会话的有用方法,扩展了SIP servlet API。

  在Personal Assistant应用程序中,我们在AssistantService类上提供两个静态方法,addSipApplicationSessionReference()和findAssistedCall()。前者将SipApplicationSession存储到servlet上下文,主键是其ID(从SipApplicationSession.getId()获得)。后者检索被赋予ID的SipApplicationSession对象。

  每次调用MRF时,通过aai参数将SipApplicationSession ID传递到VoiceXML应用程序。从MRF返回到应用服务器的每个HTTP请求均包括此ID,从而允许HTTP servlet检索对应的SipApplicationSession和关联应用程序状态。

呼叫控制

  为了更好地分解应用程序,Personal Assistant代码将呼叫线路对之间的呼叫流模式封装到专门的ITPCC子类。共有四个类:

  • CallerToVMS:将呼叫方连接到MRF,以播放问候消息和记录呼叫方名称。
  • CalleeToVMS:将出站呼叫置于被叫方,并将被叫方连接到MRF,以将呼叫方希望与其通话的消息通知被叫方。
  • CallerCalleeTransfer:将呼叫方连接到被叫方。
  • CallerValedictionTransfer:如果被叫方拒绝与呼叫方通话,则将呼叫方连接到MRF,以播放结束消息。

  (VMS指的是VoiceXML Media Server,它是MRF的别名)。

  ITPCC接口是直观的:startCall()方法开始呼叫流模式,endCall()方法终止呼叫线路。disconnectA()和disconnectB()方法可用于解除特定呼叫线路。

  图6图解了assistant.sip.tpcc软件包。

图 6. assistant.sip.tpcc软件包(单击图片查看大图)

  图 6. assistant.sip.tpcc软件包(单击图片查看大图)

CallerToVMS

  图7图解了CallerToVMS类封装的流。

图 7. CallerToVMS封装的流

图 7. CallerToVMS封装的流

  借助于用作B2BUA的应用服务器,呼叫方邀请被叫方(1)。AssistantSipServlet使用初始INVITE细节创建AssistedCall对象,然后开始一个到MRF的B2BUA呼叫。一个临时100 Trying响应被发送到呼叫方(2)。来自初始INVITE (offer1)的SDP信息被复制到INVITE请求,此请求被发送到MRF (3),但在本例中,SIP请求URI被设为指示一个资源,此资源位于Personal Assistant应用程序中(assistant?Action=greetCaller)。

  一旦收到200 OK (4) 响应,SDP信息(answer1)即被复制到另一个200 OK (5)响应,此响应随后被发回呼叫方,这样现在双方均拥有直接通信所需的媒体会话信息。一个ACK消息被从呼叫方的UA发送到AS (6),然后从AS发送到MRF (7),以完成两个呼叫线路上的三路握手。此时,AssistedCall被通知由CallerToVMS封装的呼叫线路对已经被成功建立(这是通过ITPCCEventListener接口上的callStarted()方法实现的)。

  图8图解了应用服务器和MRF之间的HTTP流。

图 8. CallerToVMS封装的HTTP流

图 8. CallerToVMS封装的HTTP流

  MRF从AssistantHttpServlet (9, 10)读取VoiceXML,解释它,并允许呼叫方记录其名称(11)。在HTTP POST操作(12)中,记录的呼叫方名称的音频字节被返回到AssistantHttpServlet,它们被应用程序存储在这里,以便音频能够在以后向被叫方传播。然后HTTP servlet将VoiceXML文档返回MRF (12),后者播放等待音乐使呼叫方等候,直到AssistedCall准备好中断呼叫。此时,HTTP servlet使用AssistedService查找关联的AssistedCall对象,并指示它现在可开始联系被叫方,因为呼叫方正处于等待状态。这通过执行AssistedCall.locateCallee()方法实现。

  CalleeToVMS

  图9图解了CalleeToVMS类封装的流。

图 9. CalleeToVMS封装的流

图 9. CalleeToVMS封装的流

  此流使用经典的第三方呼叫控制模式,将向被叫方发送出站呼叫。图10图解了应用服务器和MRF之间的HTTP流。

图 10. CalleeToVMS封装的HTTP流

图 10. CalleeToVMS封装的HTTP流

  MRF读取来自AssistantHttpServlet (9, 10)的VoiceXML (assistant?Action=greetCallee),解释它并读取音频字节以记录呼叫方名称(11,12)。这会播放给被叫方,DTMF选择允许被叫方选择是否接收呼叫(13)。选择随后通过HTTP (14)提交到AssistantHttpServlet,后者以两种方式响应,使用VoiceXML文档播放等待音乐(16),或使用空对话断开被叫方的呼叫。如果被叫方不希望与呼叫方讲话,则调用CallerValedictionTransfer对象,以对呼叫方播放结束消息(此流与图9相同)。否则,调用AssistedCall.transferCallerToCallee()方法(此方法依次使用以下描述的CallerCalleeTransfer对象),将呼叫方连接到被叫方。

  CallerCalleeTransfer

  图11图解了CallerCalleeTransfer类封装的流。

图 11. CallerCalleeTransfer封装的流

图 11. CallerCalleeTransfer封装的流

  如果被叫方选择连接呼叫方,则AssistedCall将BYE请求发送到合适的SIP会话(1,2,3,4),以终止到MRF的呼叫线路。使用原始呼叫方会话创建一个空reINVITE,以使其替换现有调用(5)。一旦200 OK响应被接收(包含SDP offer (6)),则细节会被复制到reINVITE请求(7),后者被发送给被叫方。被叫方使用具有其回答SDP的200 OK响应(8)进行响应,后者被复制到发给呼叫方的ACK消息(9)。ACK消息(10)被返回到被叫方。现在呼叫方和被叫方能够直接通信了。

结束语

  本文中,我们介绍了IMS架构模型,尤其是如何基于Java SIP servlet和VoiceXML技术为其编写应用程序。我们提供了一个完整的应用程序示例,以演示通用方法。由于VoiceXML语言轻松支持语音识别和语音合成,也能够用于视频交互式应用程序,所以可以轻松地在本文描述的框架基础上创建非常高级的交互式应用程序。Voxpilot MRF提供完整的VoiceXML2.1功能,支持所有一流的语音识别器和语音合成器,能够运行视频交互式应用程序,运行于配置了Windows或Linux操作系统的标准Intel服务器上。现在,通过结合SIP媒体网关(来自诸如Cisco、AudioCodes、Dilithium和Radvision之类的供应商),能够在Public Switched Telephone Network (PSTN)和3G网络上直接部署应用程序。

  Java SIP servlet和VoiceXML的Web编程模型的组合构成了一个功能异常强大的服务交付范例,此范例适于未来的IMS网络和当今的网络。快乐开发吧!

参考资料

  原文出处:http://dev2dev.bea.com/pub/a/2006/06/ims-sip-voicexml.html

 作者简介
David Burke 是Voxpilot的首席技术官,负责领导和实现整体技术方向和目标。Dave是W3C和IETF语音标准的编辑和投稿人,还经常在国际语音会议上发表演讲。
Darragh O'Flanagan 于2006年加入Voxpilot Ltd.,任高级软件工程师,在过去的六年间,他的工作涉及多种电信行业软件产品(包括固定和移动领域)。
dot dot dot

dot
  作者其它文章
您对本文的评价
您对这篇文章的看法如何?
太棒了!5分 不错啊 4分 一般般 3分 有待提高 2分 不好 1分

   
相关产品