Newbe.Mahua.Samples.Sqlite SQLite操作实例

文本将通过实现一个记录”收到消息数量”的功能,来演示如何在本SDK中操作数据库的SQLite数据库。

软硬条件

IDE VS2017.5
Newbe.Mahua 1.6

业务逻辑

当收到好友消息时,将消息记录在数据库中。

同时将当前数据库中已经存储的消息数目,发送给消息发送者。

实测效果图:

最终效果

新建项目

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

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

业务逻辑实现

业务逻辑比较简单,主要实现两个方法:”保存好友消息”和”获取消息数量”。

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

业务接口代码如下:


using System;
using System.Threading.Tasks;

namespace Newbe.Mahua.Samples.Sqlite.Services
{
    /// <summary>
    /// 好友消息存储
    /// </summary>
    public interface IFriendMessageStore
    {
        /// <summary>
        /// 获取消息数量
        /// </summary>
        /// <returns></returns>
        Task<int> GetCountAsync();

        /// <summary>
        /// 保存好友消息
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        Task InsertAsync(InsertFriendMessageInput input);
    }

    public class InsertFriendMessageInput
    {
        /// <summary>
        /// QQ
        /// </summary>
        public string Qq { get; set; }

        /// <summary>
        /// 消息内容
        /// </summary>
        public string Message { get; set; }

        /// <summary>
        /// 收到消息的事件
        /// </summary>
        public DateTimeOffset ReceivedTime { get; set; }
    }
}

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

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


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

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

        public PrivateMessageFromFriendReceivedMahuaEvent(
            IMahuaApi mahuaApi,
            IFriendMessageStore friendMessageStore)
        {
            _mahuaApi = mahuaApi;
            _friendMessageStore = friendMessageStore;
        }

        public void ProcessFriendMessage(PrivateMessageFromFriendReceivedContext context)
        {
            _friendMessageStore.InsertAsync(new InsertFriendMessageInput
            {
                Message = context.Message,
                Qq = context.FromQq,
                ReceivedTime = context.SendTime
            }).GetAwaiter().GetResult();
            var count = _friendMessageStore.GetCountAsync().GetAwaiter().GetResult();
            _mahuaApi.SendPrivateMessage(context.FromQq, $"存储中已经存在{count}条好友信息。");
        }
    }
}

至此业务逻辑便实现完毕。

单元测试

业务逻辑已经实现完毕,接下来对业务逻辑编写单元测试进行验证。其实这么简单的逻辑,看一眼就知道没错

单元测试项目相关的内容可以参看右侧的教程:单元测试

此处只将业务逻辑的关键测试代码展示出来:


using Autofac.Extras.Moq;
using FluentAssertions;
using Moq;
using Newbe.Mahua.MahuaEvents;
using Newbe.Mahua.Samples.Sqlite.MahuaEvents;
using Newbe.Mahua.Samples.Sqlite.Services;
using System;
using System.Threading.Tasks;
using Xunit;

namespace Newbe.Mahua.Samples.Sqlite.Tests
{
    public class PrivateMessageFromFriendReceivedMahuaEventTests
    {
        [Fact]
        public void Test()
        {
            using (var mocker = AutoMock.GetStrict())
            {
                mocker.VerifyAll = true;

                var now = DateTime.Now;
                var msg = string.Empty;

                mocker.Mock<IMahuaApi>()
                    .Setup(x => x.SendPrivateMessage("472158246", It.IsAny<string>()))
                    .Callback<string, string>((qq, inputmsg) => msg = inputmsg);

                mocker.Mock<IFriendMessageStore>()
                    .Setup(x => x.InsertAsync(It.IsAny<InsertFriendMessageInput>()))
                    .Returns(Task.FromResult(0));

                mocker.Mock<IFriendMessageStore>()
                    .Setup(x => x.GetCountAsync())
                    .Returns(Task.FromResult(200));

                var service = mocker.Create<PrivateMessageFromFriendReceivedMahuaEvent>();
                service.ProcessFriendMessage(new PrivateMessageFromFriendReceivedContext
                {
                    FromQq = "472158246",
                    Message = "MSG",
                    SendTime = now
                });

                msg.Should().Be("存储中已经存在200条好友信息。");
            }
        }
    }
}

数据库操作实现

定义数据库操作接口

单元测试通过之后便表明当前业务逻辑都已经正确实现了。

接下来进一步就可以实现业务接口的实现类了。

为了完成业务逻辑,本实例至少需要”初始化数据库”、”查询数据库”和”向数据库插入数据”三个数据库操作方法。

其中的”查询数据库”和”向数据库插入数据”可以简单定义为”创建数据库链接即可”。

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

数据库操作接口定义如下:


using System.Data.Common;
using System.Threading.Tasks;

namespace Newbe.Mahua.Samples.Sqlite.Services
{
    public interface IDbHelper
    {
        /// <summary>
        /// 初始化数据库
        /// </summary>
        Task InitDbAsync();

        /// <summary>
        /// 获取数据库链接
        /// </summary>
        /// <returns></returns>
        Task<DbConnection> CreateDbConnectionAsync();
    }
}

使用SQLite实现数据库操作

SQLite数据库操作,通过官方提供的类库便可以完成。

通过 nuget 安装以下nuget包:

  • System.Data.SQLite.Core
  • Dapper
  • Dapper.Contrib

其中System.Data.SQLite.Core是数据库驱动,Dapper则是对ADO.NET操作的扩展包。

新建应用程序配置文件

应用程序配置文件

在应用程序配置文件中配置以下内容:


<?xml version="1.0" encoding="utf-8"?>

<configuration>
  <connectionStrings>
    <!--
    数据库链接字符串
    DataDirectory需要从当前的应用程序域上下文中获取,可以通过以下代码进行设置:
    AppDomain.CurrentDomain.SetData("DataDirectory",Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data"))
    -->
    <add name="Default" connectionString="Data Source=|DataDirectory|\mydb.db;Pooling=true;FailIfMissing=false" />
  </connectionStrings>
  <system.data>
    <DbProviderFactories>
      <!--配置ado.net数据工厂-->
      <remove invariant="System.Data.SQLite" />
      <add name="SQLite Data Provider" invariant="System.Data.SQLite"
           description=".NET Framework Data Provider for SQLite"
           type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" />
    </DbProviderFactories>
  </system.data>
</configuration>

添加数据库操作实现类SqliteDbHelper,详细代码如下:


using Dapper;
using System;
using System.Configuration;
using System.Data.Common;
using System.Data.SQLite;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;

namespace Newbe.Mahua.Samples.Sqlite.Services.Impl
{
    internal class SqliteDbHelper : IDbHelper
    {
        public Task InitDbAsync()
        {
            return Task.Run(() => CreateDbIfnotExists());
        }

        public Task<DbConnection> CreateDbConnectionAsync()
        {
            return Task.Run(() => CreateDbConnectionCore());
        }

        private static DbConnection CreateDbConnectionCore()
        {
            var dbf = DbProviderFactories.GetFactory("System.Data.SQLite");
            var conn = dbf.CreateConnection();
            Debug.Assert(conn != null, nameof(conn) + " != null");
            conn.ConnectionString = ConfigurationManager.ConnectionStrings["Default"].ConnectionString;
            return conn;
        }

        private static void CreateDbIfnotExists()
        {
            var dbDirectory = (string)AppDomain.CurrentDomain.GetData("DataDirectory");
            if (!Directory.Exists(dbDirectory))
            {
                Directory.CreateDirectory(dbDirectory);
            }

            var dbfile = Path.Combine(dbDirectory, "mydb.db");
            if (!File.Exists(dbfile))
            {
                SQLiteConnection.CreateFile(dbfile);
                using (var conn = CreateDbConnectionCore())
                {
                    conn.Execute(@" CREATE TABLE MSG(
                        Id TEXT PRIMARY KEY  ,
                        Qq           TEXT    NOT NULL,
                        Message            TEXT     NOT NULL,
                        ReceivedTime       TEXT NOT NULL
                        )");
                }
            }
        }
    }
}

编写业务实现类

完成了业务接口的定义和数据操作的定义,接下来只要将两者结合起来,便可以实现业务实现类。

添加FriendMessageStore类,详细代码如下:


using Dapper;
using Dapper.Contrib.Extensions;
using System;
using System.Threading.Tasks;

namespace Newbe.Mahua.Samples.Sqlite.Services.Impl
{
    /// <inheritdoc />
    /// <summary>
    /// 通过数据库实现好友信息存储
    /// </summary>
    internal class FriendMessageStore : IFriendMessageStore
    {
        private readonly IDbHelper _dbHelper;

        public FriendMessageStore(IDbHelper dbHelper)
        {
            _dbHelper = dbHelper;
        }

        public async Task<int> GetCountAsync()
        {
            using (var conn = await _dbHelper.CreateDbConnectionAsync())
            {
                var count = await conn.ExecuteScalarAsync<int>("select count(1) from MSG");
                return count;
            }
        }

        public async Task InsertAsync(InsertFriendMessageInput input)
        {
            using (var conn = await _dbHelper.CreateDbConnectionAsync())
            {
                await conn.InsertAsync(new MessageEntity
                {
                    Id = Guid.NewGuid().ToString(),
                    Message = input.Message,
                    Qq = input.Qq,
                    ReceivedTime = input.ReceivedTime.ToString("s")
                });
            }
        }

        [Table("MSG")]
        public class MessageEntity
        {
            [Key] public string Id { get; set; }
            public string Qq { get; set; }
            public string Message { get; set; }
            public string ReceivedTime { get; set; }
        }
    }
}

在插件启动时初始化数据库

数据库的初始化,需要在插件启动时进行调用。

MahuaEvents下添加”插件初始化事件”,并在事件内调用业务逻辑。实现代码如下:

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


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

namespace Newbe.Mahua.Samples.Sqlite.MahuaEvents
{
    /// <summary>
    /// 插件初始化事件
    /// </summary>
    public class InitializationMahuaEvent
        : IInitializationMahuaEvent
    {
        private readonly IDbHelper _dbHelper;

        public InitializationMahuaEvent(
            IDbHelper dbHelper)
        {
            _dbHelper = dbHelper;
        }

        public void Initialized(InitializedContext context)
        {
            // 插件初始化时,初始化数据库
            // 此处采用异步操作,可以避免插件初始化超时的问题
            _dbHelper.InitDbAsync();
        }
    }
}

模块注册

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


using Autofac;
using Newbe.Mahua.MahuaEvents;
using Newbe.Mahua.Samples.Sqlite.MahuaEvents;
using Newbe.Mahua.Samples.Sqlite.Services;
using Newbe.Mahua.Samples.Sqlite.Services.Impl;
using System;
using System.IO;

namespace Newbe.Mahua.Samples.Sqlite
{
    /// <summary>
    /// Ioc容器注册
    /// </summary>
    public class MahuaModule : IMahuaModule
    {
        public Module[] GetModules()
        {
            AppDomain.CurrentDomain.SetData("DataDirectory",
                Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data"));

            // 可以按照功能模块进行划分,此处可以改造为基于文件配置进行构造。实现模块化编程。
            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<PrivateMessageFromFriendReceivedMahuaEvent>()
                    .As<IPrivateMessageFromFriendReceivedMahuaEvent>();
                builder.RegisterType<InitializationMahuaEvent>()
                    .As<IInitializationMahuaEvent>();
            }
        }

        private class MyServiceModule : Module
        {
            protected override void Load(ContainerBuilder builder)
            {
                base.Load(builder);
                builder.RegisterType<FriendMessageStore>()
                    .As<IFriendMessageStore>();
                builder.RegisterType<SqliteDbHelper>()
                    .As<IDbHelper>();
            }
        }
    }
}

集成测试

万事具备,只欠生成。

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

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

最终效果

总结

数据库操作本身并不困难。

开发过程中采用基于接口开发的基本思想,结合单元测试,不论是开发简单的插件还是复杂的项目,都是可靠的方法。

若SQLite无法满足项目要求,只要将多实现一个IDbHelper便可以完成了,开发者可以动手体验。

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

教程链接

Newbe.Mahua 1.X 主要特性介绍与常见问题讲解

Newbe.Mahua 插件热更新

【开源访谈】对接 QQ 机器人平台,对接共同成长的开源社区(开源中国采访)

开始第一个QQ机器人【适用于v1.9-1.12】

Newbe.Mahua.Samples.ApiExtensions 对IMahuaApi进行扩展

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

Newbe.Mahua.Samples.Sqlite SQLite操作实例

Newbe.Mahua 测试与调试

Newbe.Mahua 扩展设置中心

发布说明

Newbe.Mahua 1.12 第一个LTS版本

Newbe.Mahua 1.11 支持热更新

Newbe.Mahua 1.10 全新日志查看器

Newbe.Mahua 1.9 引入 Session

Newbe.Mahua 1.8 消息发送Fluent API

Newbe.Mahua 1.7.0 支持API扩展

Newbe.Mahua 1.6.0 开发便利性提升