跳到主要内容

Newbe.Mahua.Samples.LiveGirl 操作定时任务

B 站直播姬!定时向群友通知群主何时进行女装直播的消息。

软硬条件

IDEVS2017.5
Newbe.Mahua1.6

业务逻辑

收到 "直播姬起飞" 的消息后,启动定时任务,每个整点时,检测 B 站直播间当前是否正在直播。

如果正在直播,就向群发送 "群主正在女装" 的消息。

收到 "直播姬降落" 的消息后,取消所有定时任务。

最终效果

新建项目

使用Newbe.Mahua.Plugins.Template模板创建项目,项目名称为Newbe.Mahua.Samples.LiveGirl

新建项目的详细细节,可以参照右侧链接内容:新建项目

业务逻辑实现

定义直播姬接口ILiveGirl,包含 "启动" 和 "停止" 两个基础方法。以便收到消息命令后对定时任务进行启停。

为了提升多核 CPU 的利用率,相关接口都采用异步的方式进行定义。实际上时为了让新手看不懂

业务接口代码如下:

using System.Threading.Tasks;

namespace Newbe.Mahua.Samples.LiveGirl.Services
{
/// <summary>
/// B站直播姬
/// </summary>
public interface ILivegirl
{
/// <summary>
/// 启动
/// </summary>
/// <returns></returns>
Task StartAsync();

/// <summary>
/// 停止
/// </summary>
/// <returns></returns>
Task StopAsnyc();
}
}

MahuaEvents下添加"好友消息接收事件",并在事件内调用业务逻辑。实现代码如下:

MahuaEvents 文件夹是本 SDK 建议将事件放置的文件夹位置。也可以不接受建议而添加在其他地方。

using Newbe.Mahua.MahuaEvents;
using Newbe.Mahua.Samples.LiveGirl.Services;

namespace Newbe.Mahua.Samples.LiveGirl.MahuaEvents
{
/// <summary>
/// 来自好友的私聊消息接收事件
/// </summary>
public class PrivateMessageFromFriendReceivedMahuaEvent
: IPrivateMessageFromFriendReceivedMahuaEvent
{
private readonly ILivegirl _livegirl;

public PrivateMessageFromFriendReceivedMahuaEvent(
ILivegirl livegirl)
{
_livegirl = livegirl;
}

public void ProcessFriendMessage(PrivateMessageFromFriendReceivedContext context)
{
if (context.FromQq == "472158246")
{
switch (context.Message)
{
case "直播姬起飞":
_livegirl.StartAsync().GetAwaiter().GetResult();
break;
case "直播姬降落":
_livegirl.StopAsnyc().GetAwaiter().GetResult();
break;
}
}
}
}
}

至此直播姬的启停便实现完毕。

定时任务

定时任务的实现方式多种多样,可以利用Timer进行简单实现,也可以使用一些定时任务的类库进行实现。

比较流行的有:其实我也就知道两个

  • Quartz.net
  • Hangfire

本例程将使用Hangfire来实现这一个功能。

安装 nuget 包

安装以下 nuget 包:

  • Hangfire.Core
  • Hangfire.MemoryStorage
  • Hangfire.Autofac
  • Microsoft.Owin.Hosting
  • Microsoft.Owin.Host.HttpListener

Hangfire相关内容是实现定时任务功能。

Microsoft.Owin.*则实现了在非 IIS 进程中托管 Web 服务的功能。

插件启动时初始化 Web 服务

Hangfire 需要通过 Web 服务来展示当前的任务状态情况。

添加 IWebHost 接口,以便在插件初始化时,初始化 Web 服务。

using Autofac;
using System.Threading.Tasks;

namespace Newbe.Mahua.Samples.LiveGirl.Services
{
public interface IWebHost
{
/// <summary>
/// 启动Web服务
/// </summary>
/// <param name="baseUrl"></param>
/// <param name="lifetimeScope"></param>
Task StartAsync(string baseUrl, ILifetimeScope lifetimeScope);

/// <summary>
/// 停止
/// </summary>
Task StopAsync();
}
}

Web 服务的初始化,需要在插件启动时进行调用。

MahuaEvents下添加"插件初始化事件",并在事件内调用初始化。实现代码如下:

MahuaEvents 文件夹是本 SDK 建议将事件放置的文件夹位置。也可以不接受建议而添加在其他地方。

using Newbe.Mahua.MahuaEvents;
using Newbe.Mahua.Samples.LiveGirl.Services;

namespace Newbe.Mahua.Samples.LiveGirl.MahuaEvents
{
/// <summary>
/// 插件初始化事件
/// </summary>
public class InitializationMahuaEvent
: IInitializationMahuaEvent
{
private readonly IMahuaApi _mahuaApi;
private readonly IWebHost _webHost;

public InitializationMahuaEvent(
IMahuaApi mahuaApi,
IWebHost webHost)
{
_mahuaApi = mahuaApi;
_webHost = webHost;
}

public void Initialized(InitializedContext context)
{
// 在本地地址上启动Web服务,可以根据需求改变端口
_webHost.StartAsync("http://localhost:65238", _mahuaApi.GetSourceContainer());
}
}
}

添加 Hangfire 初始化代码

Owin 的启动入口是一个名为Startup的启动类,为了初始化Hangfire,则需要创建启动类,并初始化Hangfire

代码如下:

using Autofac;
using Hangfire;
using Hangfire.MemoryStorage;
using Microsoft.Owin;
using Owin;

// 这是Startup的入口标记
[assembly: OwinStartup(typeof(Newbe.Mahua.Samples.LiveGirl.Startup))]

namespace Newbe.Mahua.Samples.LiveGirl
{
public class Startup
{
public void Configuration(IAppBuilder app, ILifetimeScope lifetimeScope)
{
// 初始化Hangfire
var config = GlobalConfiguration.Configuration;

// 使用内存存储任务,若有持久化任务的需求,可以根据Hangfire的文档使用数据库方式存储
config.UseMemoryStorage();

// 通过Autofac容器来实现任务的构建
config.UseAutofacActivator(lifetimeScope);

// 启用Hangfire的web界面
app.UseHangfireDashboard();

// 初始化Hangfire服务
app.UseHangfireServer();
}
}
}

实现 IWebHost

上代码:

using Autofac;
using Microsoft.Owin.Hosting;
using System;
using System.Threading.Tasks;

namespace Newbe.Mahua.Samples.LiveGirl.Services.Impl
{
public class OwinWebHost : IWebHost
{
// 保存Web服务的实例
private IDisposable _webhost;

public Task StartAsync(string baseUrl, ILifetimeScope lifetimeScope)
{
_webhost = WebApp.Start(baseUrl, app => new Startup().Configuration(app, lifetimeScope));
return Task.FromResult(0);
}

public Task StopAsync()
{
_webhost.Dispose();
return Task.FromResult(0);
}
}
}

实现直播姬

基础设施已经在上一节完成,接下来就要实现直播姬和定时任务之间的调度代码。

获取直播间状态

直播间状态可以通过捕捉 HTTP 请求,看出如何实现。

本例程,将引入 RestSharp nuget 包来实现 HTTP 请求。

定义直播间接口ILiveRoom并添加实现类。

namespace Newbe.Mahua.Samples.LiveGirl.Services
{
public interface ILiveRoom
{
/// <summary>
/// 获取直播间状态
/// </summary>
/// <returns></returns>
bool IsOnLive();
}
}
using RestSharp;

namespace Newbe.Mahua.Samples.LiveGirl.Services.Impl
{
public class LiveRoom : ILiveRoom
{
public bool IsOnLive()
{
var client = new RestClient("https://api.live.bilibili.com");
var req = new RestRequest("room/v1/Room/get_info?room_id=7834872&from=room");
var resp = client.Get<Rootobject>(req);
if (resp.IsSuccessful)
{
return resp.Data.data.live_status == 1;
}

return false;
}

// 可以使用VS中的 编辑->选择性粘贴->将JSON粘贴为类
// 莫非不知道么ლ(′◉❥◉`ლ)
public class Rootobject
{
public int code { get; set; }
public string msg { get; set; }
public string message { get; set; }
public Data data { get; set; }
}

public class Data
{
public int uid { get; set; }
public string description { get; set; }
public int live_status { get; set; }
public int area_id { get; set; }
public int parent_area_id { get; set; }
public string parent_area_name { get; set; }
public int old_area_id { get; set; }
public string background { get; set; }
public string title { get; set; }
public string user_cover { get; set; }
public string live_time { get; set; }
public string tags { get; set; }
public int is_anchor { get; set; }
public string room_silent_type { get; set; }
public int room_silent_level { get; set; }
public int room_silent_second { get; set; }
public string area_name { get; set; }
public string pendants { get; set; }
public string area_pendants { get; set; }
public string verify { get; set; }
}
}
}

Livegirl

来吧,终于可以实现直播姬了。

using Hangfire;
using System.Diagnostics;
using System.Threading.Tasks;

namespace Newbe.Mahua.Samples.LiveGirl.Services.Impl
{
public class Livegirl : ILivegirl
{
private static readonly string JobId = "jobid";

private readonly IMahuaApi _mahuaApi;
private readonly ILiveRoom _liveRoom;

public Livegirl(
IMahuaApi mahuaApi,
ILiveRoom liveRoom)
{
_mahuaApi = mahuaApi;
_liveRoom = liveRoom;
}

public Task StartAsync()
{
// 添加定时任务
// 每个整点触发
RecurringJob.AddOrUpdate(JobId, () => SendMessage(), () => Cron.HourInterval(1));

// 使用浏览器打开定时任务的地址
Process.Start("http://localhost:65238/hangfire/recurring");
return Task.FromResult(0);
}

public Task StopAsnyc()
{
// 移除定时任务
RecurringJob.RemoveIfExists(JobId);
return Task.FromResult(0);
}

public void SendMessage()
{
// 如果直播间状态为正在直播,则发送消息
if (_liveRoom.IsOnLive())
{
_mahuaApi.SendGroupMessage("610394020", "群主正在女装,前往观望?https://live.bilibili.com/7834872");
}
}
}
}

模块注册

以上所有的接口与实现类与接口,都不要忘记在模块中进行注册,以下是MahuaModule的完整代码:

using Autofac;
using Newbe.Mahua.MahuaEvents;
using Newbe.Mahua.Samples.LiveGirl.MahuaEvents;
using Newbe.Mahua.Samples.LiveGirl.Services;
using Newbe.Mahua.Samples.LiveGirl.Services.Impl;

namespace Newbe.Mahua.Samples.LiveGirl
{
/// <summary>
/// Ioc容器注册
/// </summary>
public class MahuaModule : IMahuaModule
{
public Module[] GetModules()
{
// 可以按照功能模块进行划分,此处可以改造为基于文件配置进行构造。实现模块化编程。
return new Module[]
{
new PluginModule(),
new MahuaEventsModule(),
new MyServiceModule(),
};
}

/// <summary>
/// 基本模块
/// </summary>
private class PluginModule : Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
// 将实现类与接口的关系注入到Autofac的Ioc容器中。如果此处缺少注册将无法启动插件。
// 注意!!!PluginInfo是插件运行必须注册的,其他内容则不是必要的!!!
builder.RegisterType<PluginInfo>()
.As<IPluginInfo>();
}
}

/// <summary>
/// <see cref="IMahuaEvent"/> 事件处理模块
/// </summary>
private class MahuaEventsModule : Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
// 将需要监听的事件注册,若缺少此注册,则不会调用相关的实现类
builder.RegisterType<InitializationMahuaEvent>()
.As<IInitializationMahuaEvent>();
builder.RegisterType<PrivateMessageFromFriendReceivedMahuaEvent>()
.As<IPrivateMessageFromFriendReceivedMahuaEvent>();
}
}

private class MyServiceModule : Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
// 确保Web服务是单例
builder.RegisterType<OwinWebHost>()
.As<IWebHost>()
.SingleInstance();

// AsSelf是为了Hangfire能够初始化这个类
builder.RegisterType<Livegirl>()
.As<ILivegirl>()
.AsSelf();
builder.RegisterType<LiveRoom>()
.As<ILiveRoom>();
}
}
}
}

集成测试

万事具备,只欠生成。

生成解决方案,运行build.bat,复制相关的 DLL 到对应的平台,向机器人发送消息,效果达成!

以下是 CQP 平台的测试效果。其实其他的没测试

最终效果

总结

一般的定时任务只需要使用Timer就能够实现了,引入Hangfire主要是为了体现框架本身的可扩展性。分明是为了装逼

HTTP 的捕捉,可以使用Fiddler等 Web 调试工具实现。又要自己学

例程中写死的字符串,应当通过文件配置进行保存,可以自行改造。

实例的项目代码,可以在源码仓库中的Newbe.Mahua.Samples解决方案下找到。


欢迎关注的我微信公众号,第一时间获取我的最新文章。