前言

因为之前没注意服务器安全,搭建aut的服务器被侵入了,导致aut的数据丢失,并且刚好碰到小组作业,没时间,就没去重建aut。这段时间放假有空,刚好可以用格式化的机器搭建个bncr玩玩,不试不知道,bncr的确比aut设计得更规范,对开发者极友好(开发文档需要吐槽一下,太简陋了)。这里简单记录一下我的第一个Bncr插件(应该叫模块,这里为了符合标题)

需要解决的问题

很久以前,在使用盖亚的订单系统时,当遇到高并发(多人同时进行会话)的情况下,因为订单数据存在框架内嵌式 KV 数据库(无论是aut还是bncr都采用这种数据库)中,如果同时对订单数据进行修改,可能会出现数据丢失的情况。因为类似这种对订单数据进行修改的操作,都是通过先从数据库get,后set来更新数据,假如多个会话同时get同一份数据,然后set,那么当全部会话结束时,这个订单数据只会新增了最后会话set中新增的数据,前面会话set的数据就丢失了。这种是非常严重的业务事故(现在aut的盖亚系统还存在这个情况,我已经弃更了,有事也别找我),这里刚好可以针对这种情况对数据库进行扩展来练练手,尝试解决这个问题。

插件本体

plugins/yourfolder/mod/数据库扩展.js中写入下面的代码,写入后需要重启bncr,模块才会生效

/**
 * 作者
 * @author Sirhexs
 * 插件名称
 * @name 数据库扩展
 * 团队
 * @team Sirhexs
 * 版本
 * @version 1.0.0
 * 说明
 * @description 可通过这个单例模式模块对数据库频繁读写数据进行同步读写限制,采用类似悲观锁的原理,防止数据在高并发下的读写错误。
 * 是否管理员专用
 * @admin false
 * 优先级
 * @priority 10
 * 分类
 * @classification ["数据库扩展"]
 * 是否公开
 * @public true
 * 是否禁用
 * @disable false
 * 是否服务模块
 * @service true
*/

class DataAccess {
    // 构造函数,这里需要使用单例模式
    constructor() {
        if (DataAccess.instance) {
            return DataAccess.instance;
        }
        // 定义写锁
        this.isNotLock = true
        // 设置单例实例
        DataAccess.instance = this;
        // 返回单例实例
        return this;
    }

    // 在数组开头插入数据
    async unshift(tableName, key, data) {
        return await this.operate(tableName, key, async (dbData) => {
            dbData.unshift(data)
        })
    }

    // 更新对象中的数据
    async update(tableName, key, propertyPath, operation) {
        return await this.operate(tableName, key, async (dbData) => {
            // 获取路径数组
            const properties = propertyPath.split('.');
            // 初始化寻址对象
            let current = dbData;
            // 遍历寻找
            for (let i = 0; i < properties.length - 1; i++) {
                if (current[properties[i]] === undefined) {
                    throw new Error(`数据库[${tableName}]中[${key}]不存在${propertyPath}的${properties[i]}。`);
                }
                current = current[properties[i]];
            }
            // 检查路径中最后一个属性
            const finalProperty = properties[properties.length - 1];
            if (current.hasOwnProperty(finalProperty)) {
                // 调用传进来的函数进行修改
                current[finalProperty] = operation(current[finalProperty]);
            } else {
                throw new Error(`数据库[${tableName}]中[${key}]不存在${propertyPath}`);
            }
        })
    }

    async operate(tableName, key, fun) {
        if (this.isNotLock) {
            // 上锁
            this.isNotLock = false
            //创建一个系统数据库实例
            const db = new BncrDB(tableName);
            // 获取数据
            let dbData = await db.get(key, [])
            // 更改数据
            try {
                await fun(dbData)
            } catch (err) {
                // 解锁
                this.isNotLock = true
                console.error(err.message)
                return false
            }
            // 设置数据
            const bool = await db.set(key, dbData)
            // 解锁
            this.isNotLock = true
            // 返回保存结果
            return bool
        } else {
            return await this.operate(tableName, key, fun)
        }
    }

}

// 实例化
const instance = new DataAccess();
// CommonJS模块化导出
module.exports = instance;

使用

plugins/yourfolder/数据库扩展测试.js中写入下面的代码,这里不需要重启bncr,plugins二级目录下的.js 文件会热载入

/**
 * @author Sirhexs
 * @name 数据库扩展测试
 * @team Sirhexs
 * @version 1.0.0
 * @description 数据库扩展模块测试插件
 * @rule ^测试$
 * @admin false
 * @priority 100
 * @classification ["测试"]
 * @public false
 * @disable false
 */

// 导入 数据库扩展 模块
const ExtensionDB = require('./mod/数据库扩展.js');

module.exports = async (sender) => {
  const db = new BncrDB('PluginTest');

  // unshift测试
  console.log("unshift测试运行前,unshift测试数据:",await db.get("unshift测试数据",[]))
  await ExtensionDB.unshift("PluginTest", "unshift测试数据", { data: "在数组中新增一条数据" })
  console.log("unshift测试运行后,unshift测试数据:",await db.get("unshift测试数据"))

  // update测试
  const updateTestData = await db.get("update测试数据")
  // 如果是第一次运行,先写入测试数据
  if (!updateTestData) {
    await db.set('update测试数据', {
      name: 'John',
      address: {
        city: 'New York',
        postalCode: {
          code: 10001,
          suffix: 1234
        }
      }
    });
  }
  console.log("update测试运行前,update测试数据:", await db.get("update测试数据"))
  await ExtensionDB.update("PluginTest", "update测试数据", 'address.postalCode.code', (data) => {
    return data + 100
  })
  console.log("update测试运行后,update测试数据:", await db.get("update测试数据"))
}

测试

给机器人发送 测试 指令即可

测试结果

运行测试很简单,也能得出我们想要的结果,而上面提到的问题,在代码中其实很难复现出来,所以这里就没有进行相关的测试。

最后修改:2024 年 07 月 16 日
如果觉得我的文章对你有用,请随意赞赏