React应用中用户操作日志缺少会话关联怎么办?
在做审计追踪时发现,用Redux记录的用户操作日志里经常找不到对应用户ID。比如用户登录后触发的fetchData操作,日志里action的user字段会是null
尝试过在store里存用户信息,然后在logging middleware里这样获取:
const userId = store.getState().auth.user.id;
action.meta.user = userId;
但发现当用户刚登录完成时,某些异步操作的meta.user还是空的。比如登录成功后立即调用的getProfile() action,日志显示user字段是undefined
查了store状态确实有用户数据,怀疑是中间件执行顺序问题?或者需要在action creator里手动携带用户信息?这样会不会破坏redux的单向数据流?
你现在的做法是在logging middleware里从store取用户ID,这本身没问题,但问题出在“什么时候取”。当登录请求回来触发登录成功的action时,这个action会先经过所有中间件,然后才更新到reducer里。而紧随其后的getProfile action可能在同一个事件循环里就被dispatch了。这时候虽然你觉得“登录完成了”,但实际上store里的user数据还没真正写进去——因为reducer还没执行完。
中间件的执行顺序是这样的:所有中间件都会按定义顺序依次处理每个action。如果你的日志中间件在auth相关的异步逻辑之前,那它拿到的状态就是旧的。
解决办法有几种,我推荐用第三种:
第一种简单粗暴:在action creator里手动加user信息。比如
但这确实有点破坏单一数据源的原则,而且容易忘记填,不推荐。
第二种改中间件顺序。确保你的日志中间件放在最外层,也就是最后执行。比如applyMiddleware的时候把它放最后。但这治标不治本,因为还是依赖状态更新的时机。
第三种也是我最推荐的:用redux-thunk或者redux-saga这类异步方案,在异步流程控制里保证顺序。比如用thunk的话:
或者更进一步,把日志逻辑也集成进去:
这样能确保每次拿的都是最新状态。而且你在thunk里面dispatch的时候,getState()拿到的就是当前最新的store状态,不会有时序问题。
另外补充一点,如果你坚持要用纯中间件方案,可以改造一下日志中间件,让它支持异步获取用户信息:
然后在需要延迟绑定用户信息的action上加个标记:
不过这种方式不够直观,调试起来麻烦。
总结一下:本质问题是同步假设遇到了异步现实。最好的解法是承认操作是有先后依赖的,用thunk这类工具明确表达这种依赖关系,而不是靠中间件的运气去碰时序。这样代码虽然多几行,但逻辑清晰,不容易出bug。
顺便说一句,这种审计日志的需求,后期可能会扩展到记录IP、设备信息等,建议早点抽象一个createAuditAction的工具函数,统一处理这些元数据注入。