第三步:状态、会话与中间件¶
一个 Runtime 开始服务真实用户后,很快会遇到三类需求:记住某个插件的数据、等待用户的下一条消息、 以及在多个 handler 前后执行同一段逻辑。这一章分别对应状态、会话和中间件。
插件状态¶
插件的 ctx.state 默认是插件私有的。把 state_scope 声明为 persistent 后,
它会配合 [state] 后端跨重启保存。
from iamai import Context, Plugin, command
class CounterPlugin(Plugin):
name = "counter"
state_scope = "persistent"
@command("count")
async def count(self, ctx: Context) -> None:
value = int(ctx.state.get("value", 0)) + 1
ctx.state["value"] = value
await ctx.reply(f"count={value}")
对应配置可以先用 JSON,部署后再按需要切到 SQLite:
[state]
backend = "json"
path = "var/state.json"
多轮会话¶
wait_for_message 会等待同一个会话键下的下一条消息。它适合短流程,例如确认、问卷、
补充参数,不适合长时间业务状态机。
from iamai import Context, Plugin, command
class SurveyPlugin(Plugin):
name = "survey"
@command("survey")
async def survey(self, ctx: Context) -> None:
await ctx.reply("你喜欢的语言是?")
answer = await ctx.wait_for_message(timeout=30)
await ctx.reply(f"收到:{answer.text}")
如果超时,handler 会抛出超时异常;生产插件通常应捕获它并给用户一个明确反馈。
中间件¶
中间件适合横切逻辑,例如统一审计、输入规范化、耗时统计或异常兜底。不要把核心业务流程藏进 中间件里,否则 handler 的行为会变得难以阅读。
from iamai import Context, Plugin, middleware
class AuditPlugin(Plugin):
name = "audit"
@middleware("before")
async def before(self, ctx: Context) -> None:
ctx.runtime.count_metric("handler_input_seen", adapter=ctx.event.adapter)
中间件阶段包括 before、around、after 和 error。当你只需要一个 handler
自己的前置逻辑时,直接写在 handler 里通常更清楚。
Checkpoint¶
到这里你应该能区分:
ctx.state用于插件自己的数据;ctx.shared_state用于确有必要跨插件共享的数据;wait_for_message用于短时间多轮交互;middleware 用于横切能力,而不是业务主流程。