由于近期要做快手小店开放平台的对接,获取快手小店的订单,商品等进行管理.所以就需要用到接口.但是快手小店开放平台open.kwaixiaodian.com只有java的sdk,我们开发的后端都是用的C#,而且是低版本.net环境.所以手撸了一套SDK.
包含目前的93个接口(完整)和121个类(完整)
手撸的C#.net2.0环境下写的快手小店开放平台的SDK提供下载-C#文档类资源-CSDN下载
一个下载订单的Request实体类
using System;
using System.Collections.Generic;
using System.Text;
using KuaishouSDK.Domain;
namespace KuaishouSDK.RequestAndResponse.订单API
{
/// <summary>
/// 获取订单列表(游标方式)
/// 获取订单列表(游标方式)
/// 授权:需要
/// </summary>
public class OpenSellerOrderPcursorListRequest : BaseKSRequest<OpenSellerOrderPcursorListResponse>
{
public override string GetApiName()
{
return "open.seller.order.pcursor.list";
}
Int32 _type;
/// <summary>
/// 订单状态,0未知 1 全部 2 待付款 3 待发货 4 待收货(已发货)5 已收货 6 交易成功订单 7 已关闭订单
/// 是否必须:是
/// </summary>
public Int32 type { get { return _type; } set { _type = value; } }
Int64 _currentPage;
/// <summary>
/// 当前页
/// 是否必须:是
/// </summary>
public Int64 currentPage { get { return _currentPage; } set { _currentPage = value; } }
Int32 _pageSize;
/// <summary>
/// 每页请求数量 最多一页100条
/// 是否必须:是
/// </summary>
public Int32 pageSize { get { return _pageSize; } set { _pageSize = value; } }
Nullable<Int32> _sort;
/// <summary>
/// 1时间降序 2时间升序 默认降序
/// 是否必须:否
/// </summary>
public Nullable<Int32> sort { get { return _sort; } set { _sort = value; } }
Nullable<Int32> _queryType;
/// <summary>
/// 1按创建时间查找 2按更新时间查找 默认创建时间
/// 是否必须:否
/// </summary>
public Nullable<Int32> queryType { get { return _queryType; } set { _queryType = value; } }
Int64 _beginTime;
/// <summary>
/// 订单生成时间的开始时间,单位毫秒, 不能小于90天前,且需要小于结束时间
/// 是否必须:是
/// </summary>
public Int64 beginTime { get { return _beginTime; } set { _beginTime = value; } }
Int64 _endTime;
/// <summary>
/// 订单生成时间的截止时间,单位毫秒, 不能小于90天前,且与开始时间的时间范围不大于1天 (与开始时间的时间范围建议做成随时可配置,该范围可能在活动期间随时变化,比如变成小时级或者分钟级)。受春节活动影响,在2.1 10:00~2.5 10:00以及2.10 10:00~2.19 10:00期间订单的查询时间范围从1天缩短至1小时。
/// 是否必须:是
/// </summary>
public Int64 endTime { get { return _endTime; } set { _endTime = value; } }
Nullable<Int32> _cpsType;
/// <summary>
/// 分销类型 0-全部 1-普通订单 2-分销订单
/// 是否必须:否
/// </summary>
public Nullable<Int32> cpsType { get { return _cpsType; } set { _cpsType = value; } }
String _pcursor;
/// <summary>
/// 游标内容 第一次传空串,之后传上一次的pcursor返回值,若返回“nomore”则标识到底
/// 是否必须:是
/// </summary>
public String pcursor { get { return _pcursor; } set { _pcursor = value; } }
}
}
一个根据游标下载订单的Response实体类
using System;
using System.Collections.Generic;
using System.Text;
using KuaishouSDK.Domain;
namespace KuaishouSDK.RequestAndResponse.订单API
{
/// <summary>
/// 获取订单列表(游标方式)
/// 获取订单列表(游标方式)
/// 授权:需要
/// </summary>
public class OpenSellerOrderPcursorListResponse : KSResponse
{
Nullable<Int32> _result;
/// <summary>
/// 返回码
/// </summary>
Nullable<Int32> result { get { return _result; } set { _result = value; } }
MerchantOrderListData _data;
/// <summary>
/// 订单列表
/// </summary>
public MerchantOrderListData data { get { return _data; } set { _data = value; } }
String _error_msg;
/// <summary>
/// 错误码描述
/// </summary>
public String error_msg { get { return _error_msg; } set { _error_msg = value; } }
}
}
部分数据类型的实体类
using System;
using System.Collections.Generic;
using System.Text;
namespace KuaishouSDK.Domain
{
/// <summary>
/// 订单列表
/// </summary>
public class MerchantOrderListData
{
Nullable<Int64> _currentPage;
/// <summary>
/// 当前页,仅第一次调用返回值有效
/// </summary>
public Nullable<Int64> currentPage { get { return _currentPage; } set { _currentPage = value; } }
Nullable<Int32> _pageSize;
/// <summary>
/// 一页数据量
/// </summary>
public Nullable<Int32> pageSize { get { return _pageSize; } set { _pageSize = value; } }
Nullable<Int64> _totalPage;
/// <summary>
/// 总页数,仅第一次调用返回值有效
/// </summary>
public Nullable<Int64> totalPage { get { return _totalPage; } set { _totalPage = value; } }
Nullable<Int64> _totalSize;
/// <summary>
/// 总数仅第一次调用返回值有效
/// </summary>
public Nullable<Int64> totalSize { get { return _totalSize; } set { _totalSize = value; } }
Nullable<Int64> _beginTime;
/// <summary>
/// 开始时间
/// </summary>
public Nullable<Int64> beginTime { get { return _beginTime; } set { _beginTime = value; } }
Nullable<Int64> _endTime;
/// <summary>
/// 结束时间
/// </summary>
public Nullable<Int64> endTime { get { return _endTime; } set { _endTime = value; } }
String _pcursor;
/// <summary>
/// 游标,nomore代表分页到底
/// </summary>
public String pcursor { get { return _pcursor; } set { _pcursor = value; } }
List<MerchantOrderInfoView> _orderInfoList;
/// <summary>
/// 订单列表
/// </summary>
public List<MerchantOrderInfoView> orderInfoList { get { return _orderInfoList; } set { _orderInfoList = value; } }
}
}
使用方法
首先,
要通过快手小店开放平台的授权管理页面 找到授权店铺的授权code复制过来,然后通过拼接url的方式获取token
根据token发起请求.跟现在主流的Restful的API请求方式基本都一样.如下是token请求的获取连接.因为比较简单也基本不会变化,所以就没写到sdk里面做成一个方法.
https://open.kwaixiaodian.com/oauth2/access_token?app_id=你的appid&grant_type=code&app_secret=你的appsecret&code=复制来的授权码
调用Api的相关代码
OpenSellerOrderPcursorListRequest orderListGetReq = new OpenSellerOrderPcursorListRequest();
orderListGetReq.type = 3;
orderListGetReq.pcursor = "";
orderListGetReq.pageSize = 100;
orderListGetReq.currentPage = 1;
DateTime end = DateTime.Now;
DateTime start = end.AddDays(-7);
orderListGetReq.beginTime = (long)(start - new DateTime(1970, 1, 1, 0, 0, 0)).TotalMilliseconds;
orderListGetReq.endTime = (long)(end - new DateTime(1970, 1, 1, 0, 0, 0)).TotalMilliseconds;
orderListGetReq.queryType = 1;
orderListGetReq.cpsType = 0;
OpenSellerOrderPcursorListResponse ordersListGetRsp = client.Execute(orderListGetReq, session);
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine(ordersListGetRsp.Body);
Console.ResetColor();
字段的所有的注释都是全的,但是重载了一下部分接口.比如字段名称为opreator的,改成了Opreator,因为C#中不支持变量名称为系统标识符,就好比你不能定义一个变量名字为int一样.
接口调用方法:
按照如下方式写个调用的测试函数或者是直接用到项目里面即可.
main函数演示如何创建sdk请求器对象,并且演示如何获取店铺信息,订单信息,服务订购情况等接口.
using KuaishouSDK;
using KuaishouSDK.RequestAndResponse.订单API;
using KuaishouSDK.RequestAndResponse.用户API;
using KuaishouSDK.RequestAndResponse.服务市场API;
using KuaishouSDK.RequestAndResponse.商品API;
using System;
using System.Collections.Generic;
using System.Text;
namespace 快手SDK测试CMD
{
class Program
{
static void Main(string[] args)
{
DefaultKSClient client = new DefaultKSClient("https://open.kwaixiaodian.com", "你自己的appid写在这", "你自己的appsecret写在这", "你自己的signsecret写在这");
string session = "通过拼接授权连接获取到的token写在这";
OpenUserSellerGetRequest req = new OpenUserSellerGetRequest();
OpenUserSellerGetResponse rsp = client.Execute(req, session);
if (rsp.result>1)
{
Console.WriteLine("发生错误{0}", rsp.result);
Console.ReadLine();
}
Console.WriteLine(string.Format("用户ID:{0}\r\nOpenId:{1}\r\n头像地址:{2}", rsp.data.sellerId,rsp.data.openId, rsp.data.head));
//Console.ReadLine();
OpenSellerOrderPcursorListRequest orderListGetReq = new OpenSellerOrderPcursorListRequest();
orderListGetReq.type = 3;
orderListGetReq.pcursor = "";
orderListGetReq.pageSize = 100;
orderListGetReq.currentPage = 1;
DateTime end = DateTime.Now;
DateTime start = end.AddDays(-7);
orderListGetReq.beginTime = (long)(start - new DateTime(1970, 1, 1, 0, 0, 0)).TotalMilliseconds;
orderListGetReq.endTime = (long)(end - new DateTime(1970, 1, 1, 0, 0, 0)).TotalMilliseconds;
orderListGetReq.queryType = 1;
orderListGetReq.cpsType = 0;
OpenSellerOrderPcursorListResponse ordersListGetRsp = client.Execute(orderListGetReq, session);
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine(ordersListGetRsp.Body);
Console.ResetColor();
OpenServiceMarketBuyerServiceInfoRequest breq = new OpenServiceMarketBuyerServiceInfoRequest();
breq.buyerOpenId = rsp.data.openId;
OpenServiceMarketBuyerServiceInfoResponse brsp = client.Execute(breq, session);
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine(brsp.Body);
Console.ResetColor();
OpenServiceMarketOrderListRequest olreq = new OpenServiceMarketOrderListRequest();
olreq.endTime = (long)(DateTime.Now - new DateTime(1970, 1, 1, 0, 0, 0)).TotalMilliseconds;
olreq.pageNum = 1;
olreq.pageSize = 100;
olreq.queryType = 1;
olreq.startTime = olreq.endTime - (30L * 24 * 60 * 60 * 1000);
OpenServiceMarketOrderListResponse olrsp = client.Execute(olreq, session);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(olrsp.Body);
Console.ResetColor();
OpenItemListGetRequest itemsGetReq = new OpenItemListGetRequest();
itemsGetReq.pageNumber = 1;
itemsGetReq.pageSize = 20;
OpenItemListGetResponse itemsGetRsp = client.Execute(itemsGetReq, session);
Console.ForegroundColor = ConsoleColor.DarkBlue;
Console.WriteLine(itemsGetRsp.Body);
Console.ResetColor();
Console.ReadLine();
}
}
}
2023年01月10日21:18:29补充一下
DefaultKSClient类的内容:
using Norman;
using Norman.FastJSON;
using KuaishouSDK;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
namespace KuaishouSDK
{
public class DefaultKSClient
{
#region 全局变量
/// <summary>
/// 遇到错误的时候的最大尝试次数,在应用未发布触发限流的时候也可使用此参数.如果应用限流,第一次调用会发生71005
/// 之类的71开头的错误,这样的错误需要重试一下
/// </summary>
const int maxReTryTime = 20;
#endregion
string url, client_id, client_secret, sign_secret, date_type;
public DefaultKSClient(string url, string client_id, string client_secret, string sign_secret, string date_type = "json")
{
this.url = url;
this.client_id = client_id;
this.client_secret = client_secret;
this.sign_secret = sign_secret;
this.date_type = date_type;
}
public T Execute<T>(IKSRequest<T> request) where T : KSResponse
{
return this.Execute(request, null, DateTime.Now);
}
public T Execute<T>(IKSRequest<T> request, string session) where T : KSResponse
{
return this.Execute(request, session, DateTime.Now);
}
/// <summary>
/// 执行拼多多请求 2021年7月3日12:13:59 如果遇到限流错误自动重新执行一次,最多执行5次
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="request"></param>
/// <param name="session"></param>
/// <param name="timestamp"></param>
/// <returns></returns>
public T Execute<T>(IKSRequest<T> request, string session, DateTime timestamp) where T : KSResponse
{
T ret = null;
int currentTime = 0;
while (currentTime < maxReTryTime)
{
ret = DoExecute<T>(request, session, timestamp);
if (ret != null && ret.IsError == false)
{
break;
}
else if (ret.ErrCode > 0 && ret.ErrCode < 70000)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("快手SDK捕获到API错误:{0},终止重试", ret == null ? "response非实体" : ret.Body);
Console.ResetColor();
break;
}
else if (ret.ErrCode > 70000)
{
Console.ForegroundColor = ConsoleColor.DarkRed;
Console.WriteLine("拼多多SDK捕获到应用限流错误:{0}", ret.ErrMsg);
Console.ResetColor();
}
currentTime++;
System.Threading.Thread.Sleep(111);
}
return ret;
}
private T DoExecute<T>(IKSRequest<T> request, string session, DateTime timestamp) where T : KSResponse
{
Type responseType = typeof(T);
T response = Activator.CreateInstance(responseType) as T;
#region 组装请求参数
WebUtils wu = new WebUtils();
Dictionary<string, string> param4UrlBuild = new Dictionary<string, string>();
Dictionary<string, string> waitSignParams = new Dictionary<string, string>();
Dictionary<string, string> requestParams = new Dictionary<string, string>();
#region 添加APPKEY
waitSignParams.Add("appkey", this.client_id);
#endregion
#region 添加时间戳
long timeStamp = (timestamp.ToUniversalTime().Ticks - 621355968000000000) / 10000;
timeStamp = 1628490455275;
if (waitSignParams.ContainsKey("timestamp") == false)
{
waitSignParams.Add("timestamp", timeStamp.ToString());
}
else
{
waitSignParams["timestamp"] = timeStamp.ToString();
}
#endregion
#region 添加session
if (session != null)
{
waitSignParams.Add("access_token", session);
}
#endregion
#region 添加请求的API版本号,目前版本为1
waitSignParams.Add("version", "1");
#endregion
#region 添加param
#region 使用反射,获取request中的所有属性的值,添加到param
Type requestType = request.GetType();
PropertyInfo[] properties = requestType.GetProperties();
if (properties == null)
{
}
else
{
foreach (PropertyInfo p in properties)
{
if (!requestParams.ContainsKey(p.Name))
{
object val = p.GetValue(request, null);
if (val != null)
{
string valStr = string.Format("{0}", val);
requestParams.Add(p.Name, valStr);
}
}
else
{
response.ErrMsg = string.Format("重复的参数信息:{0}", p.Name);
return response;
}
}
}
#endregion
string paramStr = JSON.ToJSON(requestParams, JSON.SimpllySettingParameters);
waitSignParams.Add("param", paramStr);
//if (requestParams.Count>0)
//{
// string paramStr = JSON.ToJSON(requestParams, JSON.SimpllySettingParameters);
// waitSignParams.Add("param", paramStr);
//}
//else
//{
// waitSignParams.Add("param", "");
//}
#endregion
#region 添加api名字
string apiName = request.GetApiName();
if (apiName == null || apiName.Length < 1)
{
response.ErrMsg = "无效的api,名字不能为空";
return response;
}
waitSignParams.Add("method", apiName);
#endregion
#region 添加签名算法 2021年8月9日14:33:49 这一步比较坑 解决了一个多小时
/*
* 最后找文档上的 支持中心->API测试工具,测试了一下用户API,看一下sign跟他实际调用的时候是不是一个
* 但是突然想到 是不是要先把参数中 先 加入signMethod然后再去签名. 结果真的是这样的. waitSignParam里面要先加入signMethod=MD5...妈的这个快手
*/
waitSignParams.Add("signMethod", "MD5");
#endregion
#region 添加sign信息
string sign = CreateSign(waitSignParams, "MD5", this.sign_secret);
//快手给的java代码改的,也能用,但是没用他的,用的自己写的sign = this.sign(waitSignParams, this.sign_secret, "MD5");
waitSignParams.Add("sign", sign);
//waitSignParams.Add("signMethod", "MD5");
#endregion
#region 重新构建url
string realUrl = null;
if (this.url != null)
{
this.url.TrimEnd('?');
this.url.TrimEnd('/');
string apiPath = apiName;
apiPath = apiPath.Replace('.', '/');
realUrl = string.Format("{0}/{1}", this.url, apiPath);
}
#endregion
string responseStr = wu.DoPost(realUrl, waitSignParams);
if (responseStr == null || responseStr.Length < 1)
{
response.ErrMsg = "无效的请求返回";
return response;
}
try
{
response.Body = responseStr;
//Dictionary<string, object> responseDic = JSON.ToObject<Dictionary<string, object>>(responseStr);
//#region 正文提取
//responseStr = responseStr.Substring(1, responseStr.Length - 2);
//int startPos = responseStr.IndexOf("{");
//int endPos = responseStr.LastIndexOf("}");
//responseStr = responseStr.Substring(startPos, endPos - startPos + 1);
//#endregion
JSON.FillObject(response, responseStr);
}
catch (Exception fillObjErr)
{
response.ErrMsg = "返回类型与Response类型不匹配,解析失败" + fillObjErr.Message;
return response;
}
#endregion
return response;
}
private string CreateSign(Dictionary<string, string> parameters, string signMethod, string signSecret)
{
// 第一步:把字典按Key的字母顺序排序
IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parameters, StringComparer.Ordinal);
// 第二步:把所有参数名和参数值串在一起
StringBuilder query = new StringBuilder();
foreach (KeyValuePair<string, string> kv in sortedParams)
{
if (!string.IsNullOrEmpty(kv.Key) && !string.IsNullOrEmpty(kv.Value))
{
if (query.Length > 0)
{
query.Append("&");
}
query.AppendFormat("{0}={1}", kv.Key, kv.Value);
}
}
query.AppendFormat("&signSecret={0}", this.sign_secret);
//query = new StringBuilder("access_token=xxx&appkey=ks123&method=open.xxx.xxx.xxx¶m={\"title\":\"短袖\", \"relItemId\":123456,\"categoryId\":12}&signMethod=MD5×tamp=1583271919000&version=1&signSecret=xxxxxx");
// 第四步:使用MD5/HMAC加密
byte[] bytes;
MD5 md5 = MD5.Create();
bytes = md5.ComputeHash(Encoding.UTF8.GetBytes(query.ToString()));
// 第五步:把二进制转化为大写的十六进制
StringBuilder result = new StringBuilder();
for (int i = 0; i < bytes.Length; i++)
{
result.Append(bytes[i].ToString("x2"));
}
return result.ToString();
}
#region 使用code换取token的时候是使用的另外的连接不是api请求连接.并且请求方式是get,所以做特殊处理
public Oauth2AccessTokenCreateResult AuthTokenCreate(string code)
{
Oauth2AccessTokenCreateResult ret = new Oauth2AccessTokenCreateResult();
string tokenGetUrl = "https://open.kwaixiaodian.com/oauth2/access_token";
Dictionary<string, string> param = new Dictionary<string, string>();
param.Add("app_id", this.client_id);
param.Add("app_secret", this.client_secret);
param.Add("grant_type", "code");
param.Add("code", code);
WebUtils wu = new WebUtils();
//tokenGetUrl = wu.BuildGetUrl(tokenGetUrl, param);
string retJson = wu.DoGet(tokenGetUrl, param);
JSON.FillObject(ret, retJson);
return ret;
}
#endregion
}
}