WEB开发网
开发学院软件开发Java 使用 Web 服务和 eBay SDK 开发应用程序,第 1 部... 阅读

使用 Web 服务和 eBay SDK 开发应用程序,第 1 部分: 构建 eBay 搜索引擎

 2009-12-09 00:00:00 来源:WEB开发网   
核心提示:关于本教程eBay 的清单的百分之四十是由使用 eBay 的 Web 服务的应用程序来驱动的,这些清单中的一半是来自于第三方开发人员所编写的应用程序,使用 Web 服务和 eBay SDK 开发应用程序,第 1 部分: 构建 eBay 搜索引擎,eBay 每个月处理超过十亿个来自它的 Web 服务 API 的请求,这就

关于本教程

eBay 的清单的百分之四十是由使用 eBay 的 Web 服务的应用程序来驱动的。这些清单中的一半是来自于第三方开发人员所编写的应用程序。eBay 每个月处理超过十亿个来自它的 Web 服务 API 的请求。这就意味着两点:许多人通过供应商提供的应用程序来使用 eBay,并且这些应用程序可能做了较多的缓存。

对于 eBay 来说,人们开始使用它们的 Web 服务 API 开发应用程序非常重要,这是因为,它们在通过帮助人们进行虚拟的清仓拍卖来赚钱的同时,还通过帮助零售商将其整个业务都搬到网上来赚取真正的钱。这意味着可以通过编写应用程序帮助 eBay 将业务搬到网上来赚钱。并且,如同一个良好的合作伙伴,eBay 正努力向您这样的开发人员提供编写应用程序所需的全部,这些应用程序将为您和 eBay 赚钱。如果您不喜欢唯利是图的方面,您可能是 Richard Dawkins 的追随者,则请从这个角度考虑该问题:eBay 是一种发展策略,用于将您车库中的垃圾从它所在的地方(和您在一起但是未增值)移到它所希望的地方(某个增值的地方)。这种资本的分配是有助于激发您优化这种转移的要素。

开始之前

本教程有如下假定:

您已经加入了 eBay 开发人员计划 (eBay Developers Program)。

您已经收到了您的 devId、appId 和 certId。

您已经下载并安装了 eBay Java SDK。

您已经在沙箱 (Sandbox) 中创建了用户并拥有了用户 ID。

您已经生成了授权令牌。

您可以成功地执行 eBay SDK 示例文件夹 (*whew!*) 中的 ApiCallsDemo 应用程序。

遗憾地是,由于篇幅方面的考虑,我不能详细地描述这些步骤。

引言

SDK 概述

eBay 创建了 Web 服务 API 并通过 SOAP 将其公开,以便使您可以使用任何支持 SOAP 的编程语言来编写 eBay 应用程序。直接使用 SOAP 可能是困难的,所以 eBay 在 SOAP 层之上创建了 Java SDK。该 SDK 提取直接处理 SOAP 的细节。该 SDK 包括很多类,但是它们巧妙地分成了三个主要的类别:

内核类 (Kernel class) 距离 SOAP 层最近,并且在某些情况下,作为其他类别中的类的构件。

调用类 (Call class) 是 com.ebay.sdk.call 包的一部分,表示特定的 eBay 服务的调用。

类型类 (Type class) 在 com.ebay.soap.eBLBaseComponents 包中,表示用于作为调用的参数或返回值的数据类型。

内核类

内核类在 com.ebay.sdk 包中。该类别包括 14 个类;其中的 3 个是异常,该示例只直接使用了 2 个。最重要的两个类是 ApiContext 和 ApiCredential。ApiContext 定义了调用任何 API 的上下文。所有的调用类都将 ApiContext 作为它们的构造器的参数。ApiContext 还保存了对 ApiCredential 的引用,它用于授权调用。如果凭证设置不正确,调用将会失败。

com.ebay.sdk 包还保存了让您控制应用程序的日志记录(缺省情况下,应用程序记录到 Axis 的日志)、定义异常的调用重试策略以及使用其他的普通应用程序行为的类。ApiCall 同样也是一个内核类,但是它主要用来作为接下来所描述的所有调用类的超类。然而,如果您愿意,您可以通过调用 execute 或者 executeByApiName 方法直接使用 ApiCall 类。

调用类

这些类包装了 SOAP API 调用。使用的模式如下:

实例化调用类,在构造函数中传递 ApiContext。

实例化并填充该调用所需要的所有参数。

调用执行方法并捕获所有的返回值。

执行方法最终委托给 ApiCall 类中的某个执行方法。方法的实际名称可能会有不同,这取决于调用类。例如,AddItemCall 用于列出条目;执行方法采用 ItemType 作为参数并返回 FeesType。所以,列出条目使用的模式如下:

  ItemType item = new ItemType(); 
  // populate item with all sorts of relevant information (omitted) 
  AddItemCall call = new AddItemCall(apiContext); 
  // assume we have an instance of the ApiContext 
  FeesType fees = call.addItem(item); 

类型类

类型类是由 eBay Web 服务 Web 服务描述语言(Web Services Description Language,WSDL)所描述的数据类型的 Java 表示,并且不包含真正的行为。AbstractRequestType 和 AbstractResponseType 是这个包中的大部分类的超类。

引导 eBay 搜索

在 eBay 中搜索如何工作

执行 eBay 搜索的最简单的方法是提供一个查询字符串。使用过搜索引擎的任何人都应该很熟悉这种格式。查询由一个或多个以空格分隔的关键字所组成。标点符号用于提供查询的元信息。所有清单的标题和子标题按条件计算;条目描述没有用于这个计算中。您可以在 eBay 帮助页上找到对该查询语言的完整描述。

eBay 还具有高级的查询系统,允许您指定除了查询字符串以外的条件。例如,您可以将查询限制在某个类别,并且您可以基于条件(比如价格、清单类型和销售商)来筛选结果。

用例

您将在本文中开发的案例研究是一个接受文本字符串、查询 eBay 并返回结果的应用程序。下面是一个示例:

fetch: id title description query: godot +blooper category: 377 

这一行包含了三个指令:fetch、query 和 category。您通过在文本的后面加上冒号来指示一条指令。图 1 列出了该语法。


图 1. 查询语法

QUERY->( FETCH_CLAUSE QUERY_CLAUSE CATEGORY_CLAUSE )+
DESCRIPTOR->[a-zA-Z]+:
FETCH_CLAUSE->'fetch:' COLUMN+ | <nil>
QUERY_CLAUSE->'query:' QUERY_STRING
CATEGORY_CLAUSE->'category:' CATEGORY | <nil>
COLUMN->'id' | 'title' | 'subtitle' | 'description' | 'timeleft' | 'currentprice'
QUERY_STRING->[^DESCRIPTOR]
CATEGORY->[0-9]+

这是不严格的扩展巴科斯范式(Extended Backus-Naur Form,EBNF)语法。所有字母都大写的词是非终结符,而在右面的小写字母的词是终结符。

下面是一些有效查询的示例:

fetch: id title currentprice query: godot category: 377 这将提取在类别 377 中标题或者子标题中包含词 godot 的所有条目的 ID、标题和最高报价。fetch: title category: 377 这将提取在类别 377 中所有条目的标题。category: 377 query: godot fetch: id title currentprice 这与第一个查询是一样的——子句的顺序没有关系。

如果您没有指定 fetch 子句,搜索缺省使用 ID 和标题。如果您没有指定类别,搜索缺省使用所有的类别。唯一必需的子句是 query。因此,最简单的查询就是 query: <keyword>。

编写代码

驱动程序

该应用程序是从控制台驱动的,但是它是以这种方式设计的,即该控制台仅仅是表示层。这种逻辑可以像在 Web 应用程序或 GUI 应用程序中那样简单地使用,或者由数据库驱动。请参见 清单 1。


清单 1. Main.java

   
package org.thinkpig.ebay.search; 
 
import java.io.BufferedReader; 
import java.io.IOException; 
import java.io.InputStreamReader; 
import java.util.List; 
 
 
public class Main { 
 
  public static void main(String[] args) throws Exception { 
   System.out.println("eBayQueryTron v.001"); 
   System.out.println("-------------------"); 
   while(true) { 
     String input = getInput(); 
     if (input.startsWith("quit")) { return; } 
     AbstractQuery query = QueryParser.parse(input); 
     System.out.println("Executing query --> " + query); 
     QueryResult result = QueryExecutor.executeQuery(query); 
     displayResult(result); 
   } 
  } 
 
  private static String getInput() throws IOException { 
   System.out.print("\n> "); 
   BufferedReader in = 
   new BufferedReader(new InputStreamReader(System.in)); 
   return in.readLine(); 
  } 
 
  private static void displayResult(QueryResult _result) { 
   List items = _result.getResultItems(); 
   for(int i = 0; i < items.size(); ++i) { 
     displayItem((QueryResultItem)items.get(i)); 
   } 
  } 
 
  private static void displayItem(QueryResultItem _item) { 
   for(int i = 0; i < _item.getNumberOfColumns(); ++i) { 
     System.out.println(_item.getValue(i)); 
   } 
  } 
} 

这个代码中没有什么值得注意的。只要知道它是应用程序的入口点并作为主代码的驱动程序就足够了。它将应用程序的逻辑粘合在一起——QueryParser 和 QueryExecutor。它还显示了 QueryExecutor 的结果。

QueryParser 和 AbstractQuery

AbstractQuery 是表示在图 1 中所描述的 BNF 的数据类型。AbstractQuery 类(请参阅 清单 2)映射到 QUERY 规则,并且它具有它所支持的三个子句的属性。描述符就是类中的属性;描述符后面的规则映射到 Java 数据类型。例如,QUERY_CLAUSE 是由 QUERY_STRING 后面的查询描述符 (query:) 所构成。在 Java 语言中这将作为 private String queryString 实现。COLUMN_CLAUSE 由一个或多个 COLUMN 所组成,所以它表示为 List。


清单 2. AbstractQuery.java
   
package org.thinkpig.ebay.search; 
 
import java.util.ArrayList; 
import java.util.Collections; 
import java.util.List; 
 
public class AbstractQuery { 
  public static final String NO_CATEGORY = "NO CATEGORY"; 
 
  private String category = NO_CATEGORY; 
  private String eBayQuery = ""; 
  private List columns = new ArrayList(); 
  private boolean useDefaults = true; 
 
  public AbstractQuery() { 
   this.columns.add("id"); 
   this.columns.add("title"); 
  } 
 
  public void setCategory(String _category) { 
   if (_category == null) { 
     throw new IllegalArgumentException("Category must 
     not be null."); 
   } 
   this.category = _category; 
  } 
 
  public String getCategory() { 
   return this.category; 
  } 
 
  public void addColumn(String _column) { 
   if (this.useDefaults) { 
     this.columns.clear(); 
     this.useDefaults = false; 
   } 
   this.columns.add(_column); 
  } 
 
  public List getColumns() { 
   return Collections.unmodifiableList(this.columns); 
  } 
 
  public String toString() { 
   return "fetch: " + this.columns + " query: " + 
     this.eBayQuery + " category: " + this.category; 
  } 
 
  public void setEBayQuery(String _query) { 
   this.eBayQuery = _query; 
  } 
 
  public String getEBayQuery() { 
   return this.eBayQuery; 
  } 
} 

QueryParser 是将输入字符串变成 AbstractQuery 的对象(请参见清单 3)。


清单 3. QueryParser.java
   
package org.thinkpig.ebay.search; 
 
import java.util.ArrayList; 
import java.util.Arrays; 
import java.util.List; 
 
public class QueryParser { 
  private static String currentToken = null; 
  private static List tokens; 
  private static String END = "!END"; // not a member of the 
                                 // token alphabet 
  public static AbstractQuery parse(String _queryString) { 
   AbstractQuery query = new AbstractQuery(); 
   if (_queryString == null || _queryString.length() == 0) { 
     throw new ParseException("Query string must be non-empty."); 
   } 
   // if we don't trim first, split will give us a "" token 
    _queryString = _queryString.trim(); 
   tokens = new ArrayList(Arrays.asList(_queryString.split("\\s+"))); 
   nextToken(); // prime the pump 
   parse(query); 
   return query; 
  } 
 
  private static void parse(AbstractQuery _query) { 
   while(!currentToken.equals(END)) { 
     if(currentToken.equals("query:")) { 
       nextToken(); 
       parseQueryClause( _query); 
       continue; 
     } 
     if(currentToken.equals("category:")) { 
       nextToken(); 
       parseCategoryClause(_query); 
       continue; 
     } 
     if(currentToken.equals("fetch:")) { 
       nextToken(); 
       parseFetchClause(_query); 
       continue; 
     } 
     throw new ParseException("Unknown descriptor: " + currentToken); 
   } 
  } 
 
 
  private static void parseQueryClause(AbstractQuery _query) { 
   StringBuffer buf = new StringBuffer(); 
   while(!currentToken.equals(END)) { 
     if (currentToken.endsWith(":")) { break; } 
     buf.append(currentToken); 
     buf.append(" "); 
     nextToken(); 
   } 
   _query.setEBayQuery(buf.toString().trim()); 
  } 
 
  private static void parseFetchClause(AbstractQuery _query) { 
   while(!currentToken.equals(END)) { 
     if (currentToken.endsWith(":")) { return; } 
     _query.addColumn(currentToken); 
     nextToken(); 
   } 
  } 
 
  private static void parseCategoryClause(AbstractQuery _query) { 
   _query.setCategory(currentToken); 
   nextToken();    
  } 
 
  private static void nextToken() throws ParseException { 
   try { 
     currentToken = (String)tokens.remove(0); 
   } catch (IndexOutOfBoundsException _ioobe) { 
     currentToken = END; 
   } 
  } 
 
  private static String peek() { 
   if(tokens.size() > 0) { 
     return (String)tokens.get(0); 
   } 
   return END; 
  } 
} 

QueryParser 是一个简单的递归下降解析器 (recursive descent parser) 的示例。它以类似于 AbstractQuery 的方式表示了语法。对于该语法中的每个转移规则,您都有一个方法。parse(AbstractQuery _query) 方法对应于 QUERY -> CLAUSE+ 规则。非正式地,该方法表示,“只要还有令牌,就作为查询子句、类别子句或者提取子句进行解析”。QUERY_CLAUSE 规则是后面跟随 QUERY_STRING 的查询描述符。QUERY_STRING 可以是除描述符以外的任何事物。parseQueryClause 方法将它找到的所有令牌附加到 queryString,直到找到描述符令牌为止。这个逻辑类似于 parseFetchClause 和 parseCategoryClause 方法。

引导 eBay 查询

QueryExecutor 和 eBayApi

现在,您已经拥有了希望用作 eBay 查询基础的 AbstractQuery。QueryExecutor 接受 AbstractQuery,将其转化为 eBay 格式,并把它发送给 Web 服务(请参见清单 4)。


清单 4. QueryExecutor.java

   
package org.thinkpig.ebay.search; 
 
import java.util.Iterator; 
 
import com.ebay.sdk.ApiException; 
import com.ebay.sdk.SdkException; 
import com.ebay.sdk.call.GetSearchResultsCall; 
import com.ebay.soap.eBLBaseComponents.GetSearchResultsRequestType; 
import com.ebay.soap.eBLBaseComponents.ItemType; 
import com.ebay.soap.eBLBaseComponents.SearchResultItemType; 
 
 
public class QueryExecutor { 
  private static final EbayApi api = new EbayApi(); 
 
  public static QueryResult executeQuery(AbstractQuery _query) throws 
  Exception { 
   GetSearchResultsCall call = convertQuery(_query); 
   SearchResultItemType[] results = call.getSearchResults(); 
   QueryResult queryResult = convertResults(_query, results); 
   return queryResult; 
  } 
 
  private static GetSearchResultsCall convertQuery(AbstractQuery _query) { 
   GetSearchResultsCall call = 
   new GetSearchResultsCall(api.getApiContext()); 
   call.setQuery(_query.getEBayQuery()); 
   if (!_query.getCategory().equals(AbstractQuery.NO_CATEGORY)){ 
     call.setCategoryID(_query.getCategory()); 
   } 
   return call; 
  } 
 
  private static QueryResult convertResults(AbstractQuery _query, 
   SearchResultItemType [] _results) throws SdkException { 
   QueryResult result = new QueryResult(); 
   if (_results == null) { 
     return result; 
   } 
   for (int i = 0; i < _results.length; i++) { 
     SearchResultItemType type = _results[i]; 
     result.addItem(convertItem(_query, type)); 
   } 
   return result; 
  } 
 
  private static QueryResultItem convertItem(AbstractQuery _query, 
   SearchResultItemType _resultType) throws SdkException { 
   QueryResultItem item = new QueryResultItem(); 
   Iterator i = _query.getColumns().iterator(); 
   while(i.hasNext()) { 
     String column = (String)i.next(); 
     String value = findValue(column, _resultType); 
     item.addColumn(column, value); 
   } 
   return item; 
  } 
 
  private static String findValue(String _column, 
   SearchResultItemType _resultType) throws SdkException { 
   ItemType item = _resultType.getItem(); 
   if (_column.equalsIgnoreCase("id")) { return 
   item.getItemID().getValue().trim(); } 
   if (_column.equalsIgnoreCase("title")) { return 
   item.getTitle().trim(); } 
   if (_column.equalsIgnoreCase("subtitle")) { return 
   item.getSubTitle().trim(); } 
   if (_column.equalsIgnoreCase("description")) { 
     return item.getDescription().trim(); 
   } 
   if (_column.equalsIgnoreCase("timeleft")) { 
     return item.getTimeLeft() == 
     null ? "Unknown" : item.getTimeLeft().toString(); 
   } 
   if (_column.equalsIgnoreCase("currentprice")) { 
     return 
     item.getSellingStatus().getCurrentPrice().toString().trim(); 
   } 
   throw new SdkException("No SDK mapping for column: " + _column); 
  } 
 
} 

QueryExecutor 做了三件事:它创建 GetSearchResultsCall 并使用来自 AbstractQuery 的数据填充它;它对调用对象调用 getSearchResults() 方法,这将调用 eBay Web 服务;它将结果转换为 QueryResult,以便特定于 eBay 的类型不会泄漏到应用程序的其余部分。GetSearchResultsCall 是 eBay API 的一部分,与 SearchResultItemType 一样。它们是到目前为止应用程序所依赖的 eBay SDK 的唯一部分。您严格地将应用程序与 eBay SDK 隔离开来,因为这样做有助于保护您的逻辑不受 eBay 可能对其 SDK 所做的更改的影响。eBay 善于维护向后的兼容性,但是 API 正迅速地发展。当您更改代码以便利用新的功能时,您肯定希望将改变保持在最低程度。

请注意 findValue 方法的底部,如果条目没有您感兴趣的列的映射,它将抛出 SdkException。eBay Java API 有两个主要的异常:SdkException 和 ApiException。SdkException 用于指示 SDK 内部的异常条件,而 ApiException 指示调用 Web 服务的问题。在本例中,您将抛出 SdkException,因为您不能将查询列映射到条目的属性。如果您试图查询一个不存在的类别 ID,那么您将得到 ApiException。

EbayApi 类封装了获得 ApiContext 并使用适当的凭证将其填充的流程(请参见清单 5)。当您使用这个代码时,请确保将 USER_AUTH_TOKEN 设置为测试用户的令牌。另外,还请注意,serverURL 指向沙箱。在生产应用程序中,这个 URL 将指向生产服务器。


清单 5. EbayApi.java
   
package org.thinkpig.ebay.search; 
 
import com.ebay.sdk.ApiContext; 
import com.ebay.sdk.ApiCredential; 
 
public class EbayApi { 
  private static final String USER_AUTH_TOKEN = 
  "<<Insert your token here.>>"; 
  private static final String serverURL = 
  "https://api.sandbox.ebay.com/wsapi"; 
 
  private ApiContext apiContext; 
 
  public EbayApi() { 
   this.apiContext = new ApiContext(); 
   ApiCredential cred = apiContext.getApiCredential(); 
   cred.seteBayToken(USER_AUTH_TOKEN); 
   apiContext.setApiServerUrl(serverURL); 
  } 
 
  public ApiContext getApiContext() { 
   return this.apiContext; 
  } 
 
} 

对于这个示例,您需要做的只是实例化一个新的 ApiContext,将它的 ApiCredential 的 eBayToken 属性设置为您的令牌,并设置 ApiServerUrl。然而,您在这里还可以进行任何其他上下文级的配置(例如日志记录)。请注意,ApiContext 的属性并不是不变的,所以需要引用 ApiContext 的任何代码(而且这是许多代码)可以更改它的配置。实际上它可能并不需要您担心,但是它是值得了解的。

获得查询结果

QueryResult 和 QueryResultItem

您已经见到了所有的好东西。唯一剩下的就是 QueryResult(清单 6)和 QueryResultItem(清单 7)。后者表示单独的搜索结果,而前者将它们全部捆绑成可以传送的一个对象。这些类将搜索结果的概念抽象为表示层在不必了解 eBay SDK 的细节的情况下可以解构的某个对象。


清单 6. QueryResult.java
   
package org.thinkpig.ebay.search; 
 
import java.util.ArrayList; 
import java.util.Collections; 
import java.util.List; 
 
public class QueryResult { 
  private final List results = new ArrayList(); 
  public void addItem(QueryResultItem _item) { 
   results.add(_item); 
  } 
 
  public List getResultItems() { 
   return Collections.unmodifiableList(results); 
  } 
 
} 

请注意,您不只是返回对结果的引用。您还需要保持该结果不变,所以 getter 将其包装在不可修改的列表包装程序中。


清单 7. QueryResultItem.java
   
package org.thinkpig.ebay.search; 
 
import java.util.ArrayList; 
import java.util.List; 
 
public class QueryResultItem { 
  private final List columns = new ArrayList(); 
  private final List values = new ArrayList(); 
 
  public void addColumn(String _columnName, String _columnValue) { 
   // we use this instead of a map to retain order 
   columns.add(_columnName); 
   values.add(_columnValue); 
  } 
  public int getNumberOfColumns() { 
   return columns.size(); 
  } 
 
  public String getValue(int _index) { 
   return (String)values.get(_index); 
  } 
} 

结束语

本教程演示了 eBay SDK 的使用相当简单。主要的问题就是找到正确的调用类,使用适当的参数设置它的属性,并调用它。您不必与 SOAP 部分搅在一起。然而,这似乎意味着使用 SDK 构建 eBay 应用程序非常简单。如果真是这样,SDK 就不会附带 958 页的 PDF 文档;开发人员站点就不会有论坛、FAQ 、知识库、白皮书、示例代码以及采用流式视频的应用程序开发课程。eBay SDK 有点像 XML——它容易学习,但是又难以理解和很好地使用。好消息是,eBay 提供了刚才提到的所有资源,可以帮助作为 eBay 应用程序开发人员的您更加轻松地工作。正如我前面所提到的,公司在让开发人员使用它的 Web 服务 API 编写应用程序方面有既得利益,因而它将使该流程对于您来说尽可能地容易。请利用这一情况并全身心地投入。

Tags:使用 Web 服务

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接