// ConvertDocxToDoc 将 DOCX 文档转换为 DOC 格式 func Conve...
创建于:2025年6月17日
创建于:2025年6月17日
// ConvertDocxToDoc 将 DOCX 文档转换为 DOC 格式
func ConvertDocxToDoc(ctx context.Context, docUrl string) (string, error) {
taskUUID := uuid.New().String()
status := &DocProcessStatus{
Status: Processing,
UpdatedAt: time.Now().Unix(),
DocUrl: docUrl,
UUID: taskUUID,
}
text// 设置初始状态到Redis if err := redis.DefaultDocRedis.SetDocProcessStatus(ctx, taskUUID, status); err != nil { logger.Ex(ctx, "ConvertDocxToDoc", "SetDocProcessStatus failed: %+v", err) return "", err } newCtx := context.Background() // 异步处理文档转换 go func() { conversion, err := ProcessFileConversion(newCtx, docUrl) if err != nil { logger.Ex(newCtx, "ConvertDocxToDoc", "ProcessFileConversion failed: %+v", err) status.Status = Failed _ = redis.DefaultDocRedis.SetDocProcessStatus(newCtx, taskUUID, status) return } oss, err := uploadDocToOss(newCtx, conversion, taskUUID) if err != nil { logger.Ex(newCtx, "ConvertDocxToDoc", "uploadDocToOss failed: %+v", err) status.Status = Failed _ = redis.DefaultDocRedis.SetDocProcessStatus(newCtx, taskUUID, status) return } // 更新状态 status.Status = Completed status.Result = oss status.UpdatedAt = time.Now().Unix() _ = redis.DefaultDocRedis.SetDocProcessStatus(newCtx, taskUUID, status) }() return taskUUID, nil
}
请问上面的代码正确还是下面的代码正确?
// ConvertDocxToDoc 将 DOCX 文档转换为 DOC 格式
func ConvertDocxToDoc(ctx context.Context, docUrl string) (string, error) {
taskUUID := uuid.New().String()
status := &DocProcessStatus{
Status: Processing,
UpdatedAt: time.Now().Unix(),
DocUrl: docUrl,
UUID: taskUUID,
}
text// 设置初始状态到Redis if err := redis.DefaultDocRedis.SetDocProcessStatus(ctx, taskUUID, status); err != nil { logger.Ex(ctx, "ConvertDocxToDoc", "SetDocProcessStatus failed: %+v", err) return "", err } // 异步处理文档转换 go func() { conversion, err := ProcessFileConversion(ctx, docUrl) if err != nil { logger.Ex(ctx, "ConvertDocxToDoc", "ProcessFileConversion failed: %+v", err) status.Status = Failed _ = redis.DefaultDocRedis.SetDocProcessStatus(ctx, taskUUID, status) return } oss, err := uploadDocToOss(ctx, conversion, taskUUID) if err != nil { logger.Ex(ctx, "ConvertDocxToDoc", "uploadDocToOss failed: %+v", err) status.Status = Failed _ = redis.DefaultDocRedis.SetDocProcessStatus(ctx, taskUUID, status) return } // 更新状态 status.Status = Completed status.Result = oss status.UpdatedAt = time.Now().Unix() _ = redis.DefaultDocRedis.SetDocProcessStatus(ctx, taskUUID, status) }() return taskUUID, nil
}
核心差异
区别 | 第 1 段代码 | 第 2 段代码 |
---|---|---|
goroutine 里的 context | context.Background() (脱离调用者) | 继续沿用入参 ctx (与调用者同生共死) |
受调用方超时 / 取消的影响 | 不会;任务一定执行到底 | 会;调用者超时或取消时会被连带取消 |
继承 Trace-ID / 日志字段等 value | 丢失(除非手动拷贝) | 保留 |
典型用途 | 后台异步任务,和 HTTP 请求生命周期解耦 | 希望请求中断就立刻停止任务 |
要看需求。
需求场景 | 推荐版本 | 原因 |
---|---|---|
转码必须完成,即使客户端断开 / 请求超时(常见于消息队列、回调式任务) | 第 1 段代码 | 使用 context.Background() 可避免父 ctx 在函数返回后被取消,从而导致转码半途而废。 |
转码只在请求仍然有效时才做(客户端取消就应立即停止,节省算力) | 第 2 段代码 | 沿用 ctx 可以自动感知取消信号,及时终止 goroutine。 |
不要直接用 context.Background()
如果需要脱离父 ctx
,可以派生一个新 context,同时保留重要的 value / trace 信息,并显式控制生命周期:
gogo func(parent context.Context) { // 继承 traceId 等 value ctx2 := context.WithValue(context.Background(), traceKey, parent.Value(traceKey)) // 可选:设置整体超时,防止任务跑飞 ctx2, cancel := context.WithTimeout(ctx2, 10*time.Minute) defer cancel() // 后续调用 … }(ctx)
显式监听 ctx.Done()
如果选择第二种写法,确保在 ProcessFileConversion
/ uploadDocToOss
内部或外围定期检查 ctx.Done()
,做到优雅退出。
并发安全
status
指针在 goroutine 内被更新,主函数返回后再无并发访问,一般不会造成数据竞争;若后续代码会并行读取,可加锁或使用原子操作。
ctx.Done()
。换句话说,两段代码都能编译通过,真正“正确”与否取决于你想让转码任务与请求的生命周期保持怎样的关系。