一个简单的小 POST 转发还要编译一套程序然后部署并且运维?太复杂了!能不能只写一个文件就实现这样的功能呢?只要有 Serverless 的函数计算功能即可。这里使用阿里云函数计算功能(小量免费哦),其他平台同理。

0. 准备工作

你需要有 C#编程基础才能愉快的阅读本文章。如果你只使用过 WinForm、WPF、ASP.NET 等传统.NET 技术,请不要错过.NET Core 这个全新的跨平台产品~(疯狂安利)
请准备好.NET Core SDK 以及一个文本编辑器,推荐使用 VS Code。
请阅读腾讯 Bugly 和钉钉机器人相关文档。
推荐也阅读一下阿里云官方文档函数计算 > 构建函数 > 编程语言 > C# > C# 函数入口,本文根据需求使用的是 HTTP 触发器,但是这文档写的确实很乱,我看了半天才看懂……

1. 创建项目,添加依赖

mkdir BuglyToDingtalkServerless
cd BuglyToDingtalkServerless
dotnet new console -f netcoreapp2.1

官方示例使用的是 webapi 模板,为什么这里使用 console 呢?因为 webapi 会创建一堆在函数计算中使用不到的东西,为了避免还要删除一堆。由于在撰写本文时阿里云还不支持最新的 2.2 版本,所以我们还需要指定.NET Core 的版本。继续添加引用

dotnet add package Aliyun.Serverless.Core
dotnet add package Aliyun.Serverless.Core.Http
dotnet add package Newtonsoft.Json

最后得到的 BuglyToDingtalkServerless.csproj 文件如下

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType> <!--这行可以删除-->
    <TargetFramework>netcoreapp2.1</TargetFramework> <!--注意这里的版本-->
  </PropertyGroup>

  <ItemGroup>
     <PackageReference Include="Aliyun.Serverless.Core" Version="1.0.1" />
     <PackageReference Include="Aliyun.Serverless.Core.Http" Version="1.0.2" />
     <PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.11" /> <!--注意这里的版本-->
     <PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
  </ItemGroup>

</Project>

2. 编写业务代码

创建一个文件,名称为 FcRemoteEntrypoint.cs,当然你想塞 Program.cs 里也没问题。结合腾讯 Bugly 文档中的 example(注意那里面有的参数名竟然是错的,考验英文水平的时候到了),进行开发,下面直接贴代码了,请看注释
(提示:点击代码段左上角的三个小按钮可以将代码全屏)

using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Aliyun.Serverless.Core;
using Aliyun.Serverless.Core.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Net.Http;

namespace BuglyToDingtalkServerless
{
    public class SingleHttpHandler : FcHttpEntrypoint
    {
        // 钉钉机器人 webhook 地址
        const string webhook = "https://oapi.dingtalk.com/robot/send?access_token=your_token";

        protected override void Init(IWebHostBuilder builder)
        {
        }

        // 这里是真正的入口
        public override async Task<HttpResponse> HandleRequest(HttpRequest request, HttpResponse response, IFcContext fcContext)
        {
            string method = request.Method;
            string relativePath = request.Path.Value;
            fcContext.Logger.LogInformation("method = {0}; requestPath = {1}", method, relativePath);
            // 读取请求内容
            StreamReader sr = new StreamReader(request.Body);
            string requestBody = sr.ReadToEnd();
            fcContext.Logger.LogInformation("requestBody = {}", requestBody);

            // 没有见过 dynamic 的话千万不要慌,这是动态类型,我只是为了下面写起来像 js 才这么玩的
            // 用常规的 JObject 都可以的,就是下面引用值就变成了 value["param"]
            dynamic value = JsonConvert.DeserializeObject(requestBody);
            string msgTitle = $"每日运营统计 - {value.eventContent.date}";
            string msgText = "";
            if (value.eventType == "bugly_crash_trend")
            {
                msgText += $"### {msgTitle}\n\n\n";
                msgText += "版本: 用户访问量/崩溃次数/受影响用户\n\n";
                foreach (var s in value.eventContent.datas)
                {
                    msgText += $"[{s.version}: {s.accessUser} / {s.crashCount} / {s.crashUser}]({s.url})\n\n";
                }
            }
            var msg = new { msgtype = "markdown", markdown = new { title = msgTitle, text = msgText } };
            fcContext.Logger.LogInformation("ToDingtalkBody = {}", msg);
            // POST 拼好的 json 到钉钉 webhook
            await new HttpClient().PostAsJsonAsync(webhook, msg);

            response.StatusCode = 200;
            return response;
        }
    }
}

3. 编译与部署

发布程序

dotnet publish -c Release

将文件夹下 bin/Release/netcoreapp2.1/publish 下的文件(一堆 dll)打包成 zip(不要将 publish 文件夹打包进去)

下面我们将程序部署到阿里云函数计算中,打开阿里云函数计算后台,新建函数,选择空白函数

触发器配置,选择 HTTP 触发器,认证方式为匿名(因为腾讯 Bugly 的 Webhook 不支持认证),请求方式为 POST

基础信息请按自己的喜好设置,运行环境请选 dotnetcore,代码包上传选择之前打包好的 zip
函数入口如果是按照上文中的代码来的话,设置为:BuglyToDingtalkServerless::BuglyToDingtalkServerless.SingleHttpHandler::HandleRequest

剩下的安全配置请根据实际情况配置。

创建好后我们来到函数的触发器中,就可以得到供腾讯 Bugly 使用的 Webhook 地址了!将这个地址填入 Bugly 配置中,静候第二天早上 9 点的推送吧!(前提是应用有人用,产生了统计数据)

在此之前我们可以用假数据先测试一下,在代码执行里,请求方式 POST,路径空,Body 设置为 raw,JSON 格式,下面给到一份测试样例

{
  "eventType": "bugly_crash_trend",
  "timestamp": 1462780713515,
  "isEncrypt": 0,
  "eventContent": {
    "datas": [
      {
        "accessUser": 12972,//联网用户数
        "crashCount": 21,//crash 次数
        "crashUser": 20,//crash 影响用户数
        "version": "1.2.3",//app 版本号
        "url": "http://bugly.qq.com/realtime?app=1104512706&pid=1&ptag=1005-10003&vers=0.0.0.12.12&time=last_7_day&tab=crash"
      },
      {
        "accessUser": 15019,
        "crashCount": 66,
        "crashUser": 64,
        "version": "1.2.4",
        "url": "http://bugly.qq.com/realtime?app=1104512706&pid=1&ptag=1005-10003&vers=0.0.0.12.12&time=last_7_day&tab=crash"
      },
      {
        "accessUser": 15120,
        "crashCount": 1430,
        "crashUser": 1423,
        "version": "1.2.4",
        "url": "http://bugly.qq.com/realtime?app=1104512706&pid=1&ptag=1005-10003&vers=0.0.0.12.12&time=last_7_day&tab=crash"
      }
    ],
    "appId": "1104512706", //appId
    "platformId": 1,   //平台
"appName": "AF", //app 名称
    "date": "20160508",
"appUrl":"http://bugly.qq.com/issueIndex?app=1104512706&pid=1&ptag=1005-10000" 
  },
  "signature": "ACE346A4AE13A23A52A0D0D19350B466AF51728A"
}

Enjoy~


寻找属于自己的1%