Skip to content

[WIP] Add complete context system for Bot commands and handlers - #30

Open
chr233 with Copilot wants to merge 1 commit into
devfrom
copilot/add-context-system-for-bot
Open

[WIP] Add complete context system for Bot commands and handlers#30
chr233 with Copilot wants to merge 1 commit into
devfrom
copilot/add-context-system-for-bot

Conversation

Copilot AI commented Jun 1, 2026

Copy link
Copy Markdown
  • Add context config/attribute/DTO and lazy store types
  • Add context DB entities and repository/service interfaces
  • Implement context repositories and context service with Redis fallback
  • Update command definition and command handler for context injection/filter routing
  • Build and run tests for verification
Original prompt

概述

为 Bot 的命令和 CallbackQuery handler 添加一套完整的 会话上下文(Context)系统,支持:

  • 私聊上下文(PrivateContext)与群聊上下文(GroupContext)类型区分
  • handler 方法通过参数类型声明所需 context,框架自动注入
  • ContextFilterAttribute 标记 handler 所需的 Mode,实现多步骤交互路由
  • 懒加载:仅在 handler 实际访问 context 时才触发 IO
  • Redis TTL 热缓存 + DB 持久化双写,Redis 故障时自动回退到 DB
  • 启动时预扫描参数类型,无 context 参数的命令零 IO

需要新建的文件

1. 配置类

XinjingDaily.Bot.Infrastructure/Configs/ContextConfig.cs

namespace XinjingDaily.Bot.Infrastructure.Configs;

/// <summary>
/// Context 系统配置
/// </summary>
public sealed record ContextConfig
{
    /// <summary>
    /// Redis TTL(秒),默认 1800(30 分钟)。
    /// </summary>
    public int TtlSeconds { get; init; } = 1800;
}

AppSettings 中新增字段:

public ContextConfig? Context { get; init; }

2. Redis DTO

XinjingDaily.Bot.Infrastructure/Bot/Context/ContextRedisDto.cs

namespace XinjingDaily.Bot.Infrastructure.Bot.Context;

public sealed class ContextRedisDto
{
    public int DbId { get; set; }
    public int UserId { get; set; }
    public long ChatId { get; set; }
    public string Command { get; set; } = string.Empty;
    public string Mode { get; set; } = string.Empty;
    public Dictionary<string, string> Data { get; set; } = [];
    public DateTime ModifyAt { get; set; } = DateTime.UtcNow;
}

3. Attribute

XinjingDaily.Bot.Infrastructure/Attribute/ContextFilterAttribute.cs

namespace XinjingDaily.Bot.Infrastructure.Attribute;

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class ContextFilterAttribute : System.Attribute
{
    public string Mode { get; }
    public ContextFilterAttribute(string mode) => Mode = mode;
}

4. 懒加载 Store

XinjingDaily.Bot.Infrastructure/Bot/Context/LazyContextStore.cs

实现要点:

  • 构造时只保存 Func<Task<ContextRedisDto>> 加载函数,不执行 IO
  • IsLoadedIsDirty 属性
  • EnsureLoadedAsync():首次调用触发 IO,后续直接返回
  • GetModeAsync()SetModeAsync(string)ClearModeAsync()
  • GetAsync<T>(string)SetAsync<T>(string, T)(超过 2000 字符时抛出 InvalidOperationException)、TryGetAsync<T>(string, Action<T?>)RemoveAsync(string)ClearAsync()
  • ExportDto():未加载或无脏数据时返回 null
  • MarkClean()SerializeData()GetMode()(同步,仅在已加载时有效)、GetDbId()

5. PrivateContext

XinjingDaily.Bot.Infrastructure/Bot/Context/PrivateContext.cs

namespace XinjingDaily.Bot.Infrastructure.Bot.Context;

public sealed class PrivateContext
{
    internal LazyContextStore Store { get; }
    internal PrivateContext(LazyContextStore store) { Store = store; }

    // Mode 同步属性(仅已加载时有值)
    public string Mode => Store.IsLoaded ? Store.GetMode() : string.Empty;

    public ValueTask<string> GetModeAsync() => Store.GetModeAsync();
    public ValueTask SetModeAsync(string mode) => Store.SetModeAsync(mode);
    public ValueTask ClearModeAsync() => Store.ClearModeAsync();

    public ValueTask<T?> GetAsync<T>(string key) => Store.GetAsync<T>(key);
    public ValueTask SetAsync<T>(string key, T value) where T : notnull => Store.SetAsync(key, value);
    public ValueTask RemoveAsync(string key) => Store.RemoveAsync(key);
    public ValueTask ClearAsync() => Store.ClearAsync();

    internal bool IsDirty => Store.IsDirty;
}

6. GroupContext

XinjingDaily.Bot.Infrastructure/Bot/Context/GroupContext.cs

namespace XinjingDaily.Bot.Infrastructure.Bot.Context;

public sealed class GroupContext
{
    internal LazyContextStore UserStore { get; }
    internal LazyContextStore ChatStore { get; }

    internal GroupContext(LazyContextStore userStore, LazyContextStore chatStore)
    {
        UserStore = userStore;
        ChatStore = chatStore;
    }

    // 用户在群内(默认操作)
    public string Mode => UserStore.IsLoaded ? UserStore.GetMode() : string.Empty;
    public ValueTask<string> GetModeAsync() => UserStore.GetModeAsync();
    public ValueTask SetModeAsync(string mode) => UserStore.SetModeAsync(mode);
    public ValueTask ClearModeAsync() => UserStore.ClearModeAsync();
    public ValueTask<T?> GetAsync<T>(string key) => UserStore.GetAsync<T>(key);
    public ValueTask SetAsync<T>(string key, T value) where T : notnull => UserStore.SetAsync(key, value);
    public ValueTask RemoveAsync(string key) => UserStore.RemoveAsync(key);
    public ValueTask ClearAsync() => UserStore.ClearAsync();

    // 群组公共(Chat 前缀)
    public string ChatMode => ChatStore.IsLoaded ? ChatStore.GetMode() : string.Empty;
    public ValueTask<string> GetChatModeAsync() => ChatStore.GetModeAsync();
    public ValueTask SetChatModeAsync(string mode) => ChatStore.SetModeAsync(mode);
    public ValueTask ClearChatModeAsync() => ChatStore.ClearModeAsync();
    public ValueTask<T?> ChatGetAsync<T>(string key) => ChatStore.GetAsync<T>(key);
    public ValueTask ChatSetAsync<T>(string key, T value) where T : notnull => ChatStore.SetAsync(key, value);
    public ValueTask ChatRemoveAsync(string key) => ChatStore.Rem...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

*This pull request was created from Copilot chat.*
>
@chr233
chr233 marked this pull request as ready for review June 1, 2026 05:15
Copilot AI review requested due to automatic review settings June 1, 2026 05:15

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review any files in this pull request.

@chr233 chr233 closed this Jun 1, 2026
Copilot stopped work on behalf of chr233 due to an error June 1, 2026 05:15
Copilot AI requested a review from chr233 June 1, 2026 05:15
@chr233 chr233 reopened this Jun 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants