前文
一个简易的API调用框架
准备工作
关于钉钉机器人的API文档
点击这里
nuget安装apilay
新建项目
这种就不在赘述了
返回值
经过一些简单的测试发现几个接口,返回值都是一样的{"errmsg":"ok","errcode":0}
,
所以可以定义一个公共的返回值,
为了保证一些扩展性,可以继承 Dictionary
///
/// 返回值基类
///
public class ResponseResult : Dictionary
{
///
/// 错误消息, 请求正确返回"ok"
///
public string errmsg
{
get => this["errmsg"] as string;
set => this["errmsg"] = value;
}
///
/// 错误代码, 请求正确返回0
///
public int errcode
{
get => this["errcode"] is int i ? i : int.TryParse(this["errcode"] + "", out var code) ? code : int.MinValue;
set => this["errcode"] = value.ToString();
}
///
/// 返回值原文
///
public string OriginalResult { get; set; }
}
请求基类
定义一个请求基类,将一些公共部分提取出来,如Method
,ContentType
,Path
,access_token
参数,Json序列化,返回值判断等操作
public abstract class SendBase : ApRequest
{
///
/// 将返回值解析为 ResponseResult
///
/// http状态码
/// 响应正文
/// 用于获取响应头的委托方法
///
public sealed override ResponseResult GetData(int statusCode, byte[] content, Func getHeader)
{
if (content == null || content.Length == 0)
{
if (statusCode != 200 && statusCode != 0)
{
return new ResponseResult() { errcode = statusCode, errmsg = "请求服务器异常" };
}
return new ResponseResult() { errcode = int.MinValue + 1, errmsg = "服务器返回为空" };
}
var json = Encoding.UTF8.GetString(content, 0, content.Length);
try
{
var result = (ResponseResult)Units.ToJsonObject(json, typeof(ResponseResult));
result.OriginalResult = json;
return result;
}
catch (Exception e)
{
return new ResponseResult() { errcode = statusCode == 200 || statusCode == 0 ? int.MinValue : statusCode, errmsg = e.Message, OriginalResult = json };
}
}
///
/// url_query 参数
///
[QueryValue(Name = "access_token")]
public string AccessToken { get; set; }
public override string Method => "POST";
public override string ContentType => "application/json;charset=utf-8";
public override string Path => "/robot/send";
///
/// 提交正文内容
///
protected abstract object Content { get; }
///
/// json字符串转为字节
///
public override byte[] Body => Encoding.UTF8.GetBytes(Units.ToJsonString(Content));
}
定义会话
我这里将一个独立的机器人定义为一个会话,所以通过access_token
实例化,
另外可以在会话中设置请求根域名,方便切换环境,
根据需要包装Invoke
方法
public sealed class Robot : ApSession
{
public Robot(string accessToken)
=> AccessToken = accessToken ?? throw new ArgumentNullException(nameof(accessToken));
///
/// 机器人标识
///
public string AccessToken { get; }
///
/// 机器人api域名
///
public string BaseUrl => "https://oapi.dingtalk.com/";
///
/// 利用机器人发送消息
///
///
public async Task Send(SendBase request)
{
if (request == null) throw new ArgumentNullException(nameof(request));
request.AccessToken = AccessToken;
var result = await Invoke(BaseUrl, request, CancellationToken.None);
if (result.errcode != 0)
{
throw new ApRequestException(result.errcode, result.errmsg);
}
}
}
到这里基本对象都已经定义完了
先尝试定义一个最简单的markdown消息
继承
SendBase
按照要求构造正文参数
public sealed class SendMarkdown : SendBase
{
protected override object Content => new
{
msgtype = "markdown",
markdown = new
{
title = Title?.Length > 20 ? Title.Substring(0, 17) + "..." : Title ?? "",
text = Text ?? "",
}
};
///
/// markdown格式的消息
///
public string Text { get; set; }
///
/// 首屏会话透出的展示内容
///
public string Title { get; set; }
}
发送 markdown消息
自己建一个群,添加一个机器人
在机器人设置中获取机器人的accesstoken
测试代码
static void Main(string[] args)
{
var robot = new Robot("4aa82a4e65b81103****");
robot.Send(new SendMarkdown()
{
Title = "测试一下消息",
Text = "##标题一 \n> 引用 \n\n* 列表1\n* 列表2\n* 列表3"
}).Wait();
}
其他类型的消息
测试成功,就可以编写其他类型的消息了
根据文档写就好了
Text
public sealed class SendText : SendBase
{
public SendText(string text)
{
Text = text;
}
protected override object Content => new
{
msgtype = "text",
text = new
{
content = Text,
},
at = new
{
atMobiles = IsAtAll ? EmptyList : At ?? EmptyList,
isAtAll = IsAtAll,
},
};
///
/// 消息内容
///
public string Text { get; set; }
///
/// @所有人时:true,否则为:false
///
public bool IsAtAll { get; set; }
///
/// 被@人的手机号
///
public List At { get; set; } = new List();
static readonly List EmptyList = new List();
}
Link
public sealed class SendLink : SendBase
{
protected override object Content => new
{
msgtype = "link",
link = new
{
text = Text,
title = Title?.Length > 20 ? Title.Substring(0, 17) + "..." : Title ?? "",
picUrl = PictureUrl,
messageUrl = LinkUrl,
},
};
///
/// 消息内容。如果太长只会部分展示
///
public string Text { get; set; }
///
/// 消息标题
///
public string Title { get; set; }
///
/// 图片URL
///
public string PictureUrl { get; set; }
///
/// 点击消息跳转的URL
///
public string LinkUrl { get; set; }
}
SingleActionCard
public sealed class SendSingleActionCard : SendBase
{
protected override object Content => new
{
msgtype = "actionCard",
actionCard = new
{
title = Title?.Length > 20 ? Title.Substring(0, 17) + "..." : Title ?? "",
text = Text,
hideAvatar = HideAvatar ? "1" : "0",
btnOrientation = VerticalButton ? "0" : "1",
singleTitle = LinkText ?? "查看详情",
singleURL = LinkUrl,
},
};
///
/// 首屏会话透出的展示内容
///
public string Title { get; set; }
///
/// markdown格式的消息
///
public string Text { get; set; }
///
/// 是否隐藏发消息者头像
///
public bool HideAvatar { get; set; }
///
/// 是否竖直排列按钮, false为横向排列
///
public bool VerticalButton { get; set; } = true;
///
/// 链接文字
///
public string LinkText { get; set; }
///
/// 链接URL
///
public string LinkUrl { get; set; }
}
MultisActionCard
public sealed class SendMultisActionCard : SendBase
{
protected override object Content => new
{
msgtype = "actionCard",
actionCard = new
{
title = Title?.Length > 20 ? Title.Substring(0, 17) + "..." : Title ?? "",
text = Text,
hideAvatar = HideAvatar ? "1" : "0",
btnOrientation = VerticalButton ? "0" : "1",
btns = Links.Select(x => new
{
title = x.Key,
actionURL = x.Value,
}).ToList(),
},
};
///
/// 首屏会话透出的展示内容
///
public string Title { get; set; }
///
/// markdown格式的消息
///
public string Text { get; set; }
///
/// 是否隐藏发消息者头像
///
public bool HideAvatar { get; set; }
///
/// 是否竖直排列按钮, false为横向排列
///
public bool VerticalButton { get; set; } = true;
///
/// 超链接组
///
public Dictionary Links = new Dictionary();
}
FeedCard
public class SendFeedCard : SendBase
{
protected override object Content => new
{
msgtype = "feedCard",
feedCard = new { links = _items }
};
readonly List
测试
测试代码
static void Main(string[] args)
{
var robot = new Robot("4aa82a4e65b8110*****");
robot.Send(new SendMarkdown()
{
Title = "测试一下消息",
Text = "##标题一 \n> 引用 \n\n* 列表1\n* 列表2\n* 列表3"
}).Wait();
robot.Send(new SendText("测试Text")
{
At = { "15657966053" } // @我自己
}).Wait();
robot.Send(new SendLink()
{
Title = "测试Link",
Text = "测试Link",
LinkUrl = "http://baidu.com"
}).Wait();
robot.Send(new SendSingleActionCard()
{
Title = "测试SingleActionCard",
Text = "测试SingleActionCard",
LinkText = "点击我去百度",
LinkUrl = "http://baidu.com"
}).Wait();
robot.Send(new SendMultisActionCard()
{
Title = "测试MultisActionCard",
Text = "测试MultisActionCard",
HideAvatar = true,
VerticalButton = false,
Links =
{
["去百度"] = "http://baidu.com",
["去谷歌"] = "http://google.cn",
}
}).Wait();
var feedCard = new SendFeedCard();
feedCard.AddItem("去百度", "http://baidu.com", "https://www.baidu.com/img/bd_logo1.png");
feedCard.AddItem("去谷歌", "http://google.cn", "http://www.google.cn/landing/cnexp/google-search.png");
robot.Send(feedCard).Wait();
}
测试结果