【C#】簡(jiǎn)易API調(diào)用框架(blqw.Apilay)示例 —— 釘釘機(jī)器人

前文

一個(gè)簡(jiǎn)易的API調(diào)用框架

準(zhǔn)備工作

關(guān)于釘釘機(jī)器人的API文檔

點(diǎn)擊這里

nuget安裝apilay

新建項(xiàng)目

這種就不在贅述了


返回值

經(jīng)過(guò)一些簡(jiǎn)單的測(cè)試發(fā)現(xiàn)幾個(gè)接口狱意,返回值都是一樣的{"errmsg":"ok","errcode":0}椒涯,
所以可以定義一個(gè)公共的返回值,
為了保證一些擴(kuò)展性先口,可以繼承 Dictionary<string,object>

/// <summary>
/// 返回值基類(lèi)
/// </summary>
public class ResponseResult : Dictionary<string, object>
{
    /// <summary>
    /// 錯(cuò)誤消息, 請(qǐng)求正確返回"ok"
    /// </summary>
    public string errmsg
    {
        get => this["errmsg"] as string;
        set => this["errmsg"] = value;
    }
    /// <summary>
    /// 錯(cuò)誤代碼, 請(qǐng)求正確返回0
    /// </summary>
    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();
    }
    /// <summary>
    /// 返回值原文
    /// </summary>
    public string OriginalResult { get; set; }
}

請(qǐng)求基類(lèi)

定義一個(gè)請(qǐng)求基類(lèi)赚瘦,將一些公共部分提取出來(lái),如MethodContentTypePath理澎,access_token參數(shù),Json序列化曙寡,返回值判斷等操作

public abstract class SendBase : ApRequest<ResponseResult>
{
    /// <summary>
    /// 將返回值解析為 ResponseResult
    /// </summary>
    /// <param name="statusCode"> http狀態(tài)碼 </param>
    /// <param name="content"> 響應(yīng)正文 </param>
    /// <param name="getHeader"> 用于獲取響應(yīng)頭的委托方法 </param>
    /// <returns></returns>
    public sealed override ResponseResult GetData(int statusCode, byte[] content, Func<string, string> getHeader)
    {
        if (content == null || content.Length == 0)
        {
            if (statusCode != 200 && statusCode != 0)
            {
                return new ResponseResult() { errcode = statusCode, errmsg = "請(qǐng)求服務(wù)器異常" };
            }
            return new ResponseResult() { errcode = int.MinValue + 1, errmsg = "服務(wù)器返回為空" };
        }
        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 };
        }
    }

    /// <summary>
    /// url_query 參數(shù) 
    /// </summary>
    [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";
    /// <summary>
    /// 提交正文內(nèi)容
    /// </summary>
    protected abstract object Content { get; }

    /// <summary>
    /// json字符串轉(zhuǎn)為字節(jié)
    /// </summary>
    public override byte[] Body => Encoding.UTF8.GetBytes(Units.ToJsonString(Content));
}

定義會(huì)話

我這里將一個(gè)獨(dú)立的機(jī)器人定義為一個(gè)會(huì)話,所以通過(guò)access_token實(shí)例化寇荧,
另外可以在會(huì)話中設(shè)置請(qǐng)求根域名举庶,方便切換環(huán)境,
根據(jù)需要包裝Invoke方法

public sealed class Robot : ApSession
{
    public Robot(string accessToken) 
        => AccessToken = accessToken ?? throw new ArgumentNullException(nameof(accessToken));

    /// <summary>
    /// 機(jī)器人標(biāo)識(shí)
    /// </summary>
    public string AccessToken { get; }

    /// <summary>
    /// 機(jī)器人api域名
    /// </summary>
    public string BaseUrl => "https://oapi.dingtalk.com/";

    /// <summary>
    /// 利用機(jī)器人發(fā)送消息
    /// </summary>
    /// <param name="request"></param>
    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);
        }
    }
}

到這里基本對(duì)象都已經(jīng)定義完了

先嘗試定義一個(gè)最簡(jiǎn)單的markdown消息

文檔說(shuō)明

繼承 SendBase 按照要求構(gòu)造正文參數(shù)

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 ?? "",
        }
    };

    /// <summary>
    /// markdown格式的消息 
    /// </summary>
    public string Text { get; set; }
    /// <summary>
    /// 首屏?xí)捦赋龅恼故緝?nèi)容 
    /// </summary>
    public string Title { get; set; }
}

發(fā)送 markdown消息

自己建一個(gè)群揩抡,添加一個(gè)機(jī)器人



在機(jī)器人設(shè)置中獲取機(jī)器人的accesstoken


測(cè)試代碼

static void Main(string[] args)
{
    var robot = new Robot("4aa82a4e65b81103****");
    robot.Send(new SendMarkdown()
    {
        Title = "測(cè)試一下消息",
        Text = "##標(biāo)題一 \n> 引用  \n\n* 列表1\n* 列表2\n* 列表3"
    }).Wait();
}

其他類(lèi)型的消息

測(cè)試成功户侥,就可以編寫(xiě)其他類(lèi)型的消息了
根據(jù)文檔寫(xiě)就好了

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,
        },
    };

    /// <summary>
    /// 消息內(nèi)容
    /// </summary>
    public string Text { get; set; }

    /// <summary>
    /// @所有人時(shí):true,否則為:false
    /// </summary>
    public bool IsAtAll { get; set; }

    /// <summary>
    /// 被@人的手機(jī)號(hào)
    /// </summary>
    public List<string> At { get; set; } = new List<string>();

    static readonly List<string> EmptyList = new List<string>();
}

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,
        },
    };

    /// <summary>
    /// 消息內(nèi)容。如果太長(zhǎng)只會(huì)部分展示
    /// </summary>
    public string Text { get; set; }
    /// <summary>
    /// 消息標(biāo)題
    /// </summary>
    public string Title { get; set; }
    /// <summary>
    /// 圖片URL
    /// </summary>
    public string PictureUrl { get; set; }
    /// <summary>
    /// 點(diǎn)擊消息跳轉(zhuǎn)的URL
    /// </summary>
    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,
        },
    };

    /// <summary>
    /// 首屏?xí)捦赋龅恼故緝?nèi)容 
    /// </summary>
    public string Title { get; set; }
    /// <summary>
    /// markdown格式的消息 
    /// </summary>
    public string Text { get; set; }
    /// <summary>
    /// 是否隱藏發(fā)消息者頭像
    /// </summary>
    public bool HideAvatar { get; set; }
    /// <summary>
    /// 是否豎直排列按鈕, false為橫向排列 
    /// </summary>
    public bool VerticalButton { get; set; } = true;
    /// <summary>
    /// 鏈接文字
    /// </summary>
    public string LinkText { get; set; }
    /// <summary>
    /// 鏈接URL
    /// </summary>
    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(),
        },
    };


    /// <summary>
    /// 首屏?xí)捦赋龅恼故緝?nèi)容 
    /// </summary>
    public string Title { get; set; }
    /// <summary>
    /// markdown格式的消息 
    /// </summary>
    public string Text { get; set; }
    /// <summary>
    /// 是否隱藏發(fā)消息者頭像
    /// </summary>
    public bool HideAvatar { get; set; }
    /// <summary>
    /// 是否豎直排列按鈕, false為橫向排列 
    /// </summary>
    public bool VerticalButton { get; set; } = true;
    /// <summary>
    /// 超鏈接組
    /// </summary>
    public Dictionary<string, string> Links = new Dictionary<string, string>();
    
}

FeedCard

public class SendFeedCard : SendBase
{
    protected override object Content => new
    {
        msgtype = "feedCard",
        feedCard = new { links = _items }
    };

    readonly List<object> _items = new List<object>();

    /// <summary>
    /// 添加項(xiàng)
    /// </summary>
    /// <param name="title"> 信息文本 </param>
    /// <param name="linkUrl"> 跳轉(zhuǎn)鏈接 </param>
    /// <param name="pictureUrl"> 圖片URL </param>
    public void AddItem(string title, string linkUrl, string pictureUrl)
    {
        if (string.IsNullOrWhiteSpace(title)) throw new ArgumentNullException(nameof(title));
        if (string.IsNullOrWhiteSpace(linkUrl)) throw new ArgumentNullException(nameof(linkUrl));
        if (string.IsNullOrWhiteSpace(pictureUrl)) throw new ArgumentNullException(nameof(pictureUrl));
        _items.Add(new { title, messageURL = linkUrl, picURL = pictureUrl });
    }
}

測(cè)試

測(cè)試代碼

static void Main(string[] args)
{
    var robot = new Robot("4aa82a4e65b8110*****");
    robot.Send(new SendMarkdown()
    {
        Title = "測(cè)試一下消息",
        Text = "##標(biāo)題一 \n> 引用  \n\n* 列表1\n* 列表2\n* 列表3"
    }).Wait();

    robot.Send(new SendText("測(cè)試Text")
    {
        At = { "15657966053" } // @我自己
    }).Wait();

    robot.Send(new SendLink()
    {
        Title = "測(cè)試Link",
        Text = "測(cè)試Link",
        LinkUrl = "http://baidu.com"
    }).Wait();

    robot.Send(new SendSingleActionCard()
    {
        Title = "測(cè)試SingleActionCard",
        Text = "測(cè)試SingleActionCard",
        LinkText = "點(diǎn)擊我去百度",
        LinkUrl = "http://baidu.com"
    }).Wait();

    robot.Send(new SendMultisActionCard()
    {
        Title = "測(cè)試MultisActionCard",
        Text = "測(cè)試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();
}

測(cè)試結(jié)果


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末峦嗤,一起剝皮案震驚了整個(gè)濱河市蕊唐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌烁设,老刑警劉巖替梨,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異装黑,居然都是意外死亡副瀑,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)恋谭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)糠睡,“玉大人,你說(shuō)我怎么就攤上這事疚颊”房祝” “怎么了信认?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)均抽。 經(jīng)常有香客問(wèn)我嫁赏,道長(zhǎng),這世上最難降的妖魔是什么到忽? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任橄教,我火速辦了婚禮,結(jié)果婚禮上喘漏,老公的妹妹穿的比我還像新娘护蝶。我一直安慰自己,他們只是感情好翩迈,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布持灰。 她就那樣靜靜地躺著,像睡著了一般负饲。 火紅的嫁衣襯著肌膚如雪堤魁。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,521評(píng)論 1 304
  • 那天返十,我揣著相機(jī)與錄音妥泉,去河邊找鬼。 笑死洞坑,一個(gè)胖子當(dāng)著我的面吹牛盲链,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播迟杂,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼刽沾,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了排拷?” 一聲冷哼從身側(cè)響起侧漓,我...
    開(kāi)封第一講書(shū)人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎监氢,沒(méi)想到半個(gè)月后布蔗,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡浪腐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年何鸡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片牛欢。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡骡男,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出傍睹,到底是詐尸還是另有隱情隔盛,我是刑警寧澤犹菱,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站吮炕,受9級(jí)特大地震影響腊脱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜龙亲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一陕凹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鳄炉,春花似錦杜耙、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至谈竿,卻和暖如春团驱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背空凸。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工嚎花, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人呀洲。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓紊选,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親两嘴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理族壳,服務(wù)發(fā)現(xiàn)憔辫,斷路器,智...
    卡卡羅2017閱讀 134,656評(píng)論 18 139
  • 點(diǎn)擊查看原文 Web SDK 開(kāi)發(fā)手冊(cè) SDK 概述 網(wǎng)易云信 SDK 為 Web 應(yīng)用提供一個(gè)完善的 IM 系統(tǒng)...
    layjoy閱讀 13,761評(píng)論 0 15
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,811評(píng)論 6 342
  • 2017.02.22 可以練習(xí)仿荆,每當(dāng)這個(gè)時(shí)候贰您,腦袋就犯困,我這腦袋真是神奇呀拢操,一說(shuō)讓你做事情锦亦,你就犯困,你可不要太...
    Carden閱讀 1,346評(píng)論 0 1
  • 成長(zhǎng)令境、戀愛(ài)杠园、工作,請(qǐng)保持隨時(shí)離開(kāi)的能力 ------ 神童魏永康舔庶,2歲掌握1000多個(gè)漢字抛蚁,4歲學(xué)完初中陈醒,13歲考...
    自由拍客V閱讀 183評(píng)論 0 0