前言
因为之前没注意服务器安全,搭建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测试数据"))
}
测试
给机器人发送 测试 指令即可
运行测试很简单,也能得出我们想要的结果,而上面提到的问题,在代码中其实很难复现出来,所以这里就没有进行相关的测试。
1 条评论
魔法传奇1853-冒险世界的奇幻之旅:https://501h.com/fugu/6284.html