最近做到与eBay的交互,需要批量修改已上传到eBay的商品,现在告一段落,做个总结。
申请eBay测试账号,获取开发token
根据token开发
首先说一下开发的逻辑流程:从数据库获取要修改的商品数据-----创建符合eBay要求的的xml文件----将xml文件压缩成large merchant service(LMS)需要的的gz文件---
-将gz文件传递给eBay完成数据修改。按照这样的流程我们有四个步骤需要完成。
一、从数据库获取要修改的商品数据,因为现在是测试阶段,而且数据的获取和本文要将的主题关系不大,所以这一块略过。
二、创建符合eBay要求的xml文件。
要创建符合要求的xml文件,我们就首先应该知道要求的xml文件时什么格式的。根据我的观察,每个LMS操作需要的xml的格式基本如下,中间的
ReviseFixedPriceItem节点个数等于要修改的商品个数:
<?xml version="1.0" encoding="UTF-8"?> <BulkDataExchangeRequests xmlns="urn:ebay:apis:eBLBaseComponents"> <Header> <Version>583</Version> <SiteID>0</SiteID> </Header> <ReviseFixedPriceItem xmlns="urn:ebay:apis:eBLBaseComponents"></ReviseFixedPriceItem> <ReviseFixedPriceItem xmlns="urn:ebay:apis:eBLBaseComponents"></ReviseFixedPriceItem> </BulkDataExchangeRequests>
如果一个product对应了多个sku,ReviseFixedPriceItem节点的xml格式如下:
<ReviseFixedPriceItemRequest xmlns="urn:ebay:apis:eBLBaseComponents"> <RequesterCredentials> <eBayAuthToken>token</eBayAuthToken> </RequesterCredentials> <ErrorLanguage>en_US</ErrorLanguage> <WarningLevel>High</WarningLevel> <Version>583</Version> <Item> <ItemID>110122962805</ItemID> <Variations> <Variation> <SKU>RLauren_Wom_TShirt_Pnk_S</SKU> <StartPrice>21</StartPrice> <Quantity>11</Quantity> </Variation> <Variation> <SKU>RLauren_Wom_TShirt_Pnk_M</SKU> <StartPrice>21</StartPrice> <Quantity>31</Quantity> </Variation> </Variations> </Item> </ReviseFixedPriceItemRequest>
一个product对应了一个sku,ReviseFixedPriceItem节点的xml格式如下:
<ReviseFixedPriceItemRequest xmlns="urn:ebay:apis:eBLBaseComponents"> <ErrorLanguage>en_US</ErrorLanguage> <WarningLevel>High</WarningLevel> <Version>583</Version> <RequesterCredentials> <eBayAuthToken>token</eBayAuthToken> </RequesterCredentials> <Item> <CategoryMappingAllowed>true</CategoryMappingAllowed> <SKU>1122334455-14</SKU> <StartPrice>53</StartPrice> <Quantity>3</Quantity> </Item> </ReviseFixedPriceItemRequest>
生成xml的代码如下:
/// <summary> /// 创建批量修改商品的 xml /// </summary> /// <param name="token">商家的token,确定到要修改商品的eBay商家</param> /// <param name="EFConnectionString">数据库连接字符串,这里用不上</param> /// <param name="requestNode">这里指ReviseFixedPriceItem</param> /// <returns></returns> public static XmlDocument ReviseFixedItem(string token, string EFConnectionString, string requestNode) { XmlHeader();//生成除了ReviseFixedPriceItem节点以外的xml List<Product> products = CreateProductReviseFixed();//获取商品数据 //循环商品数据,生成ReviseFixedPriceItem节点,完成xml foreach (Product product in products) { //生成每个ReviseFixedPriceItem的公共部分 XmlNode addFixedPriceItemRequest = XmlTitle(token, requestNode); //生成每个ReviseFixedPriceItem的item部分 #region item XmlNode Item = ReturnXmlNode("Item", ""); //如果一个product对应了多个sku if (product.MultiVariant) { #region Multi-Variant Item.AppendChild(ReturnXmlNode("ItemID", product.eBayListingSettings.FirstOrDefault().eBayStoreCategoryID.ToString())); //一个产品 对应 一个eBay 产品?? XmlNode Variations = ReturnXmlNode("Variations", ""); foreach (var variant in product.Variants) { XmlNode Variation = ReturnXmlNode("Variation", ""); Variation.AppendChild(ReturnXmlNode("SKU", variant.SKU)); Variation.AppendChild(ReturnXmlNode("StartPrice", variant.SalePrice.ToString())); Variation.AppendChild(ReturnXmlNode("Quantity", variant.StockQuantity.ToString())); Variations.AppendChild(Variation); } Item.AppendChild(Variations); #endregion } //否则一个product对应了一个sku else { #region Single-Variant Item.AppendChild(ReturnXmlNode("CategoryMappingAllowed", "true")); Item.AppendChild(ReturnXmlNode("SKU",product.Variants.FirstOrDefault().SKU)); Item.AppendChild(ReturnXmlNode("StartPrice", product.Variants.FirstOrDefault().SalePrice.ToString())); Item.AppendChild(ReturnXmlNode("Quantity", product.Variants.FirstOrDefault().StockQuantity.ToString())); #endregion } addFixedPriceItemRequest.AppendChild(Item); #endregion root.AppendChild(addFixedPriceItemRequest); } //为ReviseFixedPriceItemRequest节点 添加xmlns='urn:ebay:apis:eBLBaseComponents' xmldoc.InnerXml = xmldoc.InnerXml.Replace("<" + requestNode + ">", "<" + requestNode + " xmlns='urn:ebay:apis:eBLBaseComponents'>"); return xmldoc; } /// <summary> /// 为每一个商品的xml节点添加通用的节点,如修改 3个商品,则调用3次此方法,生成3个<ReviseFixedPriceItem></ReviseFixedPriceItem> /// </summary> /// <param name="token"></param> /// <param name="requestNode"></param> /// <returns></returns> private static XmlNode XmlTitle(string token, string requestNode) { XmlNode addFixedPriceItemRequest = xmldoc.CreateElement(requestNode, xmlns); addFixedPriceItemRequest.AppendChild(ReturnXmlNode("ErrorLanguage", ErrorLanguage)); addFixedPriceItemRequest.AppendChild(ReturnXmlNode("WarningLevel", WarningLevel)); addFixedPriceItemRequest.AppendChild(ReturnXmlNode("Version", Version)); XmlNode RequesterCredentials = ReturnXmlNode("RequesterCredentials", ""); RequesterCredentials.AppendChild(ReturnXmlNode("eBayAuthToken", token)); addFixedPriceItemRequest.AppendChild(RequesterCredentials); return addFixedPriceItemRequest; } /// <summary> /// 添加larger merchant service 的 顶级xml,通用 /// </summary> /// <param name="requestHeader"></param> private static void XmlHeader() { xmldoc = new XmlDocument(); xmldoc.PrependChild(xmldoc.CreateXmlDeclaration("1.0", "UTF-8", null)); xmldoc.AppendChild(xmldoc.CreateElement("BulkDataExchangeRequests", xmlns)); root = xmldoc.DocumentElement; XmlNode RequesterHeader = ReturnXmlNode("Header", ""); RequesterHeader.AppendChild(ReturnXmlNode("Version", Version)); RequesterHeader.AppendChild(ReturnXmlNode("SiteID", "0")); root.AppendChild(RequesterHeader); } /// <summary> /// 创建每一个xml节点 /// </summary> /// <param name="XmlName"></param> /// <param name="XmlText"></param> /// <returns></returns> private static XmlNode ReturnXmlNode(string XmlName, string XmlText) { XmlNode tempNode = xmldoc.CreateNode(XmlNodeType.Element, XmlName, xmlns); if (!string.IsNullOrEmpty(XmlText)) tempNode.InnerText = XmlText; return tempNode; }
三、将xml文件压缩成large merchant service(LMS)需要的的gz文件。生成xml后,返回一个XmlDocument的数据,用来生成xml文件,同时创建gz文件,返回gz文件地
址。
/// <summary> /// 创建gz文件 /// </summary> /// <param name="xmlDocument">生成的xml数据</param> /// <param name="downloadFile">eBay返回response的下载地址</param> /// <returns>gz文件地址</returns> private static string OperationFile(XmlDocument xmlDocument, out string downloadFile) { string dateTime = DateTime.Now.ToString("yyyyMMddHHmmss"); #region 创建xml文件,将xmlDocument写入到文件里 string fileUploadPath = CreateFile(@"C:\eBayUploadFile", "Upload" + dateTime + ".xml"); //写入文件 xmlDocument.Save(fileUploadPath); #endregion #region 获取 文件,压缩成 gz 格式 FileInfo fi = new FileInfo(fileUploadPath); string fileNewPathGz = Compress(fi); #endregion #region eBay返回文件 下载地址 downloadFile = CreateFile(@"C:\eBayDownloadFile", "Download" + dateTime + ".zip"); #endregion return fileNewPathGz; } /// <summary> /// 创建文件 返回文件地址 /// /// </summary> /// <param name="path"></param> /// <param name="fileCombine"></param> /// <returns></returns> private static string CreateFile(string path,string fileCombine) { //创建eBayFile文件夹 bool existsPath = Directory.Exists(path); if (!existsPath) Directory.CreateDirectory(path); //创建 文件 string fileNewPath = Path.Combine(path, fileCombine); return fileNewPath; } /// <summary> /// gz文件生成 /// /// </summary> /// <param name="fi"></param> public static string Compress(FileInfo fi) { // Get the stream of the source file. using (FileStream inFile = fi.OpenRead()) { // Prevent compressing hidden and // already compressed files. if ((File.GetAttributes(fi.FullName) & FileAttributes.Hidden) != FileAttributes.Hidden & fi.Extension != ".gz") { // Create the compressed file. using (FileStream outFile = File.Create(fi.FullName + ".gz")) { using (GZipStream Compress = new GZipStream(outFile, CompressionMode.Compress)) { // Copy the source file into // the compression stream. inFile.CopyTo(Compress); return outFile.Name; //Console.WriteLine("Compressed {0} from {1} to {2} bytes.", // fi.Name, fi.Length.ToString(), outFile.Length.ToString()); } } } } return ""; }
四、将gz文件传递给eBay完成数据修改。这里需要用到eBay提供的示例代码。
需要到https://ebay.custhelp.com/ci/fattach/get/6896/1235418748/下载LMS示例代码。
首先要为项目添加服务引用:
Bulk Data Exchange API:http://developer.ebay.com/webservices/bulk-data-exchange/latest/BulkDataExchangeService.wsdl
File Transfer API:http://developer.ebay.com/webservices/file-transfer/latest/FileTransferService.wsdl
然后就需要根据示例代码完成与eBay的交互了。下面这幅图大致说明了与eBay的交互是怎样的一个流程。
1、通过Bulk Service的createUploadJob创建一个文件上传请求。
2、通过 File Service的uploadFile向eBay上传文件。
3、第2步只是向eBay上传了一个文件,并没有对文件的格式、大小、内容进行验证。所以这里需要调用Bulk Service的startUploadJob对文件进行导入。
4、通过Bulk Service的getJobStatus获取本次操作的状态。
5、通过 File Service的downloadFile获取本次操作结果。
private string SecurityToken; private bool envIsSandbox; private string ReqFormat = "XML"; private string ResponseFormat = "XML"; private string Version = "1.0.0"; private string BDXService = "BulkDataExchangeService"; private string FTService = "FileTransferService"; private string BDXconfigName = "BDXSandbox"; private string FTconfigName = "FTSSandbox"; //Set the endpoint config name for BDX depending on the environment selected private void setBDXconfigName() { if (envIsSandbox) BDXconfigName = "BDXSandbox"; else BDXconfigName = "BDXProduction"; } //Set the endpoint config name for FT depending on the environment selected private void setFTconfigName() { if (envIsSandbox) FTconfigName = "FTSSandbox"; else FTconfigName = "FTSProduction"; } //Sets HTTP headers private HttpRequestMessageProperty setHTTPHeaders() { HttpRequestMessageProperty httpRequest = new HttpRequestMessageProperty(); httpRequest.Headers.Add("X-EBAY-SOA-SECURITY-TOKEN", SecurityToken); httpRequest.Headers.Add("X-EBAY-SOA-SERVICE-VERSION", Version); httpRequest.Headers.Add("X-EBAY-SOA-REQUEST-DATA-FORMAT", ReqFormat); httpRequest.Headers.Add("X-EBAY-SOA-RESPONSE-DATA-FORMAT", ResponseFormat); httpRequest.Headers.Add("X-EBAY-SOA-MESSAGE-PROTOCOL", "SOAP12"); return httpRequest; } //Sets the custom headers i.e. the headers whose value depends on the API function to be executed private HttpRequestMessageProperty modifyHTTPHeaders(HttpRequestMessageProperty httpRequest, string ServiceName, string operation) { httpRequest.Headers.Remove("X-EBAY-SOA-SERVICE-NAME"); httpRequest.Headers.Remove("X-EBAY-SOA-OPERATION-NAME"); httpRequest.Headers.Add("X-EBAY-SOA-SERVICE-NAME", ServiceName); httpRequest.Headers.Add("X-EBAY-SOA-OPERATION-NAME", operation); return httpRequest; } public void UploadEndtoEnd(string JobType, string ReqfileName, string RespfileName) { string fileRefID = ""; //set the endpoint config names setBDXconfigName(); setFTconfigName(); //setHTTPHeaders HttpRequestMessageProperty httpRequest = this.setHTTPHeaders(); //Step 0. getJobs //有时候某个job未执行完毕,无法再次执行这一类型的job,这一步是将此job找出,结束他 modifyHTTPHeaders(httpRequest, BDXService, "getJobs"); GetJobsResponse getJobsResp = getJobs(httpRequest); if (getJobsResp.jobProfile != null) { if (getJobsResp.jobProfile.Count() > 0) { modifyHTTPHeaders(httpRequest, BDXService, "abortJob"); foreach (var j in getJobsResp.jobProfile) { abortJob(httpRequest, j.jobId); } } } //Step 1. createUploadJob modifyHTTPHeaders(httpRequest, BDXService, "createUploadJob"); CreateUploadJobResponse resp = createUploadJob(httpRequest, JobType); if ((resp != null) && (resp.ack.ToString().Equals("Success"))) { //Step 2. uploadFile modifyHTTPHeaders(httpRequest, FTService, "uploadFile"); if (uploadFile(httpRequest, resp.jobId, resp.fileReferenceId, ReqfileName)) { //Step 3. startUploadJob modifyHTTPHeaders(httpRequest, BDXService, "startUploadJob"); if (startUploadJob(httpRequest, resp.jobId)) { //Step 4. getJobStatus modifyHTTPHeaders(httpRequest, BDXService, "getJobStatus"); fileRefID = getJobStatus(true, httpRequest, resp.jobId); if (fileRefID != "") { //Step 5. downloadFile modifyHTTPHeaders(httpRequest, FTService, "downloadFile"); downloadFile(true, httpRequest, resp.jobId, fileRefID, RespfileName); } } } } } /// <summary> /// /// </summary> private GetJobsResponse getJobs(HttpRequestMessageProperty httpRequest) { BulkDataExchangeServicePortClient client = new BulkDataExchangeServicePortClient(BDXconfigName); using (OperationContextScope scope = new OperationContextScope(client.InnerChannel)) { OperationContext.Current.OutgoingMessageProperties.Add(HttpRequestMessageProperty.Name, httpRequest); GetJobsRequest request = new GetJobsRequest { jobStatus = new JobStatus[]{ JobStatus.Created } }; GetJobsResponse resp = client.getJobs(request); WriteBulkServiceErrors(resp.errorMessage, "getJobs"); return resp; } } /// <summary> /// /// </summary> private AbortJobResponse abortJob(HttpRequestMessageProperty httpRequest, string jobID) { BulkDataExchangeServicePortClient client = new BulkDataExchangeServicePortClient(BDXconfigName); using (OperationContextScope scope = new OperationContextScope(client.InnerChannel)) { OperationContext.Current.OutgoingMessageProperties.Add(HttpRequestMessageProperty.Name, httpRequest); AbortJobRequest request = new AbortJobRequest(); request.jobId = jobID; AbortJobResponse response = client.abortJob(request); return response; } } /// <summary> /// /// </summary> private CreateUploadJobResponse createUploadJob(HttpRequestMessageProperty httpRequest, string JobType) { //提示 调用createUploadJob 方法 Console.WriteLine("Calling createUploadJob\n"); #region 进行方法调用 BulkDataExchangeServicePortClient client = new BulkDataExchangeServicePortClient(BDXconfigName); using (OperationContextScope scope = new OperationContextScope(client.InnerChannel)) { OperationContext.Current.OutgoingMessageProperties.Add(HttpRequestMessageProperty.Name, httpRequest); //Create the request CreateUploadJobRequest req = new CreateUploadJobRequest(); //Supply additional parameters // The UUID must be unique. Once used, you can't use it again req.UUID = System.Guid.NewGuid().ToString(); req.uploadJobType = JobType; req.fileType = FileType.XML; //Get the response CreateUploadJobResponse resp = client.createUploadJob(req); //写入错误信息 WriteBulkServiceErrors(resp.errorMessage, "createUploadJob"); //如果成功提示 操作成功 if (resp.ack == LargeMerchantService.BulkService.AckValue.Success) { string txt = DateTime.Now + " :: " + JobType + " has been created with Job ID:" + resp.jobId + " and FileRefID: " + resp.fileReferenceId; Console.WriteLine(txt); } return resp; } #endregion } /// <summary> /// /// </summary> private bool uploadFile(HttpRequestMessageProperty httpRequest, string jobID, string fileRefID, string fileName) { Console.WriteLine("\n\nUploading the request file.\n"); FileTransferServicePortClient client = new FileTransferServicePortClient(FTconfigName); using (OperationContextScope scope = new OperationContextScope(client.InnerChannel)) { OperationContext.Current.OutgoingMessageProperties.Add(HttpRequestMessageProperty.Name, httpRequest); UploadFileRequest uploadReq = new UploadFileRequest(); uploadReq.fileAttachment = getFileAttachment(fileName); uploadReq.fileReferenceId = fileRefID; uploadReq.taskReferenceId = jobID; uploadReq.fileFormat = "gzip"; UploadFileResponse uploadResponse = client.uploadFile(uploadReq); if (uploadResponse.ack.ToString().Equals("Success")) { Console.WriteLine(DateTime.Now + " :: " + fileName + " has been successfully uploaded to the server.\n"); return true; } else { Console.WriteLine(DateTime.Now + " :: " + "Could not upload file to Server\n"); WriteFlieServiceErrors(uploadResponse.errorMessage, "uploadFile"); return false; } } } /// <summary> /// /// </summary> private bool startUploadJob(HttpRequestMessageProperty httpRequest, string jobID) { Console.WriteLine("\nCalling startUploadJob.\n"); BulkDataExchangeServicePortClient client = new BulkDataExchangeServicePortClient(BDXconfigName); using (OperationContextScope scope = new OperationContextScope(client.InnerChannel)) { OperationContext.Current.OutgoingMessageProperties.Add(HttpRequestMessageProperty.Name, httpRequest); StartUploadJobRequest req = new StartUploadJobRequest(); req.jobId = jobID.Trim(); StartUploadJobResponse resp = client.startUploadJob(req); if (resp.ack == LargeMerchantService.BulkService.AckValue.Success) { Console.WriteLine("Job with JobID " + jobID + " has been successfully scheduled. \n"); return true; } else { Console.WriteLine(DateTime.Now + " :: " + "This job might have already been scheduled.\n"); WriteBulkServiceErrors(resp.errorMessage, "startUploadJob"); return false; } } } /// <summary> /// /// </summary> private string getJobStatus(bool isUpload, HttpRequestMessageProperty httpRequest, string jobID) { string fileRefID = ""; Console.WriteLine("Calling getJobStatus every minute to see if the job has completed."); BulkDataExchangeServicePortClient client = new BulkDataExchangeServicePortClient(BDXconfigName); using (OperationContextScope scope = new OperationContextScope(client.InnerChannel)) { OperationContext.Current.OutgoingMessageProperties.Add(HttpRequestMessageProperty.Name, httpRequest); while (fileRefID == "") { //Sleep between successive requests Thread.Sleep(60000); GetJobStatusRequest req = new GetJobStatusRequest(); req.jobId = jobID.Trim(); GetJobStatusResponse resp = client.getJobStatus(req); WriteBulkServiceErrors(resp.errorMessage, "getJobStatus"); foreach (JobProfile jobProfile in resp.jobProfile) { if (jobProfile.fileReferenceId != null) { fileRefID = jobProfile.fileReferenceId; } else { string txt = DateTime.Now + " :: " + "Job Type: " + jobProfile.jobType + "::Job ID: " + jobProfile.jobId + "::" + "Job Status: " + jobProfile.jobStatus + "\n"; ; Console.WriteLine(txt); } } } return fileRefID; } } /// <summary> /// /// </summary> private void downloadFile(bool isUpload, HttpRequestMessageProperty httpRequest, string jobID, string fileRefID, string fileName) { Console.WriteLine("Downloading the response file.\n"); FileTransferServicePortClient client = new FileTransferServicePortClient(FTconfigName); using (OperationContextScope scope = new OperationContextScope(client.InnerChannel)) { OperationContext.Current.OutgoingMessageProperties.Add(HttpRequestMessageProperty.Name, httpRequest); DownloadFileRequest downloadReq = new DownloadFileRequest(); downloadReq.fileReferenceId = fileRefID; downloadReq.taskReferenceId = jobID; DownloadFileResponse downloadResponse = client.downloadFile(downloadReq); if (downloadResponse.ack.ToString().Equals("Success")) { FileAttachment attachment = downloadResponse.fileAttachment; saveFileAttachment(attachment, fileName); Console.WriteLine(DateTime.Now + " :: " + "File was successfully downloaded to " + fileName); } else { Console.WriteLine(DateTime.Now + " :: " + "Problem downloading the file."); WriteFlieServiceErrors(downloadResponse.errorMessage, "downloadFile"); } } } #region "FileAttachment Functions" /// <summary> /// /// </summary> /// <param name="fileName"></param> /// <returns></returns> private FileAttachment getFileAttachment(String fileName) { FileAttachment attachment = new FileAttachment(); FileStream fs = File.OpenRead(fileName); byte[] data = new byte[fs.Length]; fs.Read(data, 0, data.Length); attachment.Data = data; attachment.SizeSpecified = true; attachment.Size = fs.Length; return attachment; } /// <summary> /// /// </summary> /// <param name="attachment"></param> /// <param name="fileName"></param> private void saveFileAttachment(FileAttachment attachment, String fileName) { FileStream fs = File.Create(fileName); BinaryWriter writer = new BinaryWriter(fs); writer.Write(attachment.Data); writer.Close(); fs.Close(); } #endregion #region errorWrite /// <summary> /// /// </summary> /// <param name="errors"></param> private void WriteBulkServiceErrors(LargeMerchantService.BulkService.ErrorData[] errors, string methodName) { if (errors != null) { foreach (LargeMerchantService.BulkService.ErrorData e in errors) { Console.WriteLine("Error:" + e.message + "---------" + methodName); } } } /// <summary> /// /// </summary> /// <param name="errors"></param> private void WriteFlieServiceErrors(LargeMerchantService.FlieService.ErrorData[] errors, string methodName) { if (errors != null) { foreach (LargeMerchantService.FlieService.ErrorData e in errors) { Console.WriteLine("Error:" + e.message + "---------" + methodName); } } } #endregion