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

2020-03-16 11:07:55 浏览数 (1)

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

软硬条件

IDE

VS2017.5

Newbe.Mahua

1.6

业务逻辑

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

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

实测效果图:

新建项目

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

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

业务逻辑实现

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

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

业务接口代码如下:

代码语言:javascript复制
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建议将事件放置的文件夹位置。也可以不接受建议而添加在其他地方。

代码语言:javascript复制
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}条好友信息。");
        }
    }
}

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

单元测试

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

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

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

代码语言:javascript复制
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的利用率,相关接口都采用异步的方式进行定义。实际上时为了让新手看不懂

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

代码语言:javascript复制
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操作的扩展包。

新建应用程序配置文件

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

代码语言:javascript复制
<?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,详细代码如下:

代码语言:javascript复制
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类,详细代码如下:

代码语言:javascript复制
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建议将事件放置的文件夹位置。也可以不接受建议而添加在其他地方。

代码语言:javascript复制
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的完整代码:

代码语言:javascript复制
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 主要特性介绍与常见问题讲解
  • 开始第一个QQ机器人【适用于v1.9-v1.X】
  • Newbe.Mahua 插件热更新
  • Newbe.Mahua.Samples.ApiExtensions 对IMahuaApi进行扩展
  • Newbe.Mahua.Samples.LiveGirl 操作定时任务
  • Newbe.Mahua.Samples.Sqlite SQLite操作实例
  • Newbe.Mahua 测试与调试
  • Newbe.Mahua 扩展设置中心
  • 【开源访谈】对接 QQ 机器人平台,对接共同成长的开源社区(开源中国采访)

发布说明

  • Newbe.Mahua 1.18.2 修复项目模板
  • Newbe.Mahua 1.18.1 缺陷修复
  • Newbe.Mahua 1.18 恢复 QQLight
  • Newbe.Mahua 1.17 移除 CleverQQ
  • Newbe.Mahua 1.16 可用性修复
  • Newbe.Mahua 1.15 支持发送语音
  • Newbe.Mahua 1.14 全新项目模板
  • Newbe.Mahua 1.13 支持撤回消息
  • 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 开发便利性提升

0 人点赞