做后端服务或者前端框架的同学一定被“这个请求的上下文怎么传下去”这个问题折磨过。日志要traceId、数据库要租户ID、权限要用户信息、i18n要语言环境这些东西如果靠函数参数一个个往下传传不了三层代码就炸了。我在自己维护的一个内部基础设施项目里整理了一套叫context-mode的上下文管理模式专门解决这类跨层传递、并发隔离和生命周期管理的问题。这篇文章就把这套模式完整拆开讲清楚它解决什么问题、内部数据结构怎么设计、核心实现代码怎么写、以及我在实际业务里踩过的那些坑。适合正在做中后台系统、微服务网关、Node.js 中间件或者前端状态管理的同学参考哪怕你只是写 CRUD学完这一套代码里横飞的“上帝参数”也能立刻少一半。1. context-mode 到底是什么一个围绕“上下文传递”的工程实践1.1 先理解上下文的本质我们天天说上下文但上下文到底是什么我习惯把它理解成“一次业务执行过程中所有参与者都需要访问、但又不愿意通过参数显式传递的公共数据”。这个概念跟编程语言里的作用域很像比如 JavaScript 的闭包外层变量内层函数直接用但闭包是静态词法作用域它跟调用链没关系。而现实业务里上下文往往是动态的。举个例子你写了一个支付接口入口处解析 token 拿到 userId接下来调用了 orderService.create()里面又调用了 paymentService.pay()最后在日志里要打 userId。正常写法是把 userId 一路传下去create(userId, data)、pay(userId, order)。传两三个参数还好但真实系统里一个下单流程可能要拆七八个方法每个方法都要带 userId、tenantId、requestId、locale代码瞬间变成“参数泥潭”。这就是没有上下文管理机制的系统典型症状。而 context-mode 要做的就是把这些公共数据放进一个“隐形的背包”里让它沿着调用链自动传递。任何一层代码需要用到的时候直接从这个背包里取不用跟参数列表发生任何关系。1.2 一个定位它属于“调用上下文”而非“全局状态”很多人看到这可能会说这不就是全局变量吗绝对不是。全局变量是进程级别的、无差别的共享。它的问题在于并发请求进来A 用户的数据可能被 B 用户覆盖一个请求里改掉全局值另一个请求全被影响。这在单线程异步密集的 Node.js 环境里就是灾难。context-mode 的设计核心在于它把上下文跟“一次具体的调用链”绑定而不是跟进程绑定。同样一段代码线程 A 调用和线程 B 调用各自拿到的上下文必须是你好我好互不干扰。这就牵扯出三个关键问题上下文怎么创建怎么沿着调用链传播并发场景下怎么保证各个调用链隔离调用链结束之后上下文怎么销毁避免内存泄漏这三个问题就是 context-mode 全部分析的起点。后面所有的数据结构、API 设计、实现细节全都是围绕这三个问题展开的。2. 核心设计拆解context-mode 的三个关键数据结构2.1 Context 对象不可变性和快照模型决定做这套模式后我第一件事是定义上下文的载体。最简单的方案是搞一个可变的 Map往里塞键值对但实际用下来你会发现可变对象在嵌套场景里会带来无穷无尽的 Bug。考虑这个场景A 请求进入中间调用 B 子任务B 给上下文暂存一个值比如标记这个子任务是重试。用可变 MapB 改的就是 A 那份B 执行完忘了清理A 后面所有逻辑都能看到 B 写入的数据。这种脏数据污染在正式环境里极难排查因为你不知道是哪一层在什么时候塞进去的。所以我最终把 Context 设计成了不可变对象。每次写入数据返回的是一个新的 Context旧 Context 保持不变。这就像 git 的 commit每次提交产生新快照历史记录永远可以回溯。它的好处是不同层级的代码各自持有自己的快照谁也不会覆盖谁的数据。配合上结构的 shallow copy成本也不算高。数据结构大致长这样interface Context { readonly parent: Context | null; // 指向父级快照 readonly data: Mapstring, unknown; // 当前层新增的数据 }读取的时候先查自己的 data查不到沿着 parent 向上找。这就兼顾了“局部覆盖”和“继承父级数据”的需求和原型链的思路是同构的。2.2 上下文的结构设计Root、Local、Child 三层搞了不可变快照之后下一步要设计上下文的层级结构。我在 context-mode 里划分了三层Root、Local、Child。Root 是进程启动时创建的根上下文存放全局性的静态配置比如环境名、数据中心的 ID、默认语言这些“天生就不该变”的东西。Local 是在一次业务入口HTTP 请求进来、消息队列拿到一条消息、定时任务触发时创建的。它接收 entry 阶段解析出的动态数据比如 userId、traceId、租户 ID。Local 的生命周期跟这一次业务绑定业务结束它就要销毁。Child 是在一个业务内部需要临时追加数据时创建的。它是 Local 的下一代快照典型场景是主订单流程里要调子订单处理子订单处理需要标记一个来源字段于是基于 Local 派生一个 Child子任务结束Child 直接丢弃Local 不受影响。从 Root 到 Local 再到 Child整个结构形成一个树。任何一段代码拿到的“当前上下文”其实是这个树上的一个节点。我只要能保证当前指令所处节点是准确的取数据就不会错。这里“指令所处节点准确”这一句恰恰是最难的部分也是后面异步实现里要死磕的重点。2.3 生命周期管理从创建到销毁的整套规范定义好结构之后必须配套一套生命周期规范不然结构再漂亮也会被用成一团乱麻。我最后定下来的规范是这么几条Root 在进程启动时创建一次全程不销毁。Local 在入口创建必须绑定一个结束事件HTTP 请求的 res finished、MQ 消费的 ack、定时任务的 finally。任何手动创建的子上下文用完必须调用 restore 或者 release 回到父级我的代码里统一用 run 方法包一层由框架保证执行完后自动回退。第二条规范最重要的理由是防泄漏。Node.js 后端最典型的内存泄漏来源就是这类“长生命周期对象”被“短生命周期请求”不断挂载。如果每次请求都往一个全局的 Map 里塞数据而不清理第二天内存曲线就能给你上一课。所以context-mode 里严格规定谁创建谁负责结束能自动结束绝不交给调用方手动结束。生命周期的核心代码是用 try/finally 保证任何情况下都能正确恢复function withContext(local: Context, fn: () Promiseunknown) { const prev currentContext; currentContext local; return Promise.resolve() .then(fn) .finally(() { currentContext prev; }); }这段代码看起来简单却是整个模式的基石。后面所有异步方案都是在“正确恢复 currentContext”这一点上做文章。3. 从零实现一个轻量 context-mode核心代码实操3.1 定义基础类型与能力接口真正动手写代码我先把对外暴露的 API 定下来。参照社区里成熟库比如 cls-hooked、AsyncLocalStorage的使用习惯定成四个方法createRoot()创建根上下文。enterWith(localContext)在当前异步链路里注入一个上下文。withContext(ctx, fn)在 fn 执行期间使用指定上下文执行完恢复原状。getData(key)/setData(key, value)读取和写入。setData 遵循不可变原则返回新的 Context不修改原对象。这样就把“读链路”和“写链路”天然解耦开。接下来定义 Context 对象本身。我选用了 Map 作为底层存储因为键值操作的复杂度是 O(1)而且 Map 的迭代顺序是插入序对很多场景很友好export class Context { constructor( private readonly parent: Context | null null, private readonly data: Mapstring, unknown new Map() ) {} get(key: string): unknown { if (this.data.has(key)) return this.data.get(key); return this.parent ? this.parent.get(key) : undefined; } set(key: string, value: unknown): Context { const next new Map(this.data); next.set(key, value); return new Context(this.parent, next); } }这里 set 方法每次 new Map(this.data) 做一次浅拷贝。因为 Map 存的是引用浅拷贝只复制引用不深拷贝对象内存开销很小实测下来一万个字段级别的 context 传递完全无压力。3.2 核心实现让上下文顺着调用链跑起来有了 Context 类剩下最关键的就是“当前上下文”是怎么被记录和恢复的。初版我用了一个模块级变量let currentContext: Context | null null; export function getCurrentContext(): Context | null { return currentContext; } export function enterWith(ctx: Context) { currentContext ctx; } export function withContext(ctx: Context, fn: () unknown) { const prev currentContext; currentContext ctx; try { return fn(); } finally { currentContext prev; } }这个版本在同步场景下完美工作。但你做业务系统不可能全是同步代码一遇到异步这个模块级变量立刻暴露问题。我测试时写过这样一段enterWith(localCtx); setTimeout(() { console.log(getCurrentContext()); // 期望 localCtx实际可能是 null 或者另一个请求的 context }, 100);问题在于setTimeout 里的回调被执行时它是在一个新的事件循环 tick 里压根不记得当初是在哪个上下文里注册的。整个线程只有一个 currentContext谁最后赋值回调里就是谁。这就是典型的“异步上下文丢失”。3.3 异步场景下的上下文传递AsyncLocalStorage 与 Async Hooks解决异步上下文丢失Node.js 官方给出的答案就是AsyncLocalStorage。它底层基于 async_hooks通过跟踪异步资源的创建与回调关系为每一个异步资源维护一份独立的上下文存储。简单理解就是给每条异步链路上了一个隐形的标签链路上的任意一段回调都能拿回自己对应的存储。我的实现里把模块级变量换成了 AsyncLocalStorage 实例import { AsyncLocalStorage } from async_hooks; const als new AsyncLocalStorageContext(); export function runWithContext(ctx: Context, fn: (...args: any[]) any) { return als.run(ctx, fn); } export function getCurrentContext(): Context | null { return als.getStore() ?? null; }这里als.run(ctx, fn)的语义是在 fn 执行期间所有同步调用和 fn 内部创建的异步资源都会自动关联到 ctx 这份存储。也就是说只要入口处用 runWithContext 包一层下面所有 setTimeout、Promise、async/await 回调老老实实都能拿到同一个 Context。我实测过一个三层嵌套 Promise 定时器的测试用例所有回调里取到的都是同一个 Local 上下文串号问题直接消失。选 AsyncLocalStorage 而不是自己写 async_hooks原因是 async_hooks 的 API 太底层你要监听每个异步资源的 init、before、after、destroy 事件自己维护一个 id 到上下文的映射表稍不留神就是性能灾难和内存泄漏。阿里开源框架 midway 早期就是自研了类似链路后来也切到 AsyncLocalStorage性能稳定很多。AsyncLocalStorage 在 Node.js 16 已经标记为稳定 API生产环境可以放心用。要注意一点AsyncLocalStorage 的 run 和 withContext 的 finally 恢复并不冲突。run 会自己处理上下文关联和恢复我的 withContext 实际上就是包了一层 run 语义。所以最终对外我统一暴露成export function withContext(ctx: Context, fn: () any) { return als.run(ctx, fn); }3.4 与业务代码的集成案例实现做完看一个完整的业务集成场景。假设一个 Express 接口要做登录校验 写入审计日志app.post(/api/order, (req, res) { const local root .set(userId, req.headers[x-user-id]) .set(traceId, req.headers[x-trace-id] || uuid()); withContext(local, async () { // 这两行调用内部不需要传递 userId const order await orderService.create(req.body); await auditService.log(order.create, { orderId: order.id }); res.json({ code: 0, data: order }); }); }); // orderService 内部 async create(data: any) { const ctx getCurrentContext(); const userId ctx?.get(userId); // 直接拿不用传参 const traceId ctx?.get(traceId); // 业务逻辑... }这样一来orderService.create 的入参里只有业务数据没有横切数据审计日志、日志系统、权限校验各取所需互不干扰。下游所有函数只要愿意都可以通过 getCurrentContext() 获取公共数据不破坏原有的函数签名。4. 常见问题与排查技巧实录4.1 上下文泄漏忘记恢复的后果我最早自己写这套工具时withContext 的实现没包 finally改成export function withContext(ctx: Context, fn: () any) { currentContext ctx; const result fn(); currentContext prev; return result; }同步场景测不出问题一跑异步就现出原形如果 fn 里有一个 await后面的代码执行时 currentContext 已经恢复成 prev 了你取不到任何子上下文的数据。更严重的是fn 内部创建的 Promise 回调可能在你恢复之后才被调度于是它拿到的是恢复后的上下文数据张冠李戴。排查这类问题有个土办法在关键入口和出口各打一条日志输出 currentContext 的 id。context 创建时赋一个递增 id恢复时打日志看看哪条异步链里 id 跳跃式变化那个点就是泄漏点。4.2 并发串号日志里 traceId 乱飞用同步变量版本上线时我出过一个特别隐蔽的 Bug流量高峰时日志里 A 请求的 traceId 突然变成 B 请求的。定位了很久最后发现问题出在一个特别基础的场景——Promise.all 并发。const results await Promise.all([ taskA(), // 注册了异步资源 taskB(), // 注册了异步资源 ]);taskA 和 taskB 的执行都依赖入口处的 withContext 环境。用裸的模块级变量Promise.all 里的多个并行任务共享同一个 currentContext只要其中任何一个任务临时切换了 context整个 Promise.all 的上下文就跟着漂移。用 AsyncLocalStorage 之后每个异步资源有独立的 store并发任务之间彻底隔离这个坑才算填完。给大家一个小建议并发问题不一定要靠高并发压测才能发现写一个 50 个并发 Promise 随机 setTimeout 的单元测试断言每个任务拿到的 context 都与预期一致基本就能覆盖 90% 的串号场景。4.3 嵌套覆盖与合并策略另一个高频坑是上下文嵌套。项目里常有这种逻辑外层是租户 A 的上下文内层要调租户 B 的接口于是内层又 set 了一个租户 B 的 context。问题在于内层 context 的 parent 是谁我最初的设计里内层 set 的 context parent 存的直接是当前上下文读取时优先内层数据内层没有再向上找外层。这样租户 B 的子上下文里除了租户 ID 被覆盖其他信息比如 traceId依然继承自外层这通常是符合预期的。但如果你期望内层彻底隔离什么都不要继承就得在创建子 context 时主动把 parent 置为 null。所以我提供了两个方法extend()生成继承型子上下文默认detach()生成隔离型子上下文。业务里到底用哪个取决于你对数据边界的定义单纯的业务标记用 extend跨租户调用、第三方回调这类场景建议直接 detach。4.4 性能损耗与优化技巧说到性能很多人一听到 async_hooks 就下意识觉得慢。早期 async_hooks 确实有性能问题但在 Node.js 16V8 8.9之后AsyncLocalStorage 已经做了大版本优化。我在一台 4 核 8G 的开发机上用 autocannon 做过压测纯 Express 接口加了 context-mode 和不加对比单核 QPS 从 5200 掉到 4900 左右损耗约 6%在可接受范围。如果对性能极度敏感有几个优化技巧用 context 但不频繁创建。入口处创建一个 Local中间层尽量复用少用 set。减少嵌套的 als.run 层数。每一层 run 都会注册异步资源嵌套越多开销越大。把与业务无关的静态配置放在 Root不要每次请求重复注入口径。实测下来把静态配置放 Root 后请求内 set 次数从平均 5 次降到 1 次QPS 还能再涨 2% 到 3%。这些优化没有魔法就是减少不必要的异步资源注册。5. context-mode 的选型对比与落地建议5.1 三种方案对比参数传递、全局变量、上下文管理器很多团队在“怎么传递公共数据”这件事上一直摇摆。我这里直接放一个横向对比性能数据基于我自己的压测不同机器会有差异但趋势不变方案侵入性并发隔离异步支持维护成本适用场景函数参数传递高天然隔离天然支持高改签名牵一发动全身调用层级少于 3 层的小型方法全局变量极低无不支持极高串号后很难查普通脚本工具不适合在线业务context-mode低强强需 ALS低规范后一键全局生效中大型后端服务、中间件、框架我一直强调参数传递不是不行而是它把“业务参数”和“环境参数”搅在一起。你的函数签名里如果出现 userId、tenantId、requestId 这一类字段且它们和 createOrder 没有直接业务关系那它们就不该出现在这里应该挪到上下文里。5.2 落地时间线从单模块试点到全量推广如果团队决定引入 context-mode我不建议一口气全量改造。推荐路线是第一步只接入 traceId 和日志体系。入口处注入 context日志中间件统一从上下文取 traceId。这个改动对业务逻辑零侵入价值立刻可见日志从此有了全链路 ID。第二步把租户 ID、当前用户、语言环境这些横切数据迁入上下文。这一阶段需要各业务方配合把原来手动传参的地方改掉。要注意分批推进先改同一链路上相邻的模块避免一个链路里一半用参数一半用上下文。第三步对外暴露规范化 API把 getCurrentContext 隔离成框架内部能力业务方只通过高阶函数 withContext 使用避免到处裸调方便未来替换实现。5.3 测试策略上下文也该有专属的测试用例最后一个想强调的点是测试。很多项目上线后出问题就是因为上下文相关的代码没有单测覆盖。写业务单测时不关心上下文等排查问题时才发现上下文链路早已千疮百孔。建议每个用到 context-mode 的模块配一组专项测试用例基础值读取与覆盖逻辑父子层级、继承与隔离。并发场景下 50 路并行任务各自拿到的上下文一致。异步链路中setTimeout、Promise.all、事件监听回调上下文不丢失。withContext 在 fn 抛异常后能自动恢复原上下文。内存泄漏回归连续创建 10 万次请求级 context内存不持续增长。这类测试写起来麻烦但写完基本一劳永逸。我自己的项目里这几条用例跑一次只要两三秒却拦住了大量线上潜在的串号和数据污染问题。写在最后的一个小技巧根据我个人的落地经验context-mode 这类工具最有价值的位置其实不在业务代码里而在框架层。把上下文能力做成基础设施业务侧只感知到“我有一个 withContext 可以调用里面能 take 到当前用户、当前 traceId”就可以了。如果业务代码里到处是裸的 getData / setData说明抽象还没收敛这时候先别急着铺开停下来把治理规则定好再推。最后再分享一个调试小技巧给 Context 加一个 toJSON 方法日志打印时直接把当前上下文展开成对象配合 traceId 排查问题效率能翻一倍。
企业数字化 ERP 产品动态
相关推荐
基于MATLAB的反导导弹末制导螺旋弹道仿真与龙格库塔法实现 简介:面向导弹工程专业学生、国防科研人员与制导控制系统设计人员,这套MATLAB源码包聚焦反导导弹末制导与控制问题,以龙格库塔法为核心数值工具,完整覆盖目标跟踪、预测模型、制导法则设计、螺旋弹道计算、迭代调整、安全限制与实… · 2026/11/14 3:38:50
FVS报表工具中自定义重置与参数传递的实现方法 1. 项目背景与需求分析在数据可视化报表开发中,FVS(FineReport Visualization System)作为一款专业的企业级报表工具,其交互设计直接影响用户体验。最近在开发一个销售数据分析看板时,遇到了一个典型需求:用… · 2026/11/14 3:38:49
Android推送集成封装指南:初始化、厂商通道与通知点击全链路 做 Android 推送集成,最令人头疼的环节常常是:反复接入同一家服务商,再经历一遍相同的踩坑过程。三四个 App 下来,初始化时机、进程判断、别名绑定、厂商通道分包、通知点击拉起、8.0 渠道适配、12 的 PendingIntent 限制、13 的通… · 2026/11/11 10:29:50
苍溪在办理电工证时间全流程解析,郑州报考避坑指南 苍溪在办理电工证时间全流程解析,郑州报考避坑指南 工地太忙根本没时间复习考试,这几乎是每位建筑工人的共识。特别是像苍溪这样的地方,很多工人常年在工地作业,生活节奏快,工作强度大,想抽时间复习电工证考试几乎是奢望。再加上郑州报考避坑指南的热度… · 2026/11/14 3:38:32
浙江低压电工证k23含金量高吗?手把手教你换证流程 浙江低压电工证k23含金量高吗?手把手教你换证流程 你是不是正愁没证找不到电工工作?浙江低压电工证k23是很多电工朋友进工地、进厂子的“通行证”。要是证书到期了还没换,连面试机会都没了。今天我就把从报名到换证的全流程说清楚,特别是那些在济源… · 2026/11/14 3:38:32
特种工温老师的简介怎么报名外省证也能转过来 特种工温老师的简介怎么报名外省证也能转过来 之前考的证在外省能不能转过来?这个问题其实困扰了不少特种作业人员。特别是像驻马店项目现场管理员这类岗位,经常需要跨省调动,证件管理就显得尤为重要。如果你手里有一本外省考的特种作业操作证,别急着扔掉… · 2026/11/14 3:38:32
高压电工证能进物业吗千万别踩坑 高压电工证能进物业吗千万别踩坑 之前考的证在外省能不能转过来,这是很多特种作业人员最头疼的问题之一。尤其是拿着高压电工证的人,总想着能不能在本地找个好点的岗位,比如进物业公司干电工,省心又稳定。但现实是,高压电工证虽然好,… · 2026/11/14 3:38:32
哪里可办理电工证上岗必须要这个证吗 之前考的证在外省能不能转过来 电工证办理全攻略及电子证书详解 之前考的证在外省能不能转过来?这是很多特种作业人员最关心的问题。特别是像电工证这类必须持证上岗的证书,一旦不能转移,不仅影响工作,还可能带来经济损失。现在,越来越多的人开始关注… · 2026/11/14 3:37:32
电工证丢失还能补吗?千万别踩坑,丢了证等于失业 电工证丢失还能补吗?千万别踩坑,丢了证等于失业 你正在找工作,但手里的电工证突然丢了,这下怎么办?别急,先别慌, 电工证丢失还能补吗 ?答案是肯定的,但 千万别踩坑 ,补办流程如果不了解,可能会耽误你上岗、甚至影响到你的收入。… · 2026/11/13 22:30:50
电力电缆作业证直播报名入口与办理流程良心建议 电力电缆作业证直播报名入口与办理流程良心建议 你是不是也担心学历不高报不了名?别急,今天就给你讲讲 电力电缆作业证直播 的报名流程和那些你必须知道的干货, 良心建议 在这里,别错过。 电力电缆作业证直播报名需要什么条件?… · 2026/11/13 5:40:45
英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑 英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑 怕考不过白交培训费?英德电工证报考费多少钱,你真的了解吗?现在市面上培训机构五花八门,价格参差不齐,很多人一不小心就花了冤枉钱,结果还考不过。别急,今天就来给你说清楚英德电工证的… · 2026/11/12 23:27:43
有初级电工证怎么工作 2026河南最新政策怎么应对 有初级电工证怎么工作 2026河南最新政策怎么应对 之前考的证在外省能不能转过来?这个问题我每天被问上百次。2026年河南最新政策对证书跨省转移有了明确说明,特别是南阳市的从业者,需要特别关注。… · 2026/11/14 0:00:31
电工证过期了不知道怎么复审?考电工证自学书+官方报名入口全攻略 电工证过期了不知道怎么复审?考电工证自学书+官方报名入口全攻略 证书过期了不知道怎么复审延期?很多人遇到这种情况,要么是工作太忙没时间处理,要么是不知道具体流程,导致证书失效,影响工作和项目进展。其实复审并不难,关键是要找准官方报名入口,掌… · 2026/11/14 0:00:31
为什么要低压电工证实操考试考什么 为什么低压电工证值不值得考?别再被中介骗了 你是不是也经常看到网上有人在问“低压电工证到底值不值得考”?或者在找报名点时,担心被中间人骗钱?这些问题,我懂,我也经历过。作为一名在应急管理行业摸爬滚打多年的从业者,我深知低压电工证不仅是一个从… · 2026/11/14 0:00:31
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/13 6:12:21
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/13 6:11:51
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/13 22:31:30