商务参考体系结构:企业对消费者 第 7 章:ConsolidatedRetail.Com 的功能
Microsoft Corporation
2001年5月
摘要:本章介绍了 ConsolidatedRetail.com 应用程序的功能,同时提供了伪代码和实际代码示例来详细说明处理流程。
简介
现在,您对解决方案的各个组成部分已经有了总体了解,可以开始检查站点提供的具体功能了。本章探讨了解决方案的以下几个方面,并着重讨论了为每个功能区编写代码时所面临的内在问题:
表示服务
用户身份验证和用户配置文件的维护
产品目录
“购物篮”管理
订单处理
阅读本章的各个小节时,您将了解具体功能的实现方法,以及如何在您自己的企业对消费者 (B2C) 解决方案中重新利用 ConsolidatedRetail.com 应用程序的部分或全部内容。
表示服务
ConsolidatedRetail.com 站点中的表示服务由 XSLISAPI 过滤器提供。面临的主要开发问题在于:如何在每个 PASP 脚本中生成合适的 XML,以及如何创建合适的 XSL 样式表将 XML 表示为 HTML(或其它表示格式)。
ConsolidatedRetail.com 中来自 PASP 文件的 XML 输出
在 ConsolidatedRetail.com 站点中,每个 PASP 文件都生成格式相同的 XML 文档。这是通过在站点的每一页中加入对 Common.asp 文件的 Include 引用,并调用其 PageStart 过程实现的。
Common.asp 文件中的 PageStart 过程包含以下代码:
Sub PageStart(strPageNameWithExtension)
' 除去 pasp 文件扩展名
Dim strPageName
strPageName = Trim(Left(strPageNameWithExtension, _
InstrRev(strPageNameWithExtension, _
".") - 1))
'XML 标头
Response.Write _
"<?xml-stylesheet type=""text/xsl" _
" server-config=""" & _
strPageName & "-Config.xml"" href=""" & _
strPageName & "-IE5.xsl""?>"
'页元素的根。
'结束标记在“PageEnd”子例程中生成。
Response.Write "<page pagename="" _
" & strPageNameWithExtension & """>" & vbcrlf
End Sub
此过程为 PASP 文件将要生成的文档创建 XML 标头。在每个 PASP 脚本的末尾,将调用 PageEnd 过程来生成 <page> 的结束标记并完成 XML 文档。下面列出了 PageEnd 过程中的相关代码:
Sub PageEnd()
'为了表达得更为清楚,这里省略了有关目录、注销和错误消息的代码
Call XMLEndTag("page")
End Sub
XMLEndTag 过程是 Common.asp 中的众多 XML Helper 过程之一。(有关详细信息,请参考本章中的“XML Helper 过程”一节。)它使用以下代码生成 XML 结束标记:
Sub XMLEndTag(strTagName)
Response.Write mc_strStartTag & mc_strForwardSlashTag & _
Lcase (Replace(strTagName, mc_strBlank,_
mc_strUnderScore)) & _
mc_strEndTag & vbCrLf
End Sub
使用 PageStart 和 PageEnd 过程生成的 XML 文档类似于以下代码示例:
<?xml-stylesheet type="text/xsl"
server-config="filename-Config.xml"
href="filename-IE5.xsl"?>
<page pagename="filename.pasp">
<!-- XML 内容 -->
</page>
XML Helper 过程
除了上面提到的 XMLEndTag 过程之外,Common.asp 还包含多个与 XML 有关的实用程序例程。使用这些过程可以生成一些标记,这些标记将用于 ConsolidatedRetail.com 站点中的 PASP 页所生成的 XML 文档。
虽然在许多情况下,对所需的 XML 字符(例如 "<" 和 ">",请注意不包括引号)进行硬编码更为有效,但是,在各种 XML 生成过程中,大量 XML 字符常量均在 Common.asp 中进行声明。这样可增加代码的可读性,同时有助于调试。这些常量是:
Const mc_strStartTag = "<"
Const mc_strEndTag = ">"
Const mc_strForwardSlashTag = "/"
Const mc_strUnderScore = "_"
Const mc_strBlank = " "
以下 XML 生成过程使用了这些常量:
XMLBegTag:此实用程序根据 strTagName 参数编写一个 XML 开始标记(例如 <page>)。
XMLEndTag:此实用程序(在上文中已经做过介绍)根据 strTagName 参数编写一个结束标记(例如 </page>)。
XMLEmptyTag:此实用程序根据 strTagName 参数编写一个空 XML 标记(例如 <page/>)。
XMLTag:此实用程序根据 strTagName 和 strTagValue 参数编写一个包含值的 XML 标记(例如 <page>myvalue</page>)。
GetXMLFromRS:此实用程序创建记录集的 XML 表示。
GetXMLFromRSRow:此实用程序创建记录集中当前行的 XML 表示。
此外,还有许多其它过程(统称为 xxxWithDsplyNm)用于生成包含 displayname 属性的 XML 标记,例如:
<f_name displayname="First Name">Joe</f_name>
标记的显示名称基于标记名,从 CatalogDefinitionProperties 应用程序级字典对象变量中检索。
要全面了解 XML Helper 过程提供的功能,请查看 Common.asp 中的源代码。
ConsolidatedRetail.com 站点中的 XSL 样式表
如前所述,每页都有一个相关联的“文件名”-Config.xml 文件,该文件列出了要应用于每种客户端浏览器或设备类型的 XSL 样式表。在本实现方案中,只支持 Microsoft® Internet Explorer 5.5,尽管可以创建替代样式表并将添加到站点来解决这一问题。
每个 PASP 页的 XSL 文件被命名为“页名”-IE5.xsl(其中“页名”是 PASP 页的非版本特定名称),该文件包含该页数据专用的 XSL 代码。但是,为了使站点保持统一的外观,需要在一个单独的 XSL 文件(名为 UI_layout-IE5.xsl)中定义所有页的公共用户界面 (UI) 元素。该文件存储在 Include 目录下。每一页专用的 XSL 文件使用 XSL Include 指令将 UI_layout-IE5.xsl 中的表示逻辑合并到当前页的呈现形式中,如以下代码段所示:
<xsl:include href="include\UI_layout-IE5.xsl"/>
使用 UI_layout-IE5.xsl 中的模板
UI_layout-IE5.xsl 文件包含几个模板。page 模板将应用于每个 PASP 页所生成的 XML 文档中的 <page> 元素。该模板引用 Stylesheet.css 级联样式表,并创建一个包含五行的 HTML 表,总体页将基于该 HTML 表。系统调用 UI_layout-IE5.xsl 文件中的其它模板来填充这些行。
表的第一行包含对 pageheader 模板的调用(该模板在后面的脚本中定义)。该模板呈现页首标题,包括链接到“购物篮”、“配置文件”和“主页”的图象。
第二行用于创建距第三行的间距,而第三行包含对 main 模板的调用。该模板包含呈现页左侧菜单面板(包括搜索表单)所需的逻辑。main 模板依次调用 getCatalogsForUser 模板、Exceptions 模板以及 advertising 或 profilemenu 模板(这取决于是否存在 advertising 或 profilemenu XML 元素)。
表的第四行与第二行类似,用于创建距第五行的间距;第五行则调用了 pagefooter 模板。该模板呈现页的底部面板,包括版权声明。
UI_layout-IE5.xsl 中的 page 模板如以下代码所示。可在 UI_layout-IE5.xsl 中查看 pageheader、main、pagefooter 和其它用于呈现站点中的页的模板。
<?xml version="1.0" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="*|/">
<xsl:apply-templates/></xsl:template>
<xsl:template match="text()|@*"><xsl:value-of select="."/></xsl:template>
<xsl:template match="page">
<html>
<head>
<title>ConsolidatedRetail.com</title>
<link rel="stylesheet" type="text/css" href="stylesheet.css"/>
</head>
<body bgcolor="#ceb6d5" leftmargin="0" marginwidth="0" topmargin="0" marginheight="0" onload="Focus()">
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td>
<xsl:call-template name="pageheader"/>
</td>
</tr>
<tr>
<td height="10"/>
</tr>
<tr>
<td>
<xsl:call-template name="main"/>
</td>
</tr>
<tr>
<td height="10"/>
</tr>
<tr>
<td>
<xsl:call-template name="pagefooter"/>
</td>
</tr>
</table>
</body>
</html>
</xsl:template>
<!-- 为了表达得更为清楚,此处省略了其它模板的代码 -->
</xsl:stylesheet>
呈现 Index.pasp
您可以检查 Index.pasp 页,以查看站点中各个页之间的组合关系。Index.pasp 页是站点的默认页(即主页)。Index.pasp 包含执行以下任务的代码:
用值 Index.pasp 定义一个名为 mc_strPageName 常量。
调用 Common.asp 中的 PageStart 过程,以创建相应的 <page> 开始标记。
使用 XMLEmptyTag 过程创建空的 <advertising/> 标记。
调用 PageEnd 过程创建 </page> 结束标记。
以下代码执行上述任务:
<!--#include file = "include/Site_Const.asp" -->
<!--#include file = "include/Common.asp"-->
<%
Const mc_strPageName = "Index.pasp"
Sub Main()
Call PageStart(mc_strPageName)
XMLEmptyTag(mc_strAdvertisingMenu)
Call PageEnd()
End Sub
Call Main()
%>
此代码生成以下 XML 文档:
<?xml-stylesheet type="text/xsl"
server-config="Index-Config.xml"
href="Index-IE5.xsl"?>
<page pagename="Index.pasp">
<advertising/>
<getcatalogsforuser>
<selectiontitle>浏览目录:</selectiontitle>
<catalog>
<catalogname>书</catalogname>
</catalog>
<catalog>
<catalogname>硬件</catalogname>
</catalog>
</getcatalogsforuser>
<profile/>
<exceptions></exceptions>
</page>
这些 XML 代码将被传递到 XSLISAPI 过滤器,该过滤器确定在 Index-Config.xml 中指定相应的样式表。XSLISAPI 过滤器检查 HTTP 请求标头信息以确定浏览器的类型和版本。然后,对浏览器信息与 Index-Config.xml 中的相应样式表进行比较。如果未找到匹配条目,则应用默认样式表 (Index-IE5.xsl)。
Index-Config.xml 类似于以下代码:
<?xml version="1.0" ?>
<server-styles-config>
<!-- 对于 HDML 3.0 浏览器 -->
<device target-markup="HDML 3.0">
<stylesheet href="Index-HDML3.xsl"/>
</device>
<!-- 对于 WML 1.1 浏览器 -->
<device target-markup="WML1.1">
<stylesheet href="Index-WML11.xsl"/>
</device>
<!-- 对于 IE 4.0 浏览器 -->
<device browser="IE" version="4.0">
<stylesheet href="Index-IE5.xsl"/>
</device>
<!-- 对于 IE 5.0 浏览器 -->
<device browser="IE" version="5.0">
<stylesheet href="Index-IE5.xsl"/>
</device>
<!-- 对于 MME 浏览器 -->
<device browser="MME">
<stylesheet href="Index-WML11.xsl"/>
</device>
</server-styles-config>
从 Internet Explorer 5.0 浏览器收到请求时,使用 Index-IE5.xsl 样式表将页作为 HTML 呈现。(Index-Config.xml 文件中的其它条目只是作为例证;ConsolidatedRetail.com 站点中并不提供相应的样式表。)
Index-IE5.xsl 类似于以下代码:
<?xml version="1.0" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:include href="include\UI_layout-IE5.xsl"/>
<xsl:template match="*|/"><xsl:apply-templates/></xsl:template>
<xsl:template match="text()|@*"><xsl:value-of select="."/></xsl:template>
<xsl:template name="pageleft"/>
<xsl:template name="pagecenter">
<script language="JavaScript">
<![CDATA[
function Focus(){
document.formSearch.txtSearchPhrase.focus()
}
]]>
</script>
<table width="100%" cellpadding="0" cellspacing="0" border="0" bgcolor="#ffffff">
<tr>
<td width="11">
<img src="/china/msdn/images/spacer.gif" width="1" height="1" border="0"/>
</td>
<td valign="top" class="content-text">
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td valign="top" colspan="3" class="content-text">
<p class="headline-text-purple">欢迎来到 ConsolidatedRetail.com</p>
<p>欢迎来到本站点,您在这里可以方便地购买到所需的任何商品。</p>
<p align="center"><a href="Registration.pasp"><img src="/china/msdn/images/home_freeshipping.gif" width="271" height="46" vspace="5" border="0"/></a><br/><br/></p>
</td>
</tr>
<tr>
<td valign="top" class="content-text">
<p><b> </b>
<br/> <img src="/china/msdn/images/spacer.gif" width="200" height="1" border="0"/></p>
</td>
<td width="1" bgcolor="#ceb6d5">
<img src="/china/msdn/images/spacer.gif" width="1" height="1" border="0"/>
</td>
<td width="125" align="right" valign="top" class="content-text">
<img src="/china/msdn/images/giftregistries.gif" width="118" height="30" border="0"/><br/>
<p>在不久的将来就可实现此功能。</p>
</td>
</tr>
</table>
</td>
<td width="11">
<img src="/china/msdn/images/spacer.gif" width="1" height="1" border="0"/>
</td>
</tr>
</table>
</xsl:template>
<xsl:template name="pageright"/>
</xsl:stylesheet>
此样式表包含上文介绍的 UI_layout-IE5.xsl 样式表。UI_layout-IE5.xsl 样式表用于呈现页的公共 UI 元素。在此示例中,XML 文档包含 <advertising/> 标记。因此,UI_layout-IE5.xsl 也使用 advertising 模板来呈现页的右侧面板。所得到的索引页类似于图 7-1。
图 7-1:索引页
ConsolidatedRetail.com 站点按照上文所述的方法呈现 PASP 页。使用这种方法(在 PASP 文件中生成 XML 内容,在 XSL 样式表中定义表示形式),您可以轻松更改用于显示页面的样式表,而不会影响 PASP 文件中的业务逻辑。因此,这个解决方案十分灵活,便于重用。
用户身份验证和用户配置文件的维护
Microsoft® Commerce Server 2000 支持可扩展的配置文件系统,可以存储大量用户数据。该用户数据(或用户配置文件)可以包含收货人地址和联系人信息。客户可以使用这些配置文件来存储个人数据,其中包括收货人地址和联系人信息,这样他们就不必在每次访问站点时都要重新输入此类信息。公司则可以将配置文件信息用于商业分析和开展有针对性的广告宣传活动。
要创建和维护配置文件,用户必须登录到站点并在站点上注册。这将创建一个唯一用户 ID,用于标识用户以及从数据库中检索相应的配置文件信息。用户的 ID 存储在客户端浏览器上的 cookie 中。如果用户未登录,cookie 将包含与匿名用户相关的用户 ID,此时配置文件信息不可用。当用户登录时,将检索相应的用户 ID 并将其写入 cookie,这样,在后面的会话过程中就能够使用配置文件信息。
请注意,此站点要求将浏览器配置为允许 cookie。如果用户使用配置为允许每会话 cookie(即不存储到用户硬盘上的 cookie)的浏览器来访问此站点,而不存储 cookie,则用户将能够登录到站点,也能使用站点,但是不能将产品放到购物篮中。如果根本不允许 cookie,则用户将不能访问此站点。
用户注册
用户可以使用 Registration.pasp 页注册到此站点。使用 Registration-IE5.xsl 样式表呈现该页时,该页包含一个向自身发回数据的表单。Registration.pasp 页包含执行以下任务的代码:
检查表单中的数据以确定 Mode 和 ProcessAction 参数。Mode 参数用于指定注册完成后应将用户重定向到的页(默认值是 Acct.pasp)。ProcessAction 参数用于确定是显示注册表单以便允许用户注册,还是将表单发回以进行处理。
将注册数据传送到 PutUserObject,以便创建新的配置文件。PutUserObject 在 Profile.asp 头文件中定义。
将用户重定向到 Acct.pasp 页(或 Mode 参数所指定的其它页)。
Registration.pasp 页使用 MSCSAppFrameWork 应用程序级变量从查询字符串中检索 Mode 和 ProcessAction 值,如以下代码段所示:strPageMode = _
Application("MSCSAppFrameWork").RequestString( _
"Mode", Null, , , True, True, 0, Null)
strProcessingAction = _
Application("MSCSAppFrameWork").RequestString( _
"ProcessAction", Null, , , True, True, 0, Null)
Mode 通常为空,这表示用户已成功注册,应重定向到帐户管理页 (Acct.pasp)。在某些情况下,Mode 包含其它页的名称,应将用户重定向到该页。例如,如果匿名用户在注册前将产品添加到了购物篮,您可能希望在用户注册后将用户重定向到“结帐”页。
ProcessAction 参数用于确定用户是从站点中的另一页进入注册页的,还是从注册页本身进入的。如果是前一种情况,将会呈现注册表单;如果是后一种情况,则会使用注册表单的内容来注册用户。如果 ProcessAction 参数是 EditUserObject,则可以使用该页上表单中的数据来注册用户。
然后,脚本将检索表单数据并将数据传递到 PutUserObject 函数,该函数在 Profile.asp 头文件中定义。该头文件包含了管理用户配置文件的各种过程,站点中的各页将要用到这些过程。PutUserObject 函数用于添加或更新用户配置文件,并返回指示成功或失败的布尔值。如果成功创建了用户,则允许用户登录并将其重定向到另一页。如果失败(例如由于指定的用户名已存在),则重新显示 Registration.pasp 页,并显示错误消息指出问题所在。
代码调用 Common.asp 中的 GetUserID 函数来检索用户 ID。该函数用于更新现有用户,本章稍后将对其进行详细讨论。用于注册新用户的下一行重要代码将检查是否存在具有指定用户名的用户:Set objMSCSProfile = _
Application("MSCSProfileService").GetProfile(strUserName, _
_ mc_strUserObject, blnReturnCode)
If Not (objMSCSProfile Is Nothing) Then
Call AddException(m_varrExceptions, 1, _
"用户名已存在。", mc_strPageName)
Set objMSCSProfile = Nothing
如果尚未使用过该用户名,代码将生成一个 GUID(唯一用户 ID)并添加该用户,同时调用 ProfileService 对象的 CreateProfile 方法。来自注册表单的值将赋给配置文件:strUserID = GenerateGUID()
Set objMSCSProfile = _
Application("MSCSProfileService").CreateProfile( _
strUserName, mc_strUserObject)
objMSCSProfile.Fields(mc_strGeneralInfo).Value _
(mc_strUser_ID) = cstr(strUserID)
objMSCSProfile.Fields(mc_strAccountInfo).Value _
(mc_strAccount_Status) = CInt(1)
objMSCSProfile.Fields(mc_strGeneralInfo).Value _
("user_type") = strUserType
objMSCSProfile.Fields(mc_strGeneralInfo).Value _
(mc_strUser_Security_Password) = strPassword
该页剩余部分中的大多数代码用于更新现有用户对象。最后,更新配置文件对象,函数返回 True:objMSCSProfile.Update
Set objMSCSProfile = Nothing
PutUserObject = True
该函数运行完毕后,新用户就注册到了站点数据库。
用户注册后,Registration.pasp 将浏览器重定向到 Acct.pasp(如果 Mode 参数是 NULL)或 Mode 参数所指定的其它页:If blnRegistered Then
If IsNull(strPageMode) Then
Response.redirect "Acct.pasp?Mode=" & strPageMode
Else
Response.Redirect strPageMode & "?Mode=" & strPageMode
End If
End If
对用户进行身份验证
已注册的用户必须登录并通过身份验证,才能访问其配置文件信息。如上文中所述,Registration.pasp 中的代码将使新注册的用户自动登录。但是,用户再次登录时,必须提供用户名和口令才能通过身份验证。
注意: 在本应用程序示例中,登录详细信息是使用 HTTP 协议以纯文本格式传递的。但在实际站点中,应使用安全超文本传输协议 (HTTPS) 对安全凭据进行加密。
用户可以使用 Login.pasp 页登录。与 Registration.pasp 一样,此页向自身发送数据并使用 Profile.asp 的功能来处理数据。Login.pasp 页包含执行以下任务的代码:
检查表单中的数据以确定 Mode 和 ProcessAction 参数。Mode 参数用于指定登录完成后应将用户重定向到的页(默认值是 Acct.pasp)。ProcessAction 参数用于指定是显示用户凭据表单以便允许用户登录,还是将表单发回以进行处理。
从该表单检索用户凭据。
将凭据传递到 Profile.asp 中的 Login 函数。
将用户重定向到 Acct.pasp 页(或 Mode 参数所指定的其它页)。
Login.pasp 中最重要的代码是对 Profile.asp 中 Login 函数的调用:blnLoginSuccessful = Login(strUserName, strPassword)
Login 函数用于校验用户的用户名和口令,并以 cookie 的形式将身份验证单发送到客户端浏览器。该函数执行的第一个任务是使用以下代码创建并初始化 AuthManager 对象:Set objMSCSAuthMgr = _
Server.CreateObject(mc_strAuthManager)
Call objMSCSAuthMgr.Initialize _
(Application("MSCSDictConfig").s_SiteName)
然后,该代码将从前一登录中删除用户现有的所有身份验证单:If objMSCSAuthMgr.IsAuthenticated Then
call objMSCSAuthMgr.SetAuthTicket _
("", blnCookieSupport, _
Application("MSCSDictConfig")._
i_FormLoginTimeOut)
End If
检查并确保提供的用户名非空后,代码将使用应用程序级 MSCSProfileService 对象为具有指定名称的用户加载配置文件: Set objMSCSProfileObject = Application("MSCSProfileService").GetProfile(strUserName,
mc_strUserObject, blnReturnCode)
如果为已注册的用户找到了匹配的配置文件,则检索口令和用户 ID,并将口令与用户提供的口令进行比较: strProfilePassword = _
objMSCSProfileObject(mc_strGeneralInfo).Value _
(mc_strUser_Security_Password)
strUserID = objMSCSProfileObject(mc_strGeneralInfo). _
Value (mc_strUser_ID)
If CStr(strProfilePassword) = CStr(strPassword) Then
...
在以匿名方式浏览时能够将产品添加到购物篮,这是 ConsolidatedRetail.com 站点的设计特色之一。如果用户在登录前就将产品添加到了购物篮,则已给该用户签发了一张匿名配置文件单。AuthManager 对象现在要检索此单据,以便将匿名用户的购物篮内容传输到已通过身份验证的用户的购物篮: strProfileUserID = _
objMSCSAuthMgr.GetUserID(mc_bytProfileTicketType)
为用户签发一个新单据(通过身份验证的用户单据),将匿名 cookie 设置为空白值,从而将其删除: Call objMSCSAuthMgr.SetAuthTicket _
(strUserID, blnCookieSupport, _
Application("MSCSDictConfig"). _
i_FormLoginTimeOut)
Call objMSCSAuthMgr.SetUserID(mc_bytAuthTicketType, _
strUserID)
Call objMSCSAuthMgr.SetUserID(mc_bytProfileTicketType, "")
Call objMSCSAuthMgr.SetProfileTicket("", blnCookieSupport)
最后,传输匿名购物篮中的所有产品,并声明登录成功:If (Len(strProfileUserID) > 0) Then
' 从匿名会话获取配置文件
' 对象
Set objMSCSUnRegProfileObject = Application( _
"MSCSProfileService").GetProfilebykey( _
mc_strUser_ID, strProfileUserID, _
mc_strUserObject, blnReturnCode)
' 如果返回匹配的匿名配置文件
If Not (objMSCSUnRegProfileObject Is Nothing) Then
' 将购物篮内容从匿名 ID 传输到已注册的
' ID
Call MoveBasketItems(strProfileuserid, strUserID)
' 从配置文件存储区中删除匿名配置文件
Call Application("MSCSProfileService"). _
DeleteProfileByKey(mc_strUser_ID, _
strProfileUserID, mc_strUserObject)
End If
Set objMSCSUnRegProfileObject = Nothing
End if
' 返回表示成功登录的值
Logon = True
在之后的会话过程中,用户为每个请求提供身份验证 cookie,这样,代码就可以检索到用户的配置文件信息。
检索和更新配置文件信息
此站点允许用户在多个页上查看和编辑其配置文件信息。由于各页均使用类似的代码更新用户配置文件中的字段,因此本章只详细讨论 UserProfile.pasp 页。您也可以查看 EditAddressBook.pasp 和 ChangePasswd.pasp 中的代码,它们执行类似的功能。
UserProfile.pasp 页包含一个表单,用户可在该表单中查看和更改名字、姓氏、电子邮件地址和电话号码的配置文件值。其设计思路类似于上文所述的 Registration.pasp 页。UserProfile.pasp 页包含执行以下任务的代码:
检查 ProcessingAction 查询字符串的值。如果值为 EditUserObject,则该页已将表单内容发送给自身,必须更新配置文件。
从查询字符串中检索表单值。
将表单值传递到 Profile.asp 中的 PutUserObject 函数。
将配置文件值转换为 XML 格式。
如上文所述,PutUserObject 函数用于在收到用户名时注册新用户。如果未收到用户名,则该函数假定用户已存在并尝试更新现有配置文件中的数据。
要访问配置文件,PutUserObject 使用 Common.asp 中的 GetUserID 函数从身份验证单中检索当前用户的 ID,如下所示: Function GetUserID()
Dim objMSCSAuthMgr '身份验证管理器
Dim strUser_ID ' 从身份验证管理器中
' 检索到的用户 ID
' 将 AuthManager 对象实例化和初始化。
Set objMSCSAuthMgr = _
Server.CreateObject(mc_strAuthManager)
Call objMSCSAuthMgr.Initialize _
(Application("MSCSDictConfig").s_SiteName)
strUser_ID = Null
' 如果用户已通过身份验证且身份验证单未
' 超时
If objMSCSAuthMgr.IsAuthenticated Then
' 获取进行身份验证时所用的唯一登录 ID。
strUser_ID = _
objMSCSAuthMgr.GetUserID(mc_bytAuthTicketType)
' 否则,如果用户以匿名方式进行浏览
Else
' 获取配置文件用户 ID(即 GUID)。
' 如果 getuserid 方法返回空字符串
' 将该字符串转换为 Null
strUser_ID = _
objMSCSAuthMgr._
GetUserID(mc_bytProfileTicketType)
If not isNull(strUser_ID) Then
If len(trim(strUser_ID)) = 0 Then
strUser_ID = Null
End If
End If
End If
' 返回新的 GUID 或当前已通过身份验证的
' 用户名
GetUserID = strUser_ID
Set objMSCSAuthMgr = Nothing
End Function
为了检索用户配置文件,Profile.asp 中的 PutUserObject 函数代码使用了 ProfileService 对象的 GetProfileByKey 方法: Set objMSCSProfile = Application("MSCSProfileService").GetProfilebyKey( _
mc_strUser_ID, strUserID, mc_strUserObject, blnReturnCode)
最后,代码更新该用户的配置文件字段:objMSCSProfile.Fields(mc_strGeneralInfo).Value( _
mc_strFirst_Name) = strFirstName
objMSCSProfile.Fields(mc_strGeneralInfo).Value( _
mc_strLast_Name) = strLastName
objMSCSProfile.Fields(mc_strGeneralInfo).Value( _
mc_strEmail_Address)= strEmailAddress
objMSCSProfile.Fields(mc_strGeneralInfo).Value( _
mc_strTel_Number) = strTelNumber
objMSCSProfile.Fields(mc_strGeneralInfo).Value( _
mc_strWork_Number) = strWorkNumber
objMSCSProfile.Fields(mc_strGeneralInfo).Value( _
mc_strWork_Extension) = strWorkExtension
objMSCSProfile.Fields(mc_strProfileSystem).Value( _
mc_strDate_Last_Changed) = Now
objMSCSProfile.Update
更新配置文件后,UserProfile.pasp 中的代码调用 GetUserObjectXML 过程,将整个用户配置文件作为 XML 呈现(实际上只显示 UserProfile-IE5.xsl 中引用的字段,但是可以在不更改代码的情况下,对该样式表进行修改以显示其它配置文件信息)。GetUserObjectXML 过程使用 ProfileService 对象的 GetProfileByKey 方法来检索用户的配置文件。然后,使用 Include/common.asp 中的 XML Helper 例程呈现数据。GetUserObjectXML 例程类似于以下代码:Sub GetUserObjectXML()
Dim objMSCSProfile
Dim strUserID
Dim Field
Dim Group
Dim blnReturnCode
Const c_strProfile = "userobject"
strUserID = GetUserID
If Not IsNull(strUserID) Then
' 使用指定架构初始化配置文件服务并连接到配置文件存储区
If Not Application("MSCSProfileService") Is Nothing Then
' 使用配置文件服务检索用户的配置文件对象,并将该对象指派
' 给函数的返回值
Set objMSCSProfile = Application("MSCSProfileService") _
.GetProfilebyKey(mc_strUser_ID, GetUserID, _
mc_strUserObject, blnReturnCode)
If Not objMSCSProfile Is Nothing Then
Call XMLBegTag(c_strProfile)
For Each Group In objMSCSProfile.Fields
Call XMLBegTag(Group.Name)
For Each Field In Group.value
Call XMLTag(Field.Name, Field.Value)
Next
Call XMLEndTag(Group.Name)
Next
Call XMLEndTag(c_strProfile)
End If
Set objMSCSProfile = Nothing
End If
End If
End Sub
此过程将生成以下 XML 输出:
<?xml-stylesheet type="text/xsl"
server-config="UserProfile-Config.xml"
href="UserProfile-IE5.xsl"?>
<page pagename="UserProfile.pasp">
<profilemenu/>
<pagemode/>
<userobject>
<accountinfo>
<org_id/>
<account_status>1</account_status>
<user_catalog_set/>
<date_registered/>
</accountinfo>
<advertising>
<campaign_history/>
</advertising>
<businessdesk>
<partner_desk_role/>
</businessdesk>
<generalinfo>
<user_id>
{8A7D56E9-DABA-499A-96B8-1F8DD93D032B}
</user_id>
<logon_name>Kim</logon_name>
<user_security_password>
password
</user_security_password>
<email_address>Kim@somecompany.com</email_address>
<user_type>1</user_type>
<user_title/>
<last_name></last_name>
<first_name></first_name>
<tel_number></tel_number>
<tel_extension/>
<fax_number></fax_number>
<fax_extension></fax_extension>
<user_id_changed_by/>
</generalinfo>
<profilesystem>
<date_last_changed>
2/8/2001 11:57:13 PM
</date_last_changed>
<date_created>2/8/2001 11:57:13 PM</date_created>
</profilesystem>
</userobject>
<!—该页的其余内容由 Common.asp 生成 -->
<getcatalogsforuser>
<selectiontitle>浏览目录:</selectiontitle>
<catalog>
<catalogname>书籍</catalogname>
</catalog>
<catalog>
<catalogname>硬件</catalogname>
</catalog>
</getcatalogsforuser>
<profile auth="auth"/>
<exceptions></exceptions>
</page>
ConsolidatedRetail.com 站点提供了有效的配置文件功能,并演示了创建、检索和更新用户配置文件信息的基本方法。使用 Commerce Server Management Desk,您可以扩展此功能以创建自定义配置文件属性,并且可以使用配置文件信息为各个用户提供站点个性化服务。有关使用 Commerce Server 2000 配置文件功能的详细信息,请参考 Commerce Server 2000 文档。
产品目录
为用户提供浏览产品目录的简单方法,是设计电子商务站点时最重要的设计目标之一。ConsolidatedRetail.com 站点通过以下三种方式实现这一目标:
目录始终在用户界面中列出。
用户可以通过分层的类别结构进行浏览。
用户可以在目录中搜索特定的字符串。
当前用户相关目录集中的目录始终列在用户界面的左侧窗格中。这是通过在 Common.asp 的 PageEnd 过程中加入确定并显示可用目录的代码来实现的:
该过程创建并初始化 CatalogSet 对象,然后调用其 GetCatalogsForUser 方法,传递 MSCSProfileService 应用程序级变量引用的 ProfileService 对象、当前用户(可能是匿名用户)的 ID 以及要使用的默认目录集名(如果未给用户指派特定目录集): Set objCatalogSets = _
Server.CreateObject(mc_strCatalogSets)
Call objCatalogSets.Initialize _
(Application("MSCSDictConfig"). _
s_CatalogConnectionString, _
Application("MSCSDictConfig")._
s_TransactionsConnectionString)
Set rsCatalogs = objCatalogSets.GetCatalogsForUser( _
Application("MSCSProfileService"), GetUserID & "", _
GetDefaultCatalogSet)
这将返回一个 ADO 记录集对象,可以使用标准记录集编程技术(例如检查文件结束 (EOF) 标记和使用 MoveNext 方法)浏览此对象: Do While Not rsCatalogs.Eof
Call XMLBegTag(c_strCatalog)
Call getXMLFromRSwithdsplynm(rsCatalogs)
Call XMLEndTag(c_strCatalog)
rsCatalogs.MoveNext
Loop
使用 UI_layout-IE5.xsl 样式表呈现该代码生成的 XML 时,所得到的网页将目录集中每个目录的名称显示为到 Category.pasp 的链接。生成的 XML 段类似于以下内容: <catalog>
<catalogname>书籍</catalogname>
</catalog>
<catalog>
<catalogname>硬件</catalogname>
</catalog>
目录浏览功能
ConsolidatedRetail.com 解决方案中的目录是以分层结构实现的。“书籍”和“硬件”这两个目录分别包含若干类别。“书籍”目录还包含一个子类别层。产品可以存储在目录的任何一层上。
ConsolidatedRetail.com 站点中用于浏览目录数据的页是 Category.pasp。可以在两种模式下使用该页:根级模式或类别模式。在根级模式下,该页从指定目录的根来检索产品和类别。在类别模式下,该页从指定的类别来检索产品、子类别和父类别。
该页包含执行以下任务的代码:
使用 MSCSAppFrameWork 对象检索请求字符串中传递的 txtCatalog 和 txtCategory 值。如果未找到 txtCatalog 值,则该页将用户重定向到 Index.pasp。
调用 PageStart 生成页的 XML 标头。
从 MSCSCatalogManager 应用程序变量检索指定目录的 ProductCatalog 对象,并将目录名写入 <searchscope> XML 元素。
将目录属性作为 XML 呈现。这样,可以在用户界面中呈现诸如目录名这样的属性。
确定是否在请求字符串中传递类别名。如果未指定类别名,则该页从目录的根来检索类别和产品并将其转换为 XML 格式。如果提供了类别,则该页从提供的类别来检索数据并将数据转换为 XML 格式。
调用 PageEnd 过程来关闭 XML 文档。
在使用 Commerce Server 目录对象检索目录信息前,该页使用以下代码来创建 <searchscope> 元素:
Call XMLTag(c_strSearchScope, strCatalogName)
UI_layout-IE5.xsl 样式表使用此元素将当前目录名传递到搜索功能,然后限定搜索范围。(本章稍后将对搜索功能进行详细讨论。)
实际目录数据是使用 Commerce Server 自动化对象的分层结构进行检索的。分层结构的顶层是 CatalogManager 对象,用于对目录系统进行所有程序访问。CatalogManager 对象包含一些 ProductCatalog 对象,这些对象代表站点中的目录。在 Category.pasp 中,MSCSCatalogManager 应用程序级变量的 GetCatalog 方法用于检索指定的目录,如以下代码段所示:
Set objMSCSPrdCat = Application("MSCSCatalogManager"). _
GetCatalog(strCatalogName)
使用 GetCatalogAttributes 方法,可以将 ProductCatalog 对象的属性作为 ADO 记录集进行检索。Category.pasp 中的代码使用此方法将记录集中的每一行传递到 Common.asp 中的 GetXMLFromRSWithDsplyNm 例程,该例程将行转换为 XML 格式:
Set rsProperties = _
objMSCSPrdCat.GetCatalogAttributes
If Not (rsProperties.Eof And rsProperties.Bof) Then
Call XMLBegTag(c_strGetCatalogAttributes)
Do While Not rsProperties.Eof
'获取记录集行的 xml 版本
Call GetXMLFromRSWithDsplyNm(rsProperties)
rsProperties.MoveNext
Loop
Call XMLEndTag(c_strGetCatalogAttributes)
End If
这将生成以下格式的 XML 代码段:
<getcatalogattributes>
<catalogname>书籍</catalogname>
<locale>8</locale>
<startdate>12/8/1999</startdate>
<enddate>12/8/2006</enddate>
<variantid>ISBN</variantid>
<productid>标题</productid>
<currency>USD</currency>
<weightmeasure>lbs</weightmeasure>
<catalogid>1</catalogid>
<customcatalog>False</customcatalog>
<freetextindexcreated>2/8/2001 11:56:22 PM</freetextindexcreated>
<producttableupdated>2/8/2001 11:53:37 PM</producttableupdated>
</getcatalogattributes>
也可以使用记录集对象来代表目录根中的类别。RootCategories 方法用于从 Category.pasp 中检索这些类别,如以下代码段所示(请注意,Fields 集合属性用于从记录集中检索指定的数据字段):
Set rsCategories = objMSCSPrdCat.RootCategories
'为了表达得更为清楚,此处省略了一些代码
Do While Not rsCategories.Eof
Call XMLBegTag(c_strRootCategory)
Call XMLTagWithDsplyNm("catalogname", objMSCSPrdCat.catalogname)
Call XMLTagWithDsplyNm("categoryname", _
rsCategories.fields("CategoryName").Value)
Call XMLEndTag(c_strRootCategory)
rsCategories.MoveNext
Loop
这将生成一个 XML 代码段,如下所示:
<rootcategory>
<catalogname>书籍</catalogname>
<categoryname>商业软件</categoryname>
</rootcategory>
<rootcategory>
<catalogname>书籍</catalogname>
<categoryname>开发工具</categoryname>
</rootcategory>
<rootcategory>
<catalogname>书籍</catalogname>
<categoryname>特色产品</categoryname>
</rootcategory>
...
与之类似,对于目录根中的产品,可以使用 RootProducts 方法对包含这些产品的记录集进行检索:
Set rsProducts = objMSCSPrdCat.RootProducts
为 rsProducts 记录集中的每个产品生成的 XML 类似于以下代码:
<book>
<oid>64</oid>
<definitionname>SDKBook</definitionname>
<cy_list_price displayname="价格">19.99</cy_list_price>
<originalprice displayname="购买价">19.99</originalprice>
<i_classtype>4</i_classtype>
<parentoid>-1</parentoid>
<productid>
Microsoft Age of Empires II: The Age of Kings: Inside Moves
</productid>
<variantid/>
<title displayname="标题">Microsoft Age of Empires II: The Age of Kings: Inside Moves</title>
<isbn displayname="ISBN">0-7356-0513-0</isbn>
<description>在令人激动的新版 Microsoft Age of Empires 中,您将掌握能够帮助您夺取胜利的所有关键性战略策略、技巧和计谋!MICROSOFT AGE OF EMPIRES II: AGE OF KINGS: INSIDE MOVES 向您展示在从罗马帝国灭亡到中世纪的数千年中如何为生存、富强而奋斗。</description>
<image_filename>boxshots/press/2388.gif</image_filename>
<image_height>120</image_height>
<image_width>120</image_width>
<author displayname="作者">Microsoft Corporation</author>
<name displayname="名称">Microsoft Age of Empires II: The Age of Kings: Inside Moves</name>
<pagecount displayname="页数">280</pagecount>
<producturl displayname="产品信息Url">a href=http://mspress.microsoft.com/prod/books/2388.htm target =_a http://mspress.microsoft.com/prod/books/2388.htm /a</producturl>
<publication_year displayname="出版年份">1999</publication_year>
<publisher displayname="出版者">Microsoft Press</publisher>
<reading_level displayname="阅读级别:">所有级别</reading_level>
<catalogname>书籍</catalogname>
</book>
如果代码需要下钻到更深的目录并检索其中某个类别的内容,可以使用 Category 对象。Category.pasp 页使用 Category 对象访问指定类别的内容。该对象使用 ProductCatalog 对象的 GetCategory 方法进行实例化,如以下代码段中所示:
Set objMSCSCategory = _
objMSCSPrdCat.GetCategory(strCategoryName)
您可以使用 Products 属性将类别中的产品作为记录集检索:
Set rsProducts = objMSCSCategory.products
Category 对象还提供 ChildCategories 属性来检索子类别的记录集,提供 ParentCategories 属性返回父类别的记录集。
无论它们在分层结构中的位置如何,您都可以使用 Commerce Server Business Desk 来创建产品目录中类别和产品间的关系。Category 对象提供 RelatedCategories 和 RelatedProducts 属性来检索包含相应内容的记录集。您在 Category.pasp 中可以看到有关如何使用这些对象的示例。
查看产品
用于呈现目录数据的 Category-IE5.xsl 样式表会生成 HTML,这样,在用户单击特定产品的“获取详细资料”链接时,将请求 Product.pasp 页。该页将显示所选产品的特定数据。
Product.pasp 中的代码开头部分与 Category.pasp 很相似。该代码在请求字符串中检索目录、产品 ID 和可选的产品变量值。如果没有目录或产品 ID 参数,则将用户重定向到 Index.pasp。然后,代码调用 PageStart 开始为该页生成 XML。
使用 GetProduct 方法从目录对象检索 Commerce Server 产品对象时,代码变得有趣起来:
Set objMSCSPrd = objMSCSPrdCat.GetProduct(strProductID)
您可以使用 GetProductProperties 方法在记录集对象中检索产品的一些属性,如产品名和价格:
Set rsProduct = objMSCSPrd.GetProductProperties
Commerce Server 目录中的产品支持变体(如颜色或大小不同的产品)。您可以使用 Variants 属性将特定产品的变量列表作为记录集检索。此外,还可以使用 RelatedProducts 和 RelatedCategories 属性来检索任何相关的产品或类别。利用这些属性可以创建链接,获得交叉销售机会。Product.pasp 页中使用所有这些属性来为产品生成 XML 数据,下面列出了一段 XML 代码。然后使用 Product-IE5.xsl 样式表来呈现这些数据。
<getproduct>
<book>
<catalogname>书籍</catalogname>
<definitionname>SDKBook</definitionname>
<cy_list_price displayname="价格">19.99</cy_list_price>
<originalprice displayname="购买价">19.99</originalprice>
<i_classtype>4</i_classtype>
<productid>Microsoft Age of Empires II: The Age of Kings: Inside Moves</productid>
<variantid/>
<author displayname="作者">Microsoft Corporation</author>
<description>在令人激动的新版 Microsoft Age of Empires 中,您将掌握能够帮助您夺取胜利的所有关键性战略策略、技巧和计谋!MICROSOFT AGE OF EMPIRES II: AGE OF KINGS: INSIDE MOVES 向您展示在从罗马帝国灭亡到中世纪的数千年中如何为生存、富强而奋斗。</description>
<image_filename>boxshots/press/2388.gif</image_filename>
<image_height>120</image_height>
<image_width>120</image_width>
<isbn displayname="ISBN">0-7356-0513-0</isbn>
<name displayname="名称">Microsoft Age of Empires II: The Age of Kings: Inside Moves</name>
<pagecount displayname="页数">280</pagecount>
<producturl displayname="Product Info. Url">a href=http://mspress.microsoft.com/prod/books/2388.htm target =_a http://mspress.microsoft.com/prod/books/2388.htm /a</producturl>
<publication_year displayname="出版年份">1999</publication_year>
<publisher displayname="出版者">Microsoft Press</publisher>
<reading_level displayname="阅读级别:">所有级别</reading_level>
<title displayname="标题">Microsoft Age of Empires II: The Age of Kings: Inside Moves</title>
</book>
</getproduct>
搜索目录
除了提供用户用于浏览目录的页面之外,一个高效的站点还应提供搜索功能。在 ConsolidatedRetail.com 站点中,用户可以输入搜索标准,在目录中搜索特定产品。
使用 Commerce Server 2000 中的搜索功能可以在当前目录中进行搜索,也可以在基于当前用户的目录集中进行搜索。搜索功能是在 SearchResults.pasp 中实现的,SearchResults.pasp 包含执行以下任务的代码:
从查询字符串中检索搜索短语、目录、要返回的行数和开始位置参数(根据需要为返回的行数和开始位置参数赋予默认值,并在未提供搜索短语的情况下产生异常)。
如果未指定目录,则检索基于当前用户配置文件的目录集。
在指定目录或用户目录集的目录列表中搜索指定的搜索短语。
将搜索结果作为 XML 呈现。
搜索目录的实际代码使用应用程序级 MSCSCatalogManager 对象的 FreeTextSearch 方法来检索记录集中的搜索结果。此方法接受以下参数:
搜索短语
要搜索的目录列表(以逗号分隔)
应返回的属性列表(以逗号分隔)
作为结果排序依据的属性列表
表示按升序进行排序的布尔值
开始进行搜索的记录号
要返回的号码或行数
输出参数,表示实际返回的总行数
如果用户未指定目录,将把当前用户的默认目录集作为记录集进行检索,并将每个目录名连接成以逗号分隔的字符串:
Set objCatalogSets = Server.CreateObject(mc_strCatalogSets)
Call objCatalogSets.Initialize _
(Application("MSCSDictConfig").s_CatalogConnectionString, _
Application("MSCSDictConfig").s_TransactionsConnectionString)
Set rsCatalogs = objCatalogSets.GetCatalogsForUser _
(Application("MSCSProfileService"), GetUserID & "", _
GetDefaultCatalogSet)
strCatalogsToSearch = ""
If Not (rsCatalogs.EOF And rsCatalogs.BOF) Then
Do While Not rsCatalogs.EOF
strCatalogsToSearch = strCatalogsToSearch & "," & _
rsCatalogs.Fields("CatalogName").Value & ""
rsCatalogs.MoveNext
Loop
strCatalogsToSearch = Trim(Mid(strCatalogsToSearch, 2))
End If
或者,如果指定了目录,只需将目录名赋给 strCatalogsToSearch 变量即可:
strCatalogsToSearch = strCatalogName
最后,调用 FreeTextSearch 方法:
Set rsProducts = _
Application("MSCSCatalogManager").FreeTextSearch _
(strSearchPhase, strCatalogsToSearch, , _
"CatalogName, CategoryName, DefinitionName, _
OriginalPrice, cy_list_price, i_ClassType,
ProductID, Description, image_filename, _
image_width, image_height, Name", _
"i_ClassType, CatalogName", _
True, _
lngSearchStartPos, _
lngSearchRowToReturn, _
lngTotalRecordsInQuery)
该页上其余的代码只是将 FreeTextSearch 返回的记录集中的行转换为 XML 格式,这样,XSLISAPI 应用程序就可以呈现搜索结果,以便进行显示。为搜索结果生成的 XML 具有以下格式:
<searchscope>书籍</searchscope>
<searchstring>Age of Empires</searchstring>
<searchcount>4</searchcount>
<searchrowstoreturn>15</searchrowstoreturn>
<searchstartpos>1</searchstartpos>
<searchresults>
<book>
<catalogname>书籍</catalogname>
<definitionname>SDKBook</definitionname>
<originalprice displayname="购买价">19.99</originalprice>
<cy_list_price displayname="价格">19.99</cy_list_price>
<i_classtype>4</i_classtype>
<productid>Microsoft Age of Empires II: The Age of Kings: Inside Moves</productid>
<description>在令人激动的新版 Microsoft Age of Empires 中,您将掌握能够帮助您夺取胜利的所有关键性战略策略、技巧和计谋!MICROSOFT AGE OF EMPIRES II: AGE OF KINGS: INSIDE MOVES 向您展示在从罗马帝国灭亡到中世纪的数千年中如何为生存、富强而奋斗。</description>
<image_filename>boxshots/press/2388.gif</image_filename>
<image_width>120</image_width>
<image_height>120</image_height>
<name displayname="名称">Microsoft Age of Empires II: The Age of Kings: Inside Moves</name>
</book>
<!-- 为了表达得更为清楚,此处省略了其它结果 -->
<selectiontitle>根据搜索标准 'Age of Empires' 找到的产品。</selectiontitle>
</searchresults>
“购物篮”管理
与大多数 B2C 站点一样,ConsolidatedRetail.com 解决方案使用购物车或购物篮的概念将用户选定要购买的产品集中在一起。无论是已登录的用户还是匿名用户,ConsolidatedRetail.com 都允许他们向购物篮添加产品;但是匿名用户在结帐前必须登录。
向购物篮中添加产品
当用户在 Product.pasp 页上单击“添加到购物车”链接时,会将产品和数量信息发送给 _additem.asp 页。在将用户重定向到显示购物篮内容的 Basket.pasp 页之前,该页包含执行以下任务的代码:
检索查询字符串中记录的类别、产品、变量和目录值。
校验在查询字符串中传递的数量值(如果未传递数量,则添加项目的 1 个实例)。
将用户的购物篮载入一个 OrderGroup 对象。
如果产品已在购物篮中列出,则将指定的数量添加到现有条目;否则为此产品创建新条目。
将用户重定向到 Basket.pasp。
_additem.asp 页调用 Basket.asp 头文件中的 LoadBasket 函数来检索包含当前用户购物篮的 OrderGroup 对象。LoadBasket 函数类似于以下代码:
Function LoadBasket(strUserID)
Dim objMSCSOrderGroup
Set objMSCSOrderGroup = Server.CreateObject( _
mc_strOrderGroup)
Call objMSCSOrderGroup.Initialize(Application( _
"MSCSDictConfig").s_TransactionsConnectionString, _
strUserID)
Call objMSCSOrderGroup.LoadBasket()
Set LoadBasket = objMSCSOrderGroup
Set objMSCSOrderGroup = Nothing
End Function
请注意:OrderGroup 对象是通过传递 MSCSDictConfig 应用程序变量中定义的事务连接字符串和当前用户的用户 ID 来创建和初始化的。(用户 ID 取自 Profile.asp 头文件中的 GetGuaranteedUserID 函数。对于已登录的用户,将返回通过身份验证的用户 ID,对于匿名用户,则返回配置文件用户 ID。)
然后,调用 OrderGroup 对象的 LoadBasket 方法来检索与当前用户相关联的购物篮内容。
加载购物篮后,_additem.asp 中的代码搜索购物篮中的明细项目,查看是否列出了请求的产品。如果产品已在购物篮中,则在订单上增加指定的数量,如下所示:
blnSkuMatched = False
'如果明细项目存在
If objMSCSOrderGroup.Value("total_lineitems") > 0 Then
'循环搜索每个明细项目,查找匹配项
For Each colItem in objMSCSOrderGroup.Value _
("OrderForms").Value("default").Items
If IsNull(strVariantID) Then
If (Trim(Cstr(colItem.product_id)) = _
Trim(Cstr(strProductID))) And _
IsNull(colItem.product_variant_id) Then
blnSkuMatched = True
Exit For
End If
Else
If (Trim(Cstr(colItem.product_id)) = _
Trim(Cstr(strProductID))) And _
Trim(Cstr(colItem. _
product_variant_id)) = _
Trim(Cstr(strVariantID))) Then
blnSkuMatched = True
Exit For
End If
End If
Next
If blnSkuMatched Then
' 如果项目已在购物篮中,则将新的项目数量加到
' 现有项目数量上
colItem.Quantity = colItem.Quantity + intProductQty
'保存新的购物篮
Call objMSCSOrderGroup.SaveAsBasket()
...
如果产品未在购物篮中列出,则代码调用 AddItemToBasket 局部函数,将其添加到购物篮中。AddItemToBasket 函数创建一个字典对象来表示该项目,并使用 OrderGroup 对象的 AddItem 方法将该项目添加到购物篮中。然后,使用 SaveAsBasket 方法保存购物篮。AddItemToBasket 函数的代码类似于以下代码:
Function AddItemToBasket(objMSCSOrderGroup, _
strProductID, _
strCatalogName, intProductQty, _
strVariantID, strCategoryName)
Dim objMSCSProductDictionary
'创建产品字典
Set objMSCSProductDictionary = _
Server.CreateObject(mc_strDictionary)
objMSCSProductDictionary.lineitem_uid = GenerateGUID()
objMSCSProductDictionary.product_id = strProductID
objMSCSProductDictionary.product_catalog = strCatalogName
objMSCSProductDictionary.Quantity = intProductQty
If Not IsNull(strVariantID) Then
objMSCSProductDictionary.product_variant_id = _
strVariantID
End If
If Not IsNull(strCategoryName) Then
objMSCSProductDictionary.product_category = _
strCategoryName
End If
Call objMSCSOrderGroup.AddItem(objMSCSProductDictionary)
Call objMSCSOrderGroup.SaveAsBasket()
Set objMSCSProductDictionary = Nothing
End Function
请注意:添加项目是通过创建一个表示该项目的字典对象,然后将该项目传递给 OrderGroup(表示购物篮)的 AddItem 方法来实现的。
更新购物篮后,_additem.asp 中的代码将用户重定向到 Basket.pasp。
查看购物篮
用户可以使用 Basket.pasp 页查看和编辑购物篮的内容。Basket.pasp 包含执行以下任务的代码:
检查购物篮的完整性。
检索用户的用户 ID(从身份验证单或匿名配置文件单中检索)。
如果用户的购物篮非空,则使用 PAGBasket 管道来检索要显示的购物篮信息。
将明细项目总计数写入 <totallineitems> XML 标记。
使用 Commerce Server DictionaryXMLTransforms 对象将购物篮内容转换为 XML,并将其写入响应。
该页通过使用以下代码加载用户的购物篮并检查购物篮中是否包含产品:
Set objMSCSOrderGroup = LoadBasket(strUserID)
'检查购物篮中是否包含项目
If Not IsBasketEmpty(objMSCSOrderGroup) Then
blnBasketIsEmpty = False
End If
Include/basket.asp 中的 IsBasketEmpty 函数检查 OrderGroup 中是否包含项目。如果购物篮包含产品,则将购物篮传递给 PAGBasket 管道以便为显示做准备:
Set objMSCSPipelines = Application("MSCSPipelines")
intErrorLevel = _
RunMtsPipeline(objMSCSPipelines.PAGBasket, _
objMSCSPipelines.LogFolder & strUserID & _
".log", _
objMSCSOrderGroup)
PAGBasket 管道
管道用于配置一系列组件,这些组件将以固定顺序对一个业务对象进行操作。管道的操作分为几个“阶段”。在本示例中,PAGBasket 管道包含的组件对 OrderGroup 对象进行操作,该对象表示用户的购物篮。您可以使用 Commerce Server 管道编辑器来查看 PAGBasket 管道的配置,只需要打开站点上“管道”文件夹中的 PAGBasket.pcf 文件即可。PagBasket 管道如图 7-2 所示。
图 7-2:PAGBasket 管道
每次显示购物篮时,PAGBasket 管道收集显示购物篮所需的所有数据并进行必要的计算。除了在显示购物篮之前运行该管道之外,在结帐过程的最后阶段也要运行它,以便进行必要的计算来创建最终订单总计。
产品信息阶段
该管道从“产品信息”阶段开始执行。此阶段用于管理产品信息,涉及以下两个组件:
QueryCatalogInfo:QueryCatalogInfo 组件为订单中的每个项目从目录系统中检索产品信息。它将检索到的信息添加到订单表单中的每个项目字典。
RequiredProdInfo:RequiredProdInfo 组件检查 OrderForm 中 items 集合的所有项目,并删除 delete 键设置为 1 的所有项目。
订单初始化阶段
然后进入“订单初始化”阶段,初始化 OrderGroup 中的相应值。此阶段只涉及一个组件:RequiredOrderInitCy。首先,RequiredOrderInitCy 确保 order_id 键有值。如果没有值,RequiredOrderInitCy 将生成一个唯一订单 ID,并将其赋给此键。然后,为了确保订单完整性,该组件将把 NULL 值赋给各 total 键,将其初始化。最后,对于 items 集合中的每个项目,RequiredOrderInitCy 将存储在 quantity 中的值复制到 _n_unadjusted 键,初始化未打折的项目数量,并将 _oadjust_adjustedprice (项目的总费用)初始化为零。
订单检查阶段
“订单检查”阶段校验显示的订单是否有效以及是否包含后续处理所需的所有条目。此阶段只涉及一个组件:RequiredOrderCheck。RequiredOrderCheck 组件确保 OrderForm 的项目列表不为空。
项目定价阶段
“项目定价”阶段为订单表单中的每个项目设置 _iadjust_regularprice。此阶段涉及以下两个组件:
DefaultItemPriceCy:对于订单表单中的每个项目(即 items 集合中的每个项目),DefaultItemPriceCy 将 _iadjust_regularprice 键指派给 the _cy_product_list_price 中存储的值。管道中“项目定价”阶段后面的各个阶段所包含的一些组件依赖于所设置的 _iadjust_regularprice 键。如果未给此键赋值,则这些组件将失败。
RequiredItemPriceCy:RequiredItemPriceCy 组件校验是否为项目列表中的每个项目设置了 _cy_iadjust_regularprice。在运行 RequiredItemPrice 组件前,应使用 DefaultItemPriceCy 组件将项目列表初始化,以便包含项目的最新定价信息。如果 _cy_iadjust_regularprice 不包含值,将产生错误。
项目调价阶段
“项目调价”阶段为订单表单中的每个项目设置 _iadjust_currentprice。此阶段只涉及一个组件:RequiredItemAdjustPriceCy。RequiredItemAdjustPriceCy 校验项目列表中每个项目的当前价格 (_cy_iadjust_currentprice) 是否存在。如果此值不存在,则该组件创建它并将它初始化为常规价格 (_cy_iadjust_regularprice)。此外,该组件对照当前价格 (cy_iadjust_currentprice) 来检查放置价格 (cy_placed_price),看看自用户将产品放入购物篮后当前价格是否发生了更改。如果放置价格不存在,该组件创建它并将它设置为当前价格 (cy_iadjust_currentprice)。如果放置价格存在但是不等于当前价格,则 RequiredItemAdjustPriceCy 从 MessageManager 中检索无效放置价格的警告消息文本,并将消息写入订单表单中的 _Basket_Errors 集合。
订单调价阶段
“订单调价”阶段为订单表单中的每个项目设置 _oadjust_adjustedprice,以便将价格折扣的因素考虑进去。此阶段只涉及以下一个组件: RequiredOrderAdjustPriceCy:对于每个项目,RequiredOrderAdjustPriceCy 首先计算不打折数量的费用。方法是:将项目的当前价格 (_cy_iadjust_currentprice) 与项目不打折的数量 (n_unadjusted) 相乘,然后将乘积加到项目的总费用 (_cy_oadjust_adjustedprice) 上。然后,计算项目折扣 (cy_oadjust_discount)。方法是:用项目的当前价格 (_cy_iadjust_currentprice) 乘以总数量 (quantity) 得出项目的总费用,然后减去以前算出的不打折的项目费用,这样就计算出了项目折扣。
订单小计阶段
“订单小计”阶段在订单表单上设置 _oadjust_subtotal。此阶段涉及以下三个组件:
DefaultOrderSubTotalCy:将 OrderForm 中 SimpleList 项目中的每个项目总费用 (_cy_oadjust_adjustedprice) 加起来,计算出小计值 (_cy_oadjust_subtotal)。
PersistUtility:PersistUtility 是一个自定义组件,用于复制 OrderForm 中的值。它主要用来使值持久化。否则,名称以 _ 开头的 OrderForm 值将不会持久保存。PersistUtility 组件的源代码是用 Visual C++ 编写的,随商务参考体系结构一起提供。
RequiredOrderSubTotalCy:RequiredOrderSubtotalCy 检查OrderForm 中的 _oadjust_subtotal 键,确保赋给该键的值不是 NULL。
将购物篮内容转换为 XML
运行管道后,Basket.pasp 中的代码再次检查 OrderGroup 对象,以确保该对象仍包含一些项目(因为某些项目可能已在管道中删除)。如果购物篮包含项目,则使用 Commerce Server DictionaryXMLTransforms 对象将内容转换为 XML 并将其写入 Response 对象。DictionaryXMLTransforms 对象生成的 XML 类似于以下代码:
orderform_id="{0F111D4C-E79F-4615-B1AD-9193C811DE86}"
saved_cy_oadjust_subtotal="24.99">
<Items quantity="1"
product_id="Quick Course in Microsoft Office 2000"
product_catalog="书籍" product_category=""
description="QUICK COURSE IN MICROSOFT OFFICE 2000 提供一些速成教程,帮助您快速掌握此办公套件的基础知识,熟练使用 Microsoft Excel、Microsoft Word、Microsoft PowerPoint、Microsoft Outlook、Microsoft Access、Microsoft Internet Explorer 5、Microsoft FrontPage 和 Microsoft Publisher。"
d_DateCreated="16/02/2001 18:36:06"
cy_lineitem_total="24.99" cy_unit_price="24.99"
lineitem_uid="{FC57A7EA-1420-40A8-8F55-569FE9B2BEDE}"
_product_Name="Quick Course in Microsoft Office 2000"
_cy_oadjust_adjustedprice="24.99"/>
</orderform>
然后,使用 Basket-IE5.xsl 样式表将购物篮页作为 HTML 呈现。
订单处理
用户决定结帐时,必须执行以下步骤:
为订单指定收货人地址(或选择将不同项目发送到不同地址)。
选择发货方法。
提供订单的付款信息。
确认订单详细信息。
完成订货过程。
ConsolidatedRetail.com 站点使用 Commerce Server 对象和管道组件来处理这些流程。
指定收货人地址
用户通常使用 Shipping.pasp 来指定收货人地址。该页包含一个表单,该表单列出了用户配置文件中的所有收货人地址,还带有创建新地址或编辑现有地址的选项。用户只需要指定项目要发往的地址即可。然后,该页将表单发回给自身,用指定地址更新表示购物篮内容的 OrderGroup 对象。
用户指定收货人地址时,Shipping.pasp 中的代码使用 Common.asp 头文件中的 GetUserID 函数来检索当前用户的 ID。然后,Shipping.pasp 调用 Basket.asp 头文件中的 LoadBasket 例程填充 OrderGroup 对象,如下所示:
strUserID = GetUserID
Set objMSCSOrderGrp = LoadBasket(strUserID)
接着,代码从查询字符串中检索提供的地址 ID,用指定的地址 ID 更新购物篮中的每个项目:
<orderform
For Each strOrderFormName In objMSCSOrderGrp.Value.OrderForms
Set objMSCSOrderForm = objMSCSOrderGrp.Value.OrderForms _
(strOrderFormName)
For each colItem in objMSCSOrderForm.Items
colItem.value("shipping_address_id") = strAddressID
Next
Next
最后,设置整个 OrderGroup 的收货人地址;对于以前设置的地址,如果没有用到则将其删除;然后保存购物篮。将用户重定向到 ShippingMethod.pasp,为订单指定发货方法:
Call objMSCSOrderGrp.SetShippingAddress(strAddressID)
Call objMSCSOrderGrp.SaveAsBasket()
Set objMSCSOrderGrp = Nothing
Response.Redirect "ShippingMethod.pasp"
但是,如果用户是首次访问 Shipping.pasp 页,则必须从用户配置文件中检索可能的地址列表以便在表单中显示该列表。Shipping.pasp 中的代码使用 ADO 查询在站点数据库的地址表中进行检索,如下所示:
strUserID = GetUserID()
Set cnBizDesk = server.CreateObject(mc_stradodb_connection)
Set rsAddress = server.CreateObject(mc_stradodb_recordset)
cnBizDesk.Open _
Application("MSCSDictConfig").s_BizDataStoreConnectionString
rsAddress.Open "select g_address_id as 'address_id', i_address_type as 'address_type', u_description as 'description', u_address_name as 'address_name', u_address_line1 as 'address_line1', u_address_line2 as 'address_line2', u_city as 'city', u_region_name as 'region_code', u_region_name as 'region_name', u_postal_code as 'postal_code' from addresses where g_id = '" & strUserID & "' ORDER BY u_address_name", cnBizDesk
如果用户的配置文件中未列出地址,则使用 mode 参数将用户重定向到 EditAddressBook.pasp 页。该参数在添加地址后将用户返回到此页:
If rsAddress.EOF Then '没有为该用户 ID 输入任何地址
rsAddress.Close
Set rsAddress = Nothing
cnBizDesk.Close
Set cnBizDesk = Nothing
Response.Redirect "EditAddressBook.pasp?Mode=" & mc_strPageName
End If
如果用户配置文件包含一个或多个地址,则会将地址信息添加到购物篮(指定的最后一个地址是默认收货人地址)并将其作为 XML 呈现:
Call XMLBegTag(mc_strAddresses)
Do While Not rsAddress.EOF
strAddressID = rsAddress.Fields("address_id").value
strDescription = rsAddress.Fields("description").value
strAddressName = rsAddress.Fields("address_name").value
strAddressLine1 = rsAddress.Fields("address_line1").value
strAddressLine2 = rsAddress.Fields("address_line2").value
strCity = rsAddress.Fields("city").value
strRegionCode = rsAddress.Fields("region_code").value
strRegionName = rsAddress.Fields("region_name").value
strPostalCode = rsAddress.Fields("postal_code").value
blnSaveSuccessful = PutOrderAddress(objMSCSOrderGrp, _
mc_lngShippingAddress, strAddressID, strAddressName, _
strAddressLine1, strAddressLine2, strCity, strRegionName, _
strPostalCode, strDescription)
If blnSaveSuccessful Then
Call XMLBegTag(mc_strAddress)
Call XMLTag(mc_strAddress_ID, strAddressID)
Call XMLTag(mc_strDescription, strDescription)
Call XMLTag(mc_strAddress_Name, strAddressName)
Call XMLTag(mc_strAddress_Line1, strAddressLine1)
Call XMLTag(mc_strAddress_Line2, strAddressLine2)
Call XMLTag(mc_strCity, strCity)
Call XMLTag(mc_strRegion_Code, strRegionName)
Call XMLTag(mc_strRegion_Name, strRegionName)
Call XMLTag(mc_strPostal_Code, strPostalCode)
Call XMLEndTag(mc_strAddress)
'设置付款地址
If rsAddress.Fields("address_type").value = 2 Then
objMSCSOrderGrp.Value("OrderForms").Value _
("default").Value("billing_address_id") = strAddressID
End If
End If
rsAddress.MoveNext
Loop
Call XMLEndTag(mc_strAddresses)
End If
此代码所生成的 XML 格式如以下代码所示:
<addresses>
<address>
<address_id>{0243FFF8-E633-4DE4-AA2E-2083E9D5ABB4}</address_id>
<description>家庭地址</description>
<address_name>Kim Abercrombie</address_name>
<address_line1>我的住宅号</address_line1>
<address_line2>我所在的街道</address_line2>
<city>Redmond</city>
<region_code>Washington</region_code>
<region_name>Washington</region_name>
<postal_code>12345</postal_code>
</address>
<address>
<address_id>{B87EDB27-8FBE-4BDA-83BE-ED037A0B7E4C}</address_id>
<description>工作地址</description>
<address_name>Kim Abercrombie</address_name>
<address_line1>Microsoft Corp.</address_line1>
<address_line2>1 Microsoft Way</address_line2>
<city>Redmond</city>
<region_code>Washington</region_code>
<region_name>Washington</region_name>
<postal_code>54321</postal_code>
</address>
</addresses>
指定发货方法
用户为订单指定收货人地址后,必须选择发货方法。Commerce Server 2000 支持配置多种发货方法,每个方法的价格范围各不相同,这取决于重量、项目数量或订单总费用。(有关定义发货方法的详细信息,请参考 Commerce Server 2000 文档。)
使用 ShippingMethod.pasp 页,用户可以从站点数据库中定义的众多发货方法中选择一个。该页包含一个表单,将各种发货方法作为选项列出。用户选择了一个选项后,将把所作的选择发回该页,进行处理。从应用程序级 ShippingMethodsXML 变量检索发货方法,以便显示。
可用发货方法列表是从应用程序级 ShippingMethodsXML 变量检索的,使用 Global.asa 中的 GetShippingMethodsXML 函数将该变量初始化,如下所示:
Function GetShippingMethodsXML(objMSCSDictConfig)
Dim objMSCSShpMthMgr
Dim rsShippingMethods
Dim strXMLStream
strXMLStream = ""
Set objMSCSShpMthMgr = _
Server.CreateObject("Commerce.ShippingMethodManager")
objMSCSShpMthMgr.Initialize _
(objMSCSDictConfig.s_TransactionsConnectionString)
Set rsShippingMethods = _
objMSCSShpMthMgr.GetInstalledMethodList _
("", "shipping_method_name", Array("shipping_method_id", _
"shipping_method_name"))
If Not (rsShippingMethods.EOF And rsShippingMethods.BOF) Then
Do While Not rsShippingMethods.EOF
strXMLStream = strXMLStream & vbCrLf & "<shipping_method>"
strXMLStream = strXMLStream & vbCrLf & "<shipping_method_id>" & _
rsShippingMethods.Fields("shipping_method_id").Value & _
"</shipping_method_id>"
strXMLStream = strXMLStream & vbCrLf & _
"<shipping_method_name>" & _
rsShippingMethods.Fields("shipping_method_name").Value & _
"</shipping_method_name>"
strXMLStream = strXMLStream & vbCrLf & "</shipping_method>"
rsShippingMethods.MoveNext
Loop
End If
Set objMSCSShpMthMgr = Nothing
GetShippingMethodsXML = strXMLStream
End Function
ShippingMethod.pasp 页包含可用发货方法列表,如以下 XML 格式所示:
<shipping_method>
<shipping_method_id>
{00000000-0000-0000-0000-003688009465}
</shipping_method_id>
<shipping_method_name>快递</shipping_method_name>
</shipping_method>
<shipping_method>
<shipping_method_id>
{00000000-0000-0000-0000-001140002642}
</shipping_method_id>
<shipping_method_name>标准</shipping_method_name>
</shipping_method>
用户选择发货方法后,必须将该方法的 ID 和名称存储在 OrderGroup 中。由于从表单传递的只是 ID,因此必须使用 Commerce Server ShippingMethodManager 对象从数据库中检索名称。此对象提供 GetInstalledMethodList 方法,使用该方法可以将匹配指定标准的方法作为记录集进行检索。根据提供的 ID 检索相关方法名称的代码类似于以下内容:
Set objMSCSShpMthMgr = _
Server.CreateObject(mc_strShippingMethodManager)
objMSCSShpMthMgr.Initialize(Application("MSCSDictConfig"). _
s_BizDataStoreConnectionString)
set rsShpMthName = objMSCSShpMthMgr.GetInstalledMethodList _
("shipping_method_id = '" &
strShippingMethodID & "'", "", _
Array(mc_strshipping_method_name))
set objMSCSShpMthMgr = nothing
If Not (rsShpMthName.EOF and rsShpMthName.BOF) then
strShippingMethodName = rsShpMthName.Fields(0).Value
End If
rsShpMthName.Close
Set rsShpMthName = Nothing
检索到发货方法名称后,将使用发货方法数据更新购物车中的项目,并将用户重定向到 Payment.pasp 以指定付款信息:
For Each strOrderFormName In objMSCSOrderGrp.Value.OrderForms
Set objMSCSOrderForm = objMSCSOrderGrp.Value. _
OrderForms(strOrderFormName)
For each colItem in objMSCSOrderForm.Items
colItem.value(mc_strshipping_method_id) = strShippingMethodID
colItem.value(mc_strshipping_method_name) = strShippingMethodName
Next
Next
Call objMSCSOrderGrp.SaveAsBasket()
Set objMSCSOrderGrp = Nothing
Response.Redirect "Payment.pasp"
指定多个收货人地址和发货方法
用户可以为购物车中的每个项目指定不同的收货人地址和发货方法。支持此功能的代码位于 MultiShipping.pasp 页中。该页包含一组 txtShippingMethodID 和 txtAddressID 输入字段(购物篮中的每个项目都有一个输入字段)。
用户请求每个项目的发货方法和地址时,将窗体发回 MultiShipping.pasp,同时更新代表购物篮的 OrderGroup。以下代码用于更新每个项目的发货方法和地址数据:
' 用发货方法 ID 填充每个项目
For Each strOrderFormName In objMSCSOrderGrp.Value.OrderForms
Set objMSCSOrderForm = _
objMSCSOrderGrp.Value.OrderForms(strOrderFormName)
For each colItem in objMSCSOrderForm.Items
intIndex = intIndex + 1
strShippingMethodID = _
Request.Form("txtShippingMethodID").Item(intIndex)
strShippingMethodName = Null
If Not IsNull(strShippingMethodID) Then
' 发货方法名称
Set objMSCSShpMthMgr = Server.CreateObject _
(mc_strShippingMethodManager)
objMSCSShpMthMgr.Initialize (Application("MSCSDictConfig"). _
s_BizDataStoreConnectionString)
Set rsShpMthName = objMSCSShpMthMgr.GetInstalledMethodList _
("shipping_method_id = '" & strShippingMethodID & _
"'", "", Array(mc_strshipping_method_name))
Set objMSCSShpMthMgr = nothing
If Not (rsShpMthName.EOF and rsShpMthName.BOF) then
strShippingMethodName = rsShpMthName.Fields(0).Value
else
strShippingMethodID = Null
End If
rsShpMthName.Close
Set rsShpMthName = Nothing
End If
strAddressID = Request.Form("txtAddressID").Item(intIndex)
colItem.value(mc_strshipping_method_id) = strShippingMethodID
colItem.value(mc_strshipping_method_name) = strShippingMethodName
colItem.value("shipping_address_id") = strAddressID
Next
指定付款信息
作为结帐流程的一部分,用户必须为订单指定付款详细信息。Payment.pasp 页按订单处理顺序处理这一事项。
注意: 在本应用程序示例中,付款详细信息是使用 HTTP 以纯文本的形式进行传输的。在实际站点中,应使用 HTTPS 对付款数据进行加密。
Payment.pasp 页包含一个表单,允许用户从配置文件指定信用卡和付款地址详细信息。如果配置文件中未定义付款地址,则将用户重定向到可以添加此地址的 EditAddressBook.pasp 页。Payment.pasp 页读取该表单中的付款详细信息,然后将信息写入代表用户购物篮的 OrderGroup 对象。
strUserID = GetUserID
Set objMSCSOrderGrp = LoadBasket(strUserID)
For Each strOrderFormName In objMSCSOrderGrp.Value.OrderForms
Set objMSCSOrderForm = objMSCSOrderGrp.Value. _
OrderForms(strOrderFormName)
' 付款方法 - 站点只支持信用卡
objMSCSOrderForm.value("payment_method") = "credit_card"
' 信用卡全称
objMSCSOrderForm.value("cc_name") = strPaymentMethod
' 信用卡内部代码
objMSCSOrderForm.value("cc_code") = strPaymentMethod
' 信用卡持有人姓名
objMSCSOrderForm.value("cc_Account_Holder") = strNameOnAcct
' 信用卡号
objMSCSOrderForm.value("cc_number") = strCardNo
' 信用卡到期月份
objMSCSOrderForm.value("cc_expmonth") = strCardExpMth
' 信用卡到期年份
objMSCSOrderForm.value("cc_expyear") = strCardExpYr
Next
Call objMSCSOrderGrp.SaveAsBasket()
Response.Redirect "OrderSummary.pasp"
填写完付款信息后,用户将被重定向到 OrderSummary.pasp 页。
确认订单详细信息
在进入订单处理的最后一个步骤前,用户还有机会确认或更改订单详细信息。订单详细信息显示在 OrderSummary.pasp 页上。
代码使用两个管道来检索订单详细信息,以便显示该信息。首先,PAGBasket 管道检索购物篮内容。然后,PAGTotal 管道计算装运费用和小计。有关 PAGBasket 管道的说明,请参考本章中的“查看购物篮”一节。PAGTotal 管道将在下一节中介绍。
PAGTotal 管道
PAGTotal 管道如图 7-3 所示。使用 Commerce Server 管道编辑器打开 Total.pcf 就可以进行查看。
图 7-3:PAGTotal 管道
货物分拆阶段
“货物分拆”阶段准备发货字典。该阶段涉及以下两个组件:
Commerce.Splitter:分拆组件生成发货字典 (shipments),此字典位于 OrderForm 中。此字典包含要单独发送的货物,它们分别根据订单中的 shipping_address_id 和 shipping_method_id 值来创建。
Commerce.ShippingMethodRouter:ShippingMethodRouter 组件使用 CacheManager 对象读取 ShippingManagerCache,以将发货方法映射到特定的发货组件。该组件浏览发货方法列表,如果存在使用指定方法发送的货物,它将收集这些货物的字典,将货物传递给发货组件,然后运行相关的发货组件。各个发货组件为传来的货物计算总装运费用。处理完发送的所有货物后,将计算总的装运费用。
发货阶段
“发货”阶段将发货折扣应用于订单。该阶段只涉及一个组件:ShippingDiscountAdjust。此组件检查 _shipping_discount_type 并调整订单的总装运费用。如果折扣为空白,则组件不执行任何操作。如果折扣类型为 1 或 2,则组件应用折扣。如果折扣既不为空白也不属于折扣类型 1 或 2,则组件返回一个错误。使用 OrderDiscount 对象确定 _shipping_discount_type 值。
经办阶段
“经办”阶段在订单表单上设置 _handling_total。此阶段涉及以下两个组件:
DefaultHandlingCy:此组件将零分配给订单字典中的总经办费用。它是一个占位符组件,实际使用时将被另一个经办组件替代。
RequiredHandlingCy:RequiredHandlingCy 组件校验 order._handling_total 键是否存在。
税金阶段
“税金”阶段在订单表单上设置 _tax_total 和 _tax_included 值。此阶段涉及以下两个组件:
SampleRegionalTax:此组件根据 RegionalTaxCache 中的税金信息将税金字段 (_cy_tax_total、shipments._cy_tax_total 和 cy_tax_included) 设置为合适的纳税值。
RequiredTaxCy:RequiredTaxCy 组件校验订单表单中是否存在 _cy_tax_total 和 _cy_tax_included 键。如果某个键不存在,则 RequiredTaxCy 使用 pur_badtax 常量从 MessageManager 中检索错误消息文本,并将消息存储在 OrderForm 的 _Purchase_Errors 集合中。
订单合计阶段
“订单合计”阶段在订单表单上设置 _total_total 值。此阶段涉及以下三个组件:
DefaultTotalCy:此组件校验订单上是否设置了全部四个合计项。如果这四个值都存在,将它们加起来并将和值分配给订单字典的最终合计项。如果某些值缺失,组件将停止运行并返回一个错误。
PersistUtility:PersistUtility 是一个自定义组件,用于复制 OrderForm 中的值。它主要用来使值持久化。否则,名称以下划线 (_) 开头的 OrderForm 值不会持久化。PersistUtility 组件的源代码是用 Visual C++ 编写的,随商务参考体系结构一起提供。
RequiredTotalCy:RequiredTotalCy 组件浏览 _Verify_With 字典中的键和值,确保每个键在订单表单中存在并具有相同的值。
修正阶段
最后一个阶段是“修正”阶段,它只涉及一个组件:Truncate Description。在 Commerce Server 2000 中,任何长度超过 255 个字符的字段将被归于 Text 类型;而此组件是用 VBScript 编写的,提供了解决这个问题的方法。
获取用户的电子邮件地址
运行管道后,OrderSummary.pasp 中的代码为当前用户检索 ProfileObject,同时提取电子邮件地址以供订单确认消息使用。然后将地址添加到 OrderGroup 的默认 OrderForm,保存 OrderGroup:
'获取默认 orderform
Set objMSCSOrderForm = objMSCSOrderGroup.Value("OrderForms"). _
Value("default")
'设置电子邮件地址以供订单确认消息使用。
Set objMSCSProfileObject = GetUserObject()
objMSCSOrderForm.user_email_address = objMSCSProfileObject _
(mc_strGeneralInfo).Value(mc_strEmail_Address)
Set objMSCSProfileObject = Nothing
Call objMSCSOrderGroup.SaveAsBasket()
最后,使用 Commerce Server DictionaryXMLTransforms 对象将 OrderGroup 的内容转换为 XML 并将其写入 Response 对象,如以下代码所示:
Set objXMLTransforms = _
Server.CreateObject(mc_strXMLTransforms)
Set objXMLOrderForm = _
objXMLTransforms.GenerateXMLForDictionaryUsingSchema _
(objMSCSOrderForm, Application("TransformSchema"))
Set objMSCSOrderForm = Nothing
Set objXMLTransforms = Nothing
If not isEmpty(objXMLOrderForm) Then
Response.Write objXMLOrderForm.xml
Else
Call AddException(m_varrExceptions, "1222", _
"将 orderform 转换为 XML 时出错。", _
"basket.asp")
End if
这将生成用以下 XML 格式表示的 OrderGroup:
<orderform orderform_id="{CD9EABBE-B0BF-48FE-9DB9-58A35A8FEE9F}"
payment_method="credit_card"
billing_address_id="{0243FFF8-E633-4DE4-AA2E-2083E9D5ABB4}"
saved_cy_oadjust_subtotal="44.98" saved_cy_total_total="49.98"
cc_name="American Express" cc_expyear="2001" cc_expmonth="08"
cc_number="1234" _cy_shipping_total="5" _cy_tax_total="0"
user_email_address="kim@somecompany.com"
cc_Account_Holder="Kim" cy_tax_total="0"
cy_shipping_total="5">
<Addresses address_name="Kim Abercrombie" address_line1="我的住宅"
address_line2="我的街道" city="我的城市" region_code="WA"
postal_code="12345" country_code="US" description="家庭地址"
AddressesDictKey="{0243FFF8-E633-4DE4-AA2E-2083E9D5ABB4}"/>
<Items quantity="1"
product_id="Quick Course in Microsoft Office 2000"
product_Name="Quick Course in Microsoft Office 2000"
product_catalog="书" product_category=""
description="QUICK COURSE IN MICROSOFT OFFICE 2000 提供一些速成教程,帮助您快速掌握此办公套件的基础知识,熟练使用 Microsoft Excel、Microsoft Word、Microsoft PowerPoint、Microsoft Outlook、Microsoft Access、Microsoft Internet Exp"
shipping_address_id="{0243FFF8-E633-4DE4-AA2E-2083E9D5ABB4}"
shipping_method_id="{00000000-0000-0000-0000-003688009465}"
shipping_method_name="快递"
d_DateCreated="16/02/2001 18:36:06" cy_lineitem_total="24.99"
cy_unit_price="24.99"
lineitem_uid="{FC57A7EA-1420-40A8-8F55-569FE9B2BEDE}"
_product_Name="Quick Course in Microsoft Office 2000"
_cy_oadjust_adjustedprice="24.99"/>
<shipments
shipping_address_id="{0243FFF8-E633-4DE4-AA2E-2083E9D5ABB4}"
shipping_method_id="{00000000-0000-0000-0000-003688009465}"
_cy_shipping_total="5"/>
</orderform>
完成订单处理
用户确认订单详细信息后,订单处理就完成了。有关订单处理的最后一个步骤的代码是在 Thankyou.pasp 页中实现的。
运行以前所述的 PAGBasket 和 PAGTotal 管道,即开始运行 Thankyou.pasp 页了。然后,运行 PAGFinal 管道完成订单处理。
PAGFinal 管道
使用 Commerce Server 管道编辑器打开 Final.pcf,就可以查看 PAGFinal 管道。PAGFinal 管道如图 7-4 所示。
图 7-4:PAGFinal 管道
订购流程中的唯一阶段名为 OrderTransfer。该阶段只涉及一个组件:QueueEmail Class。这是一个自定义管道组件,用于将订单确认消息以电子邮件的形式发送给默认 OrderForm 中的 user_e-mail_address 字段。
发送订单确认电子邮件
QueueEmail Class 是一个管道组件(COM 对象),用于实现 IPipelineComponent 接口。QueueEmail Class 组件从 OrderForm 获取信息,将信息转换为 XML,然后使用自定义的 QueuedEMailer.CMailer 排队组件将信息作为电子邮件消息发送。由于调用了排队组件来发送电子邮件消息,因此这一进程是异步进行的,这样可以防止电子邮件进程导致的延迟对用户结帐的响应时间产生不良影响。
QueueEmail Class 组件的自定义属性页用于在管道编辑器中配置该组件。这将允许您设置相应的消息队列、标记和 ProgID 以便将排队组件实例化,该组件将实际用于发送电子邮件,如图 7-5 所示。
图 7-5:QueueEmail Class 自定义属性页
QueuedEMailer.CMailer
实际发送电子邮件的过程由 QueuedEMailer.Cmailer 排队组件来处理。该组件安装在被标记为排队的 COM+ 应用程序中。此外,由于 _CMailer 接口也被标记为排队,从而允许通过消息队列异步调用该接口上的方法。_CMailer 接口只包含一个方法 (SendMail),该方法是使用以下方法签名来定义的:
HRESULT SendMail(
[in] BSTR strXMLOrderForm,
[in] VARIANT_BOOL blnUseHTMLMail);
请注意以上代码中的两个参数都被标记为 [in],这是因为排队组件不支持[out] 或 [retval] 参数。strXMLOrderForm 参数用于将订单表单的 XML 表示传递给该组件。blnUseHTMLMail 参数值是布尔值,用于确定是以 HTML 格式还是以纯文本格式发送电子邮件消息。
也可以将 QueuedEMailer.CMailer 组件配置为支持对象构建。这意味着该组件可以实现 IobjectConstruct 接口,该接口包含名为 Construct 的方法,当对象实例化时,COM+ 将调用这一方法。将构造函数字符串传递给 Construct 方法,该方法包含对象可以使用的配置信息。传送给 QueuedEMailer.CMailer 的构造函数字符串采用以下 XML 格式:
<config>
<from>support@consolidatedretail.com</from>
<subject>订单确认</subject>
<TextXSL>C:\Inetpub\b2cref\xml\emailtext.xsl</TextXSL>
<HTMLXSL>C:\Inetpub\b2cref\xml\emailhtml.xsl</HTMLXSL>
<SMTPServer></SMTPServer>
<SMTPPort></SMTPPort>
<SMTPTimeout></SMTPTimeout>
<UseSSL>False</UseSSL>
<SMTPUserName></SMTPUserName>
<SMTPPassword></SMTPPassword>
<SMTPAuthMethod></SMTPAuthMethod>
</config>
使用 Component Services Microsoft Management Console (MMC) 管理单元(如图 7-6 所示)来配置此构造函数。
图 7-6:Component Services MMC 管理单元
QueuedEMailer.CMailer 对象使用 MSXML3 和 CDOSYS 对象。XML 和 XSL 的加载是借助使用 MSXML3 对象的 XMLDomDocument 完成的。电子邮件消息的实际发送是由 IMessage 对象(CDO 协作数据对象)完成的,该对象要用到 CDOSYS 对象。用于 Windows 2000 的协作数据对象 (CDO) Cdosys.dll 实现了 CDO API 规范的 2.0 版本,它是一个 COM 组件,专用于简化创建或操纵 Internet 消息的程序编写工作。
总结
现在,您应该已经了解了 ConsolidatedRetail.com 应用程序背后的基本功能和开发策略。文中的代码注释提供了更为详细的信息。
--