Session
Session 是 Web 应用最常见的状态管理手段。ACE 提供开箱即用的 sessionMiddleware,基于 Cookie 存储 Session ID,实际数据保存在可插拔的 SessionStore 中。
注册中间件
在应用入口或 @Middleware 装饰的类中注册:
cangjie
import ace_web.*
import ace_web.session.*
let app = App()
app.use(sessionMiddleware(
MemorySessionStore(), // 存储后端
"ace.sid", // Cookie 名称
1800_000, // TTL:毫秒,此处 30 分钟
defaultSidGenerator // Session ID 生成器(可选,默认 UUID v4)
))sessionMiddleware 参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
store | SessionStore | 存储后端,内置 MemorySessionStore |
cookieName | String | 写入客户端的 Cookie 名,默认建议 ace.sid |
ttlMs | Int64 | 会话有效期(毫秒),超时自动失效 |
sidGenerator | () -> String | Session ID 生成函数,默认使用安全随机 UUID |
辅助函数
读写 Session 无需直接操作 store,使用框架提供的辅助函数即可:
cangjie
import ace_web.session.*
// 存入键值
await sessionSet(ctx, "userId", "42")
await sessionSet(ctx, "role", "admin")
// 读取键值(返回 Option<String>)
let uid = await sessionGet(ctx, "userId") ?? "anonymous"
// 读取当前请求的 Session ID
let sid = sessionId(ctx)
// 销毁整个 Session(退出登录)
await sessionDestroy(ctx)| 函数 | 返回类型 | 说明 |
|---|---|---|
sessionSet(ctx, key, value) | Unit | 写入 Session 键值 |
sessionGet(ctx, key) | Option<String> | 读取 Session 值,不存在返回 None |
sessionId(ctx) | String | 读取当前 Session ID |
sessionDestroy(ctx) | Unit | 清除整个 Session 并失效 Cookie |
内置存储:MemorySessionStore
MemorySessionStore 将所有 Session 数据保存在进程内存中,适合开发调试。
cangjie
let store = MemorySessionStore()生产环境不可用
MemorySessionStore 不持久化、不跨进程共享,重启即丢失。多实例部署或生产环境请替换为 Redis/数据库后端。
自定义 SessionStore
实现 SessionStore 接口即可接入任意存储:
cangjie
interface SessionStore {
func get(sid: String): Future<Option<HashMap<String, String>>>
func set(sid: String, data: HashMap<String, String>, ttlMs: Int64): Future<Unit>
func remove(sid: String): Future<Unit>
func touch(sid: String, ttlMs: Int64): Future<Unit> // 刷新 TTL
}以 Redis 为例(伪代码):
cangjie
class RedisSessionStore <: SessionStore {
let client: RedisClient
public func get(sid: String): Future<Option<HashMap<String, String>>> {
let raw = await client.get("sess:\(sid)")
return raw.map { parseJson($0) }
}
public func set(sid: String, data: HashMap<String, String>, ttlMs: Int64): Future<Unit> {
await client.setex("sess:\(sid)", ttlMs / 1000, stringifyJson(data))
}
public func remove(sid: String): Future<Unit> {
await client.del("sess:\(sid)")
}
public func touch(sid: String, ttlMs: Int64): Future<Unit> {
await client.expire("sess:\(sid)", ttlMs / 1000)
}
}注册时替换存储后端:
cangjie
app.use(sessionMiddleware(RedisSessionStore(client), "ace.sid", 1800_000))登录/鉴权示例
登录接口——写入 Session:
cangjie
@Controller["/auth"]
class AuthController {
@Inject var userService: UserService
@Post["/login"]
func login(ctx: Context): Future<Unit> {
let body = ctx.formValue("username") ?? ""
let passwd = ctx.formValue("password") ?? ""
let user = await userService.verify(body, passwd)
match user {
case Some(u) =>
await sessionSet(ctx, "userId", u.id)
await sessionSet(ctx, "role", u.role)
ctx.json("""{"ok":true}""")
case None =>
ctx.status = 401
ctx.json("""{"ok":false,"msg":"credentials invalid"}""")
}
}
@Get["/profile"]
func profile(ctx: Context): Future<Unit> {
let uid = await sessionGet(ctx, "userId") ?? throw Unauthorized()
let user = await userService.findById(uid)
ctx.json(stringifyJson(user))
}
@Post["/logout"]
func logout(ctx: Context): Future<Unit> {
await sessionDestroy(ctx)
ctx.json("""{"ok":true}""")
}
}多实例部署
水平扩展时,所有实例必须共享同一个 Session 存储,推荐:
- Redis:最常见,支持 TTL、集群;实现
RedisSessionStore。 - 数据库:适合对审计有要求的场景;TTL 靠定时清理任务维护。
Cookie 安全
生产环境建议在 sessionMiddleware 配置中开启 secure: true(仅 HTTPS 传输)和 httpOnly: true(防 XSS 读取 Cookie)。