訂餐系統之同步口碑外賣商家選單與點點送訂單
2015年餓了麼、百度外賣、美團外賣、口碑外賣幾家幾乎分完了外賣這碗羹,讓其他外賣網站幾乎舉步維艱,也讓那些蠢蠢欲動想進入外賣領域的人猶豫不決了(這估計是要砸我飯碗的節奏啊,ヾ(@⌒ー⌒@)ノ)。當然了,喝了外賣這碗羹,肯定得有“產物”,不然,還不被撐破了肚皮麼。對,這個"產物"就是外賣訂單,是大量的外賣訂單,商戶的配送員能力非常有限,於是第三方的配送公司如雨後春筍般的冒了出來,當然了,市場大,競爭也非常殘酷,有些剛冒出頭,就被"扼殺"在襁褓裡了;估計還有不少的沒找好“土壤”,壓根就沒冒出來;肯定還有好些隔岸觀火,躍躍欲試的主兒....反正是你方唱罷我登場,你有張良計我有過牆梯,好不熱鬧呢!!
熱鬧落盡,平淡歸真,不少配送公司才發現:指望某一個平臺,訂單有限,壯大何其難?再則,大部分使用者估計也和我一樣,沒有忠誠度,哪家優惠大,就去哪家。於是,一方面開始發展自己的業務(開發一個自己的app),其次介面多家平臺(每個平臺一個app)..... 就這樣,活下來的配送公司業務穩定了,訂單也穩定了,但是問題來了:排程人員得幾個平臺來回切換,操作還不一樣,配送員也得在幾個app中切換,你要知道每個配送員身上的訂單,還得費不小勁....於是,整合各家訂單,統一排程,集中配送,讓客服與配送員從其他平臺解放出來,重複單一動作,效率自然就高了!但是願望是美好的,現實是殘酷的:除了口碑有完整的對配送商的介面外(之前申請還非常麻煩,不知道現在好些了沒),美團外賣只對品牌商家開放了介面外,壓根沒有對配送商的介面;餓了麼找半天也沒找著相關介面....雖然現在沒有,但是我估計著,不久的將來這幾個肯定會有針對配送商的介面。既然現在只有口碑有相關介面,那這一系列文章就以口碑外賣介面為準。以後,如果其他平臺有了相關介面,我再給大家分享下。說得不好的地方,也請大家海涵、指出哈!
之前的文章《訂餐系統之自動確認淘點點訂單》已經介紹過相關應用的申請,及獲取授權賬號下的訂單,在這裡就贅述了,有興趣的可以瞭解下。
批量上傳與下載口碑商家選單在口碑商家後臺新增過選單的人都知道,商品只能一個個新增,如果只有1、2個商家還好,但是配送商要生存,就得不停的開店,開了店就得上傳商品,一個店少說也得有百八十個商品,如此激烈的競爭不開個三、五百家商家生存都難啊....於是,我們會看到這一幕:他們在不停的尋找店鋪,到口碑上開設店鋪,再一個個新增商品....然後再重複著,重複著,重複著...於是想起了我。下圖為批量上傳選單的過程,同時也可以同步口碑商家已經有選單到本地。這裡面有一個注意的地方就是:先上傳圖片到口碑,返回圖片地址,再上傳選單。如果是先上傳選單,再上傳圖片,特別麻煩。另外,就是介面中有些限制比如:原價不能小於現價,商品介紹不能少於5個字等等之類的,瞭解就可以了。
程式碼方面都是呼叫TopSdk.dll封裝的方法,自己在簡單的封裝下,方便使用和複用。我的程式碼結構如下圖:
baseTopAPI.cs 主要是初始化 appkey,appsecret,top_session幾個必要的引數,方便子類呼叫介面。
TopSysFoodSort.cs 主要是處理分類相關的介面。
FoodTopAPI.cs 主要是處理商品相關介面的。
FoodSortSync.cs 客戶端呼叫,同步分類
FoodSync.cs 客戶端呼叫,同步商品
詳情程式碼如下,寫得不好,有需要的可以看下:
using Hangjing.Model; using Hangjing.SQLServerDAL; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Web; using Top.Api; /// <summary> /// 淘點點呼叫介面基類 /// </summary> public class baseTopAPI { public SortedList<string, string> parameters = null; public string appkey { get; set; } public string appsecret { get; set; } public string top_session { get; set; } /// <summary> /// 系統商家編號 /// </summary> public int shopid { get; set; } /// <summary> /// 淘寶商家編號 /// </summary> public long TaoBaoId { get; set; } /// <summary> /// 介面地址 /// </summary> public string serverurl = "http://gw.api.taobao.com/router/rest?"; public baseTopAPI(int shopid) { parameters = new SortedList<string, string>(); IList<taobaoAPIAcountInfo> apis = SectionProxyData.GettaobaoAPIAcountList(); TogoInfo shop = new Togo().GetModelByDataId(shopid); if (shop.shoptype != 0 && shop.ReveVar1 != "") { foreach (var key in apis) { if (shop.shoptype == key.ID) { this.appkey = key.classname; this.appsecret = key.pic; this.top_session = key.hovepic; break; } } } TaoBaoId = Convert.ToInt64(shop.ReveVar1); this.shopid = shopid; } /// <summary> /// 用任意一個api即可 /// </summary> public baseTopAPI() { parameters = new SortedList<string, string>(); taobaoAPIAcountInfo apiconfig = SectionProxyData.GettaobaoAPIAcountList().First(); this.appkey = apiconfig.classname; this.appsecret = apiconfig.pic; this.top_session = apiconfig.hovepic; } }baseTopAPI
using Hangjing.Common; using Hangjing.Model; using Hangjing.SQLServerDAL; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Web; using Top.Api; using Top.Api.Request; using Top.Api.Response; /// <summary> /// 商品分類 /// </summary> public class FoodSortTopAPI : baseTopAPI { public FoodSortTopAPI(int shopid) : base(shopid) { } public apiResultInfo CallServer(int operate, string data) { apiResultInfo rs = new apiResultInfo(); if (TaoBaoId == 0) { rs.error_info = "top_session,或者淘寶商家編號未設定未設定"; rs.error_no = "1"; return rs; } ITopClient client = new DefaultTopClient(serverurl, appkey, appsecret, "json"); WaimaiCategoryOperateRequest req = new WaimaiCategoryOperateRequest(); req.ShopId = TaoBaoId; req.Operate = operate; if (operate > 0) { req.Data = data; } WaimaiCategoryOperateResponse response = client.Execute(req, top_session); if (response != null && response.Result != null) { rs.error_info = ""; rs.error_no = "0"; rs.data = response.Result; } else { rs.error_info = "接口出錯:" + response.ErrMsg; rs.error_no = "1"; } return rs; } public TopFoodSort QueryFoodSort() { TopFoodSort model = new TopFoodSort(); apiResultInfo rs = CallServer(0, ""); if (rs.error_no == "0") { model = JsonConvert.DeserializeObject<TopFoodSort>(rs.data.ToString()); if (model == null) { model = new TopFoodSort(); model.cates = new List<CatesItem>(); } model.msg = ""; } else { model.msg = rs.error_info; } return model; } /// <summary> /// 刪除分類 /// </summary> /// <param name="shopid"></param> /// <param name="Top_sort_cids">多個用逗號分開</param> /// <returns></returns> public apiResultInfo DelFoodSort(string Top_sort_cids) { TopFoodSort add = new TopFoodSort(); add.cates = new List<CatesItem>(); foreach (var sortid in Top_sort_cids.Split(',')) { CatesItem item = new CatesItem(); item.cid = sortid; add.cates.Add(item); } string data = JsonConvert.SerializeObject(add); apiResultInfo rs = CallServer(3, data); return rs; } /// <summary> /// 新增一個分類 /// </summary> /// <param name="shopid"></param> /// <param name="addsort"></param> /// <returns></returns> public apiResultInfo CreateOneFoodSort(EFoodSortInfo addsort) { IList<EFoodSortInfo> addsorts = new List<EFoodSortInfo>(); addsorts.Add(addsort); return CreateFoodSortS(addsorts); } /// <summary> /// 新增一個分類 /// </summary> /// <param name="shopid"></param> /// <param name="addsort"></param> /// <returns></returns> public apiResultInfo CreateFoodSortS(IList<EFoodSortInfo> addsorts) { TopFoodSort add = new TopFoodSort(); add.cates = new List<CatesItem>(); foreach (var addsort in addsorts) { CatesItem item = new CatesItem(); item.name = addsort.SortName; item.order = 100000 - addsort.Jorder; //這裡用10000-排序得到新的排序,是因為系統裡是從降序,淘點點是升序 add.cates.Add(item); } string data = JsonConvert.SerializeObject(add); apiResultInfo rs = CallServer(1, data); return rs; } }FoodSortTopAPI
using Hangjing.Common; using Hangjing.Model; using Hangjing.SQLServerDAL; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Web; using Top.Api; using Top.Api.Request; using Top.Api.Response; /// <summary> /// 淘寶商品介面 /// </summary> public class FoodTopAPI : baseTopAPI { public FoodTopAPI(int shopid) : base(shopid) { } public apiResultInfo AddFood(EFoodInfo food, long syssortid, string shopsortid) { apiResultInfo rs = new apiResultInfo(); if (TaoBaoId == 0) { rs.error_info = "top_session,或者淘寶商家編號未設定未設定"; rs.error_no = "1"; return rs; } ITopClient client = new DefaultTopClient(serverurl, appkey, appsecret); WaimaiItemAddRequest req = new WaimaiItemAddRequest(); req.Title = food.Name; req.Price = food.Price.ToString(); req.Oriprice = food.Price.ToString(); req.Quantity = 9999L; req.Picurl = food.Picture.Length == 0 ? SectionProxyData.GetSetValue(39):food.Picture; req.Goodsno = ""; req.Auctionstatus = 0L; req.Limitbuy = 0L; req.Categoryid = syssortid; req.Auctiondesc = food.Introduce; req.Shopids = TaoBaoId.ToString(); req.Categoryids = shopsortid; WaimaiItemAddResponse response = client.Execute(req, top_session); if (response != null && response.Result != null) { rs.error_info = ""; rs.error_no = "0"; rs.data = response.Result; rs.topFoodID = response.Result.ResultData; } else { rs.error_info = "接口出錯:" + response.ErrMsg + "(" + response.ErrCode + ")"; rs.error_no = "1"; HJlog.toLog("AddFood錯誤:" + rs.error_info); } return rs; } public apiResultInfo AddFood(int foodid, long syssortid, string shopsortid) { EFoodInfo addfood = new EFood().GetModel(foodid); return AddFood(addfood, syssortid, shopsortid); } /// <summary> /// 操作商品 /// </summary> /// <param name="ids">待操作寶貝id,多個以英文逗號分隔</param> /// <param name="operate">操作型別(1上架2下架3刪除)</param> /// <returns></returns> public void operateFood(string ids, int operate) { int maxsize = 20;//淘點點一次能刪除20個商品 string reqids = ""; string[] foodids = ids.Split(','); if (foodids.Length > maxsize) { for (int i = 0; i < foodids.Length; i++) { reqids += foodids[i] + ","; if (i % (maxsize -1) == 0 && i > 0) { operateFoodCallServer(WebUtility.dellast(reqids),operate); reqids = ""; } } } if (reqids != "") { operateFoodCallServer(WebUtility.dellast(reqids), operate); } } /// <summary> /// 操作商品 /// </summary> /// <param name="ids">待操作寶貝id,多個以英文逗號分隔</param> /// <param name="operate">操作型別(1上架2下架3刪除)</param> /// <returns></returns> public apiResultInfo operateFoodCallServer(string ids, int operate) { apiResultInfo rs = new apiResultInfo(); ITopClient client = new DefaultTopClient(serverurl, appkey, appsecret); WaimaiItemOperateRequest req = new WaimaiItemOperateRequest(); req.Ids = ids; req.O = operate; WaimaiItemOperateResponse response = client.Execute(req, top_session); if (response != null && response.BatchOperateResult != null) { rs.error_info = ""; rs.error_no = "0"; rs.data = response.BatchOperateResult; } else { rs.error_info = "接口出錯:" + response.ErrMsg + "(" + response.ErrCode + ")"; rs.error_no = "1"; HJlog.toLog("operateFood錯誤:" + rs.error_info); } return rs; } /// <summary> /// 查詢商品,如果keyword為空,就不加此引數 /// </summary> /// <param name="keyword"></param> /// <returns></returns> public List<Top.Api.Domain.TopAuction> queryFood(string keyword) { ITopClient client = new DefaultTopClient(serverurl, appkey, appsecret); WaimaiItemlistGetRequest req = new WaimaiItemlistGetRequest(); req.Shopid = TaoBaoId; req.SalesStatus = 0L; if (keyword != "") { req.Keyword = keyword; } req.PageNo = 1L; req.PageSize = 200L; req.Fields = "itemid,title,price,oriprice,goods_no,auction_desc,pic_url,category_id"; WaimaiItemlistGetResponse response = client.Execute(req, top_session); if (response != null) { long total_results = response.TotalResults; return response.DataList; } return null; } }FoodTopAPI
using System; using System.Collections.Generic; using System.Linq; using System.Web; using Hangjing.Model; using Hangjing.SQLServerDAL; using Hangjing.DBUtility; using System.Data; using Hangjing.Common; /// <summary> /// 商品分類同步 /// </summary> public class FoodSortSync { FoodSortTopAPI sortapid = null; public FoodSortSync(int shopid) { sortapid = new FoodSortTopAPI(shopid); } /// <summary> /// 從淘點點同步到系統 /// </summary> /// <returns></returns> public apiResultInfo SyncFromTop() { TopFoodSort Topsorts = sortapid.QueryFoodSort(); string sql = " DELETE dbo.EFoodSort WHERE TogoNUm = "+sortapid.shopid; WebUtility.excutesql(sql); IList<DBEFoodSortInfo> websorts = new List<DBEFoodSortInfo>(); foreach (var topsort in Topsorts.cates) { DBEFoodSortInfo websort = new DBEFoodSortInfo(); websort.SortName = topsort.name; websort.JOrder = 10000 - topsort.order; websort.TogoNUm = sortapid.shopid; websort.topsortid = topsort.cid; websorts.Add(websort); } DataTable dt = CollectionHelper.ConvertTo(websorts, "EFoodSort"); SQLHelper.SqlBulkCopyData(dt); return null; } /// <summary> /// 上傳選單到口碑 /// </summary> /// <returns></returns> public apiResultInfo Upload() { TopFoodSort Topsorts = sortapid.QueryFoodSort(); IList<EFoodSortInfo> Sysorts = new EFoodSort().GetListByTogoNum(sortapid.shopid); //1,刪除系統中沒有,而淘寶中有的分類 string notinsyssortids = ""; foreach (var topsort in Topsorts.cates) { if (Sysorts.Where(a => a.SortName == topsort.name).ToList().Count == 0) { notinsyssortids += topsort.cid + ","; } } notinsyssortids = WebUtility.dellast(notinsyssortids); if (notinsyssortids != "") { sortapid.DelFoodSort(notinsyssortids); } //新增系統中有,而淘寶中沒有的 IList<EFoodSortInfo> notintopsorts = new List<EFoodSortInfo>(); foreach (var syssort in Sysorts) { if (Topsorts.cates.Where(a => a.name == syssort.SortName).ToList().Count == 0) { notintopsorts.Add(syssort); } } if (notintopsorts.Count > 0) { sortapid.CreateFoodSortS(notintopsorts); } return null; } }FoodSortSync
using System; using System.Collections.Generic; using System.Linq; using System.Web; using Hangjing.Model; using Hangjing.SQLServerDAL; using System.Data; using Hangjing.DBUtility; using Hangjing.Common; /// <summary> /// 商品同步 /// </summary> public class FoodSync { FoodTopAPI api = null; public FoodSync(int shopid) { api = new FoodTopAPI(shopid); } /// <summary> /// 從淘點點同步選單 /// </summary> /// <returns></returns> public apiResultInfo SyncFromTop() { List<Top.Api.Domain.TopAuction> Topfoods = api.queryFood(""); IList<EFoodSortInfo> websorts = new EFoodSort().GetListByTogoNum(api.shopid); string sql = " DELETE dbo.EFood WHERE TogoNum = " + api.shopid; WebUtility.excutesql(sql); IList<DBEFoodInfo> webfoods = new List<DBEFoodInfo>(); foreach (var topfood in Topfoods) { DBEFoodInfo food = new DBEFoodInfo(); food.FoodID = 0; food.Name = topfood.Title; food.Picture = topfood.PicUrl; food.FoodType = 0; foreach (var websort in websorts) { if (topfood.CategoryId.ToString() == websort.topsortid) { food.FoodType = websort.SortID; break; } } food.Price = Convert.ToDecimal(topfood.Price); food.Introduce = topfood.AuctionDesc; food.Discount = 10; food.TogoNum = api.shopid; food.Total = 0; food.IsDelete = 0; food.WeekDay = ""; food.istuan = 0; food.nprice = food.Price; food.allnum = 0; food.joinnum = 0; food.salecount = 0; food.PackageFree = 0; food.IstuanInve = 0; webfoods.Add(food); } DataTable dt = CollectionHelper.ConvertTo(webfoods, "EFood"); SQLHelper.SqlBulkCopyData(dt); return null; } /// <summary> /// 上傳選單 /// </summary> /// <returns></returns> public apiResultInfo Upload() { List<Top.Api.Domain.TopAuction> Topfoods = api.queryFood(""); IList<EFoodInfo> Sysorts = new EFood().GetList(1000, 1, " IsDelete = 0 and TogoNum=" + api.shopid, "Total", 1); //1,刪除系統中沒有,而淘寶中有的分類 string notinsyids = ""; foreach (var topsort in Topfoods) { if (Sysorts.Where(a => a.Name == topsort.Title).ToList().Count == 0) { notinsyids += topsort.ItemId + ","; } } notinsyids = WebUtility.dellast(notinsyids); if (notinsyids != "") { api.operateFood(notinsyids, 3); } FoodSortTopAPI sortapi = new FoodSortTopAPI(api.shopid); TopFoodSort Topsorts = sortapi.QueryFoodSort(); //新增系統中有,而淘寶中沒有的 foreach (var syssort in Sysorts) { if (Topfoods.Where(a => a.Title == syssort.Name).ToList().Count == 0) { foreach (var tsort in Topsorts.cates) { if (tsort.name == syssort.SortName) { apiResultInfo rs = api.AddFood(syssort, Convert.ToInt64(syssort.Weekday), tsort.cid); if (rs.error_no == "0") { WebUtility.excutesql("UPDATE dbo.EFood SET topFoodID = '"+rs.topFoodID+"' WHERE FoodID = "+ syssort.FoodID); } break; } } } } return null; } }FoodSync 批量營業與歇業口碑商家
口碑的商家有這樣的一個算是要求吧:如果商家在營業中,一天內多次拒單,或者超時不接單,會讓你停業整改一天;再則,口碑的商家後臺沒有批量營業與歇業店鋪的功能(想不通是為什麼....)天氣晴朗,配送高效,訂單自然是多多益善,少數實在不能配送的,與使用者友好協商,讓他取消,多數還是非常理解的;但遇到打雷、颳風、下雨....等惡劣天氣時,訂單肯定是一增再增,配送效率一降再降,這時,不能配送的訂單可能佔了很大的比重,再一個個去溝通,這個成本就非常高了,關掉部分店鋪是唯一有效的方法,但等你一個個去口碑商家後臺關店時,訂單已經堆積成山了,投訴也一個接一個的....不生在其中 不知其中苦,還是看程式碼吧,主要就是呼叫介面,沒有太多可說的!
/// <summary> /// 商戶營業 返回:成功:0,失敗:1 /// </summary> public string shopOpen(string shopid, string sessionkey) { string appkey = ""; string appsecret = ""; string top_session = ""; IList<taobaoAPIAcountInfo> apis = SectionProxyData.GettaobaoAPIAcountList(); foreach (var item in apis) { if (item.hovepic == sessionkey) { top_session = item.hovepic; appsecret = item.pic; appkey = item.classname; break; } } //3.生成url string url = "http://gw.api.taobao.com/router/rest?";//線上環境: http://gw.api.taobao.com/router/rest 測試環境: http://gw.sandbox.taobao.com/router/rest ITopClient client = new DefaultTopClient(url, appkey, appsecret, "json"); WaimaiShopOpenRequest req = new WaimaiShopOpenRequest(); req.Shopid = Convert.ToInt64(shopid); WaimaiShopOpenResponse response = client.Execute(req, top_session); string rs = "1"; if (response != null) { rs = response.RetCode; ; } return rs; }shopOpen
客戶端呼叫如下,使用 BackgroundWorker 響應操作是否完成。
/// <summary> /// 歇業 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void offline_Click(object sender, EventArgs e) {