在业务代码里驱动流程

启动它、喂它、停掉它,都发生在你平时写的代码里,比如一个 HTTP handler、一个支付回调、一个命令行工具。这些操作都走 Redis,调用方不必是跑流程的那个进程。

本页目录

Trigger

func (t *Server) Trigger(ctx context.Context, flowId string, data MetaData, opts ...TriggerOption) (string, error)

启动一个流程,拿回 callId。MetaData 就是流程的入参,流程里用 ctx.MetaData(k) 读。WithDelay(d) 让它晚一点再开始。

callId, err := tick.Trigger(ctx, "order/close", gotick.MetaData{    "order_id": order.Id,    "user_id":  order.UserId,}, gotick.WithDelay(5*time.Second))

WithKey

func WithKey(key string) TriggerOption

用你本来就有的标识给这次调用命名,比如订单号、用户 ID,之后就用它寻址。这样就不必在表上多存一列 gotick 生成的 callId,那一列对业务没有任何意义。作用域是 (flow, key),两个不同的 flow 用同一个订单号不会互相干扰。

_, err := tick.Trigger(ctx, "order/close", meta, gotick.WithKey(order.Id)) // later, from anywhere, with no callId storederr = tick.CancelByKey(ctx, "order/close", order.Id, "user canceled")

Supersede

同一个 (flow, key) 再触发一次,前一个还没结束的调用自动取消,只有最后一次继续跑。旧的那次落进 canceled 终态,并写明被谁顶替了,不是凭空消失,检查界面上还看得到。调用一进终态,绑定立刻解除,同一个 key 之后还能再跑。注意这是顶替,不是去重:没有「已经有一个在跑就别开新的」这种模式。

// each edit supersedes the unfinished run from the edit before itfor _, edit := range edits {    tick.Trigger(ctx, "doc/render", meta, gotick.WithKey(edit.DocId))}// only the last one reaches its final step

SendSignal

func (t *Server) SendSignal(ctx context.Context, callId, key string, value any) (bool, error)func (t *Server) SendSignalByKey(ctx context.Context, flowId, key, signal string, value any) (bool, error)

把流程等的那个东西递进去。返回的 bool 表示这个信号有没有被采纳。false 说明那个位置已经定死了:要么被更早的信号占了,要么被超时占了。把 false 当情报看,不是错误,你就是靠它知道自己输了这场赛跑。

// in your payment webhook handleraccepted, err := tick.SendSignalByKey(ctx, "order/close", orderId, "paid", Payment{    TradeNo: notify.TradeNo,    Amount:  notify.Amount,})if err != nil {    return err}if !accepted {    // the timeout already won: the order is closed, so refund instead    return refund(ctx, notify.TradeNo)}

Cancel

func (t *Server) Cancel(ctx context.Context, callId, reason string) errorfunc (t *Server) CancelByKey(ctx context.Context, flowId, key, reason string) error

不管调用停在哪一步,都能让它停下。背后做了三件事:每一轮开头无条件读一次取消标志;正在睡觉或等信号的流程立刻唤醒,不用等它自己的定时器;正在执行的 Task 拿到的 context 会被 cancel,好让你的代码提前返回,这一件是每 3 秒轮询一次的。取消一个不存在的调用返回 ErrRunNotFound,取消一个已经结束的返回 ErrRunNotCancelable,所以迟到的取消改不了已经完成的结果。

err := tick.Cancel(ctx, callId, "user canceled") switch {case errors.Is(err, gotick.ErrRunNotFound):    // never existed, or already cleaned upcase errors.Is(err, gotick.ErrRunNotCancelable):    // already finished — nothing was rewritten}

Client 有个限制:它只有 Trigger(ctx, flowId, data, delay),没有 options,所以用不了 WithKey,也没有 SendSignal 和 Cancel。不跑调度的进程要用这些,就用 NewServerFromConfig 建一个 Server,然后不调 StartServer。触发、发信号、取消都走 Redis,调度器没启动也照样能用。

Render diagnostics