用户与权限

用户资料、权限检查和用户封禁是三个独立行为,由两个插件模块和数据库组件共同提供。

模块职责

模块提供的能力数据库表
falowp-bot-plugin-user保存用户资料,查询用户,修改金币和好感度,生成群内排行bot_user
falowp-bot-plugin-auth检查消息处理器的最低权限,执行 ban / unban,拦截被封禁用户ban_info
falowp-bot-utils-db建立数据源并执行 Exposed 事务不创建业务表

两个插件可以分别启用。它们都需要可用的数据库配置;数据库连接方式见数据库

添加依赖

同时启用用户资料和权限控制:

dependencies {
    implementation("com.blr19c.falowp:falowp-bot-plugin-user:${VERSION}")
    implementation("com.blr19c.falowp:falowp-bot-plugin-auth:${VERSION}")
    runtimeOnly("org.mariadb.jdbc:mariadb-java-client:${VERSION}")
}

插件源码需要直接调用数据库 API 时,再把 falowp-bot-utils-db 声明为直接依赖:

implementation("com.blr19c.falowp:falowp-bot-utils-db:${VERSION}")

用户资料

数据何时写入

每次收到消息时,用户插件在消息处理器执行前完成以下操作:

  1. (userId, sourceId) 查询发送者。
  2. 用户不存在时创建记录,金币和好感度初始为 0
  3. 用户已存在时更新昵称、头像和权限快照。
  4. 对消息中被 AT 的用户执行相同的创建或更新流程。

同一个平台用户在不同群聊或私聊来源中对应不同记录。sourceId 是资料隔离边界。

用户数据

BotUserVo 是一次查询得到的用户快照:

字段类型内容
idInt数据库主键
userIdString平台用户 ID
nicknameString最近一次消息中的昵称
avatarImageUrl最近一次消息中的头像
authApiAuth最近一次消息中的权限快照
impressionBigDecimal好感度
coinsBigDecimal金币
sourceIdString消息来源 ID
sourceTypeString消息来源类型

查询当前用户

下面的扩展函数在 BotApi 上调用,因此可直接用于消息处理器:

import com.blr19c.falowp.bot.plugins.user.currentUser
import com.blr19c.falowp.bot.plugins.user.currentUserOrNull

private val balance = message(Regex("我的金币")) {
    val user = currentUser()
    sendReply("当前金币:${user.coins.toPlainString()}")
}
API返回值空值行为
BotApi.currentUser()BotUserVo找不到用户时抛出空值异常
BotApi.currentUserOrNull()BotUserVo?找不到用户时返回 null
queryByUserId(userId, sourceId)BotUserVo?指定来源中没有记录时返回 null
queryBySourceId(sourceId)List<BotUserVo>指定来源中的全部用户

currentUser() 使用当前消息的 sender.idsource.id。它依赖消息上下文,不适用于没有真实发送者的系统任务。

查询来源中的用户

queryBySourceId 的第二个参数可以调整 Exposed 查询,例如排序和限制数量:

import com.blr19c.falowp.bot.plugins.user.database.BotUser
import com.blr19c.falowp.bot.plugins.user.queryBySourceId
import org.jetbrains.exposed.v1.core.SortOrder

val topUsers = queryBySourceId(receiveMessage.source.id) { query ->
    query.orderBy(BotUser.coins, SortOrder.DESC).take(10)
}

自定义查询块返回 List<ResultRow>,随后由用户插件转换为 BotUserVo

修改金币与好感度

import com.blr19c.falowp.bot.plugins.user.decrementCoins
import com.blr19c.falowp.bot.plugins.user.decrementImpression
import com.blr19c.falowp.bot.plugins.user.incrementCoins
import com.blr19c.falowp.bot.plugins.user.incrementImpression

val user = currentUser()
user.incrementCoins(10.toBigDecimal())
user.decrementCoins(3.toBigDecimal())
user.incrementImpression(1.toBigDecimal())
user.decrementImpression(1.toBigDecimal())

四个函数直接更新数据库中的数值。BotUserVo 本身不会同步变化;更新后需要最新值时,重新查询用户记录。

内置命令

命令结果
金币排行当前来源中金币最高的 7 名用户
好感度排行当前来源中好感度最高的 7 名用户

排行以图片形式回复,只统计当前 sourceId 下的用户记录。

权限控制

权限级别

级别code来源
ADMINISTRATORInt.MAX_VALUEbot.system.administrator 中的用户 ID
MANAGER100适配器映射的平台管理角色
ORDINARY_MEMBER0普通成员

权限插件比较消息处理器要求的 auth.code 与当前消息发送者的 auth.code。检查使用的是适配器写入 ReceiveMessage.sender.auth 的实时消息数据,不读取 BotUserVo.auth 快照。

限制消息处理器

private val reload = message(
    regex = Regex("重新加载"),
    auth = ApiAuth.ADMINISTRATOR,
) {
    sendReply("已执行")
}

执行顺序如下:

  1. 消息匹配器选中处理器。
  2. 权限插件读取处理器的最低权限。
  3. 权限不足时触发 NoAuthorizationHook
  4. 默认回复“你还没有权限操作此功能”,并终止该处理器。
  5. 权限满足时继续执行消息处理器。
auth = ... 只保存最低权限条件。运行时权限检查由 falowp-bot-plugin-auth 提供;未加载该插件时,条件不会阻止处理器执行。

封禁与解封

命令执行权限目标
ban @用户ADMINISTRATOR当前来源中被 AT 的非超级管理员用户
unban @用户ADMINISTRATOR当前来源中被 AT 的用户

封禁记录按 (userId, sourceId) 保存。被封禁用户在对应来源中的新消息会在接收阶段终止,以该用户作为执行者的事件也会在事件处理阶段终止。其他来源不受影响。

封禁集合在首次使用时从数据库加载,banunban 会同时更新数据库与运行时集合。

故障定位

现象检查项
启动时数据库初始化失败JDBC 驱动、bot.plugin.db 和主数据源配置
currentUser() 找不到用户是否处于真实消息上下文;用户资料插件是否已加载;当前来源是否存在记录
金币更新后变量仍是旧值BotUserVo 是快照;更新后重新查询
同一用户出现多条资料不同 sourceId 会分别保存,这是预期的隔离行为
auth 条件没有拦截权限插件是否已加载;适配器是否正确填充发送者权限
管理员命令仍提示无权限用户 ID 是否以字符串形式出现在 bot.system.administrator
封禁在另一个群不生效封禁记录按 sourceId 隔离
排行图片生成失败Webdriver、Chromium、字体和运行环境依赖

消息匹配条件的完整说明见消息处理