数据库大作业-基于Redis的QQbot插件

数据库大作业-基于Redis的QQbot插件

运行环境:1核2GB云服务器,ubuntu系统

基于Yunzai-Bot v3项目开发插件

查看redis配置状况

进入redis客户端:redis-cli

  1. 查看redis使用进程(可以看到有YUnzai-Bot):

image-20230511192526281

  1. 查看BOT使用的数据库:

image-20230513153039819

image-20230513153006249

查询QQ号为1830796073的账号绑定的星铁UID:

image-20230513154716255

查询5月6日BOT发送的信息:

image-20230513155139845

查看BOT项目的插件是如何使用Redis的

lib/config/redis.js

image-20230513163954135

示例:查看星穹铁道插件中读写Redis数据库的js代码:

image-20230514213127080

note.js:

image-20230514212321709

image-20230514212604479

查看键“早安data”的过期时间:

image-20230517160942146

image-20230523212442202

编写使用Redis的bot插件

查看插件作者们编写的插件,其中简单的只用了一个js文件就可以完成一些常见的api调用、数据库存取操作;更多的集成多种功能的插例如StarRail-Plugin和Miao-Plugin等插件,都使用了复杂的文件夹结构,其中存有插件所必须的各种本地资源。因为本次实验只是完成对Redis的调用测试,因此只实现简单的一个自习室的小功能,只需要一个js文件即可。

该自习室实现了群聊/私聊中对QQbot的一些命令,其中自习/结束可以分别实现进入、退出自习室的功能,每次退出时将计算当次学习时长并累加到该日学习时长中,学习时长最多维持到该日晚上23:30 ;支持用户查询当日学习时长、学习时长排名和自习室人数。

插件功能函数:

函数 功能 命令
start_study 进入自习室,记录开始时间 开学|自习|学习|自习室|进入自习室
end_study 退出自习室,计算学习时长并追加到当日时长 下课|离开自习室|结束|结束自习|不学啦|开玩|退出
get_time 获取本日学习时长 自习时间|学习时间|我的学习时间|学习时长
get_numbers 获取当前自习室在线人数 自习室人数|自习人数|人数|当前人数
get_rank 获取今日学习时长排名 自习室排名|自习排名|排名|我的排名
schedule.scheduleJob 后台任务,清空自习室,维护用户信息
Redis2sql.sh 后台shell脚本,信息持久化

实验所用redis变量:

变量 存储内容
db:numbers 自习室人数
db:study:$ 用户学习状态
db:study:${e.user_id}:start_time 本次学习开始时刻
db:study:userlist 自习室在线用户ID表
db:study:\({e.user_id}:\) 用户当日学习时长
db:study:rank:${today}(sorted set) 存储当日所有用户的ID与学习时长的信息表,持久化用

以下为study.js文件代码

import plugin from '../../lib/plugins/plugin.js' 
import moment from 'moment';
import schedule from 'node-schedule'
import mysql from 'mysql';
const connection = mysql.createConnection({
	host:'127.0.0.1',   // 主机名 (服务器地址)
	user:'root',    //用户名
	password:'123',    // 密码
	database:'StudyRoom',  // 写上自己要连接的数据库名字
	port:'3306'
});
// let numbers = await redis.get('db:numbers') //自习室人数
// if(!numbers){
//     await redis.set('db:numbers',0)
//     logger.info('初始化自习室人数')
// }

// let list = await redis.get('db:study:userlist') 
// if(!list){
//     await redis.set('db:study:userlist','')

let reset_time =`0 21 22 * * ?`//重置学习状态,保存最后学习时间,持久化redis数据到mysql
schedule.scheduleJob(reset_time, async ()=>{
    console.log('清空自习室');
    let userlist = await redis.get('db:study:userlist')
    userlist =userlist + ""
    let users=userlist.split('|')
    for(let i=0;i<users.length-1;i++){//遍历所有用户,计算学习时间,追加后,重置学习状态
        let flag = await redis.get(`db:study:${users[i]}`)
        if(flag=="1"){//说明忘记推出自习室了,算上最后的时间
            let today=moment().format('MMDD')
            let myDate = new Date();
            let end_time = myDate.getTime();//自1970年..
            let start_time = await redis.get(`db:study:${users[i]}:start_time`)
            let time = end_time - start_time//获取毫秒(且换算成北京时间)
            let oldtime = await redis.get(`db:study:${users[i]}:${today}`)
            if(oldtime){
                time = parseInt(oldtime) + time
            }
            //设置键的生存时间,这里设置到当天晚上的23:59
            let now = new Date();
            let endOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 0);
            let timeDiff = endOfDay.getTime() - now.getTime();
            timeDiff = parseInt(timeDiff/1000); //转成秒
            //设置键值
            await redis.set(`db:study:${users[i]}:${today}`,time,'EX',timeDiff)
            await redis.expire(`db:study:${users[i]}:${today}`,timeDiff)
            await redis.zAdd(`db:study:rank:${today}`,{score:parseInt(time/1000),value:`${users[i]}`})
            await redis.expire(`db:study:rank:${today}`,timeDiff)
        }
        //把每一个用户的开始学习时间清空,学习状态重置
        await redis.set(`db:study:${users[i]}`,-1)
        await redis.set(`db:study:${users[i]}:start_time`,0)
    }
    // redis.zRange("myzset", 0, -1, "WITHSCORES", function(err, reply) {
    //     if (err) {
    //       console.error(err);
    //     } else {
    //       console.log(reply); // 回复是一个数组,包含成员和分数交替出现
    //     }
    //   });
    //   //reply形式为[ 'one', '1', 'two', '2', 'three', '3' ]
    // let today = moment().format('YYYY-MM-DD HH:mm:ss')
    // connection.connect(); // 建立连接
    // for(let i=0;i<reply.length;i=i+2){
    //     sql = `insert into study (date,user_id,study_time) values ('${today}','${reply[i]}','${reply[i+1]/60}')`
    //     connection.query(sql, function (err, result) { // 执行SQL语句
    //         if (err) throw err;	
    //         });
    // } 
    // connection.end(); // 关闭连接
    //将redis中的数据持久化到mysql

});


let rereset_time =`0 59 23 * * ?`//真正清空redis
schedule.scheduleJob(rereset_time, async ()=>{
    await redis.set('db:numbers',0)//清空自习室
    await redis.del(`db:study:rank:${today}`)
    await redis.set('db:study:userlist','')
});

export class Study extends plugin {
    constructor(){
        super({
                name: '蒙德自习室',
                dsc: '睡眠',
                event: 'message',
                priority: 3000,
                rule: [
                    {
                        reg: '^(开学|自习|学习|自习室|进入自习室)$',//命令匹配
                        fnc: 'start_study'//执行函数
                      },
                    {
                        reg: '^(下课|离开自习室|结束|结束自习|不学啦|开玩|退出)$',//命令匹配
                        fnc: 'end_study'//执行函数
                    },
                    {
                        reg: '^(自习室排名|自习排名|排名|我的排名)$',//命令匹配
                        fnc: 'get_rank'//执行函数
                    },
                    {
                        reg:'^测试$',
                        fnc:'test_redis'
                        
                    },
                    {
                        reg:'^(自习时间|学习时间|我的学习时间|学习时长)$',
                        fnc:'get_time'
                    },
                    {
                        reg: '^(自习室人数|自习人数|人数|当前人数)$',//命令匹配
                        fnc: 'get_numbers'//执行函数
                    }
                    
                ]
            }
        )
    }

    async start_study (e) {
        let numbers = await redis.get('db:numbers')
        let user = await redis.get(`db:study:${e.user_id}`)
        let myDate = new Date();
        let hours = myDate.getHours();
        let mins = myDate.getMinutes();  //获取当前分钟数(0-59)
        let secs = myDate.getSeconds();
        let start_time = myDate.getTime();//自1970年...

        if(user=="1"&&numbers>0){
            await e.reply(`你已经在自习室了哦!好好学习吧!`)
        }
        else if(hours>=23){
            await e.reply(`喂喂 已经过了23点啦,别卷了快去睡觉!`)
        }
        else{
            await redis.set(`db:study:${e.user_id}`,1)
            await e.reply(`已进入自习室,现在是${hours}时${mins}分${secs}秒,当前人数:${parseInt(numbers)+1}`,false, { at: true })
            await redis.incr('db:numbers')
            //将现在的时间存入redis,维护user表
            await redis.set(`db:study:${e.user_id}:start_time`,start_time)
            let tmp = await redis.get('db:study:userlist')
            tmp =tmp + `${e.user_id}`+'|'
            await redis.set('db:study:userlist',tmp)
            //await redis.append('db:study:userlist',`${e.user_id}`+'|')
        }
        return true
    }
    async end_study (e) {
        let numbers = await redis.get('db:numbers')
        let user = await redis.get(`db:study:${e.user_id}`)
        if(user=="1"&&numbers>0){
            let myDate = new Date();
            let end_time = myDate.getTime();//自1970年...
            let start_time = await redis.get(`db:study:${e.user_id}:start_time`)
            let time = end_time - start_time//获取毫秒
            let T =moment(time).utcOffset(0).format('HH'+'小时'+'mm'+'分钟'+'ss'+'秒')//转下格式
            await e.reply(`已离开自习室,本次学习时长为${T},当前人数:${parseInt(numbers)-1}`,false, { at: true })
            //同时将本次学习时间存入当日学习总时间中 //同时将本次学习时间作为分数,存入有序列表中,键就是学习时间
            let today = moment().format('MMDD')
            let oldtime = await redis.get(`db:study:${e.user_id}:${today}`)
            if(oldtime){//如果oldtime存在,要累加!
                // await redis.zincrby(`db:study:rank:${today}`,time,`${e.user_id}`)
                time = parseInt(oldtime) + time
            }
            //设置键的生存时间,这里设置到当天晚上的23:59
            let now = new Date();
            let endOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 0);
            let timeDiff = endOfDay.getTime() - now.getTime();
            timeDiff = parseInt(timeDiff/1000); //转成秒
            //await e.reply("距离11.59有"+timeDiff+"秒")
            //设置键值
            await redis.set(`db:study:${e.user_id}:${today}`,time)
            await redis.expire(`db:study:${e.user_id}:${today}`,timeDiff)
            await redis.zAdd(`db:study:rank:${today}`,{score:parseInt(time/1000),value:`${e.user_id}`})
            await redis.expire(`db:study:rank:${today}`,timeDiff)
            //let test = redis.zcard(`db:study:rank:${today}`)
            //e.reply("排名人数"+test)
            //把用户的开始学习时间清空,学习状态重置
            await redis.decr('db:numbers')
            await redis.set(`db:study:${e.user_id}`,-1)
            await redis.set(`db:study:${e.user_id}:start_time`,0)
            let userlist = await redis.get('db:study:userlist')
            userlist = userlist.replace(`${e.user_id}`+'|','')
            redis.set('db:study:userlist',userlist)


        }else{
            await e.reply(`你不在自习室哦!要学习吗?`,false, { at: true })
        }
        return true
    }

    async get_numbers (e) {
        let numbers = await redis.get('db:numbers')
        await e.reply(`当前自习室人数:${numbers}`)
        return true
    }

    async get_time (e) {
        let today = moment().format('MMDD')
        let time = await redis.get(`db:study:${e.user_id}:${today}`)
        //await e.reply(`${time}!`)
        if(!time){
            await e.reply(`你今天还没有学习哦!`,false, { at: true })
        }else{
            time = parseInt(time)
            let T =moment(time).utcOffset(0).format('HH'+'小时'+'mm'+'分钟'+'ss'+'秒')//转下格式
            await e.reply(`你今天已经学习了${T}!`,false, { at: true })
        }
        return time
    }
    async test_redis(e){
        e.reply("test")
        let today = moment().format('MMDD')
        let item = await redis.zRange(`db:study:rank:${today}`, 0, -1 )
        e.reply(typeof item)
        let entries = [];
        items.forEach((item) => {
            entries.push(JSON.parse(item));
        });
        e.reply(entries)
    }
    
        //let result = await redis.zRange("myzset", 0, -1, "WITHSCORES") 
        //res = result.values()
        //e.reply(typeof res)
        //   //reply形式为[ 'one', '1', 'two', '2', 'three', '3' ]
        // let today = moment().format('YYYY-MM-DD HH:mm:ss')
        // connection.connect(); // 建立连接
        // for(let i=0;i<reply.length;i=i+2){
        //     sql = `insert into study (date,user_id,study_time) values ('${today}','${reply[i]}','${reply[i+1]/60}')`
        //     connection.query(sql, function (err, result) { // 执行SQL语句
        //         if (err) throw err;	
        //         });
        // } 
        // connection.end(); // 关闭连接
    

    async get_rank(e){
        let today = moment().format('MMDD')
        let count = await redis.zCard(`db:study:rank:${today}`) 
        redis.zRevRank(`db:study:rank:${today}`,`${e.user_id}`).then((res)=>{
            e.reply("你在总计"+count +"名用户中的排名为"+(res+1),false, { at: true })
        })

    }

}

可以看到我们还使用了一个计时函数每日清除用户的当日学习时长。由于实验中测试了Redis的数据持久化(即将Redis中的数据保存到mysql中以保存在磁盘上),因此我们必须在清空用户学习时长之前完成对其的持久化。

为了完成该任务,想到了之前实验中学过的系统可以定时执行shell脚本,因此写了一个shell脚本完成数据持久化的任务:

以下shell代码的作用是从当日用户学习时长的有序集合(Redis的数据结构)中读取学习时长,然后按照mysql数据库的格式(date,user_id,study_time四个键)存入mysql数据库中。

#!/bin/bash
# execute sql  
cur_date=$(date "+%m%d")
redis-cli -h localhost --csv ZRANGE db:study:rank:${cur_date} 0 -1 WITHSCORES > /root/redis1.txt

IFS=","
declare -a my_array
 #while read line
 #do
#	echo $line >> /root/tess.txt
	#my_array+=($line)
#	my_array[${#my_array[*]}]=$line
 #done <  /root/redis1.txt

read -a my_array < <(redis-cli -h localhost --csv ZRANGE db:study:rank:$(date "+%m%d") 0 -1 WITHSCORES )
for((i=0;i<${#my_array[@]};i=i+2));
do 
    my_array[i]=${my_array[i]//\"/} 
	echo ${my_array[i]}
	mysql -uroot -p123 -e  "    
	use StudyRoom;
	insert into study (date,user_id,study_time) values ('$(date "+%Y-%m-%d")','${my_array[i]}',${my_array[i+1]})
	"
done
echo "test250" >> /root/tess.txt


至于如何在linux系统中配置定时执行shell脚本的方法,网上有许多详细教程,不再赘述。

至此我们已经完成了插件的编写。现在重启Yunzai-Bot:在bot根目录下开终端,输入:

pnpm restart

完成重启任务,之后即可测试:

image-20230610204758345

image-20230610204837112

posted @ 2023-06-10 20:49  丘丘王  阅读(34)  评论(0编辑  收藏  举报
鼠标点击页面特效

尝试在空白处点击鼠标