DDD-layered login-state and session toolkit for MoonBit: aggregates, pure domain policies, async storage port, route guard DSL
Dependencies
moon update
moon add mldong/moon-token
moon add moonbitlang/async # async 运行时;库本身不依赖它,但调用方要| 能力 | 说明 |
|---|---|
| 双向映射 | token → login_id 正查 + login_id → token 族 反查;踢人/顶人/在线列表全靠反查 |
| 精确反馈 | 被踢 / 被顶 / 过期 / 闲置超时 / 无 token / 伪造 / refresh 重放——七种原因各报各的,绝不塌成"未登录" |
| 并发三态 | Coexist(默认共存)/ Supersede(顶人下线)/ Shared(同设备共用一枚) |
| 双层时效 | 签发时效 timeout + 活跃时效 active_timeout;"记住我"是长时效档 |
| 全量轮转 | rotate 换新整对,旧 access 与旧 refresh 同时失效;不校验绑定 access 是否存活(那是必现缺陷的来源) |
| 滑动续期 | SlideOnAccess(带 60s 节流窗)/ IdleMark(活跃标记) |
| 多账号体系 | realm 维度实例化,键位前缀隔离 |
| 可插拔存储 | async 仓储端口 + 意图补丁 FamilyPatch,换后端不改业务代码 |
| 守卫 DSL | 路径模式 + 豁免 + 追加断言,纯逻辑、不绑定任何 web 框架 |
| 领域事件 | 7 个事实事件,落库后 fire,观察者异常不影响主流程 |
| 可注入时钟 | 时效类特性不需要真等;测试与演示都靠它 |
pub(all) struct DemoPerms {
permissions : Array[String]
roles : Array[String]
}
// 业务只负责"给数",AND/OR 裁决在库里
pub impl @port.PermissionProvider for DemoPerms with fn get_permissions(
self,
_login_id,
_device,
) {
self.permissions
}
pub impl @port.PermissionProvider for DemoPerms with fn get_roles(self, _login_id, _device) {
self.roles
}
pub impl @port.PermissionProvider for DemoPerms with fn is_super_admin(
_self,
_login_id,
_device,
) {
false
}
pub DemoPerms with @port.PermissionProvider::{
get_permissions,
get_roles,
is_super_admin,
}
fn new_auth() -> @app.TokenAuth[@mem.MemoryStore, DemoPerms] {
let p : DemoPerms = { permissions: ["user:info"], roles: ["user"] }
@app.TokenAuth::new(
"user", // realm:账号体系
@app.TokenConfig::default(), // 十三项配置,默认值即推荐值
@mem.MemoryStore::new("user"), // 存储端口;换后端只改这一处
p,
@style.opaque_style(), // token 生成;取不到平台熵即 abort
)
}
async fn flow() -> (String, String) raise {
let auth = new_auth()
let result = auth.login("u1", device="pc") // 一次签发 access + refresh 整对
let login_id = auth.check_login(result.token) // 通过给 login_id
auth.kickout("u1", device="pc") |> ignore
let reason = auth.check_login(result.token) catch {
err => err.message()
}
(login_id, reason)
}
async test "被踢方拿到的是 KickedOut,不是笼统未登录" {
let (login_id, reason) = flow()
assert_eq(login_id, "u1")
assert_eq(reason, "not login: KickedOut")
}| 文档 | 讲什么 |
|---|---|
| 快速开始 | 装好、五分钟跑通一条完整链 |
| 核心概念 | realm、整对 token、反查族、双层时效、五种键位 |
| 登录与并发策略 | 三态策略各自跑出来是什么样 |
| 会话与踢人 | 在线列表、附加态、踢/顶/封禁/宽窗 |
| 刷新与轮转 | 全量轮转语义、重放防护、为什么不校验 access 存活 |
| 权限与角色 | SPI 供数、has_* 与 check_*、AND/OR |
| 路由守卫 | 模式匹配、豁免优先级、怎么挂到 HTTP 上 |
| 领域事件 | 7 个事件、码值表、落库后 fire |
| 错误词汇表 | 七种原因 + 五类错误,响应码映射建议 |
| 配置与默认值 | 每个默认值为什么是这个数 |
| 时钟与熵源 | 注入时钟、三档熵源与 abort 守卫 |
| 存储端口 | 怎么写自己的后端 |
| 测试指南 | 三场景怎么落地、假绿长什么样 |
| 数据模型 | 六类记录字段、关系、TTL 公式、Redis/SQL 物理映射、变更纪律 |
| 常见问题 | 集群、多 realm、与 JWT 的取舍、边界 |
Install
Download zipDDD-layered login-state and session toolkit for MoonBit: aggregates, pure domain policies, async storage port, route guard DSL
Dependencies