【Go语言-Day 26】深入解析error:从errors.New到errors.As的演进之路

Langchain系列文章目录

01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南
02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖
03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南
04-玩转 LangChain:从文档加载到高效问答系统构建的全程实战
05-玩转 LangChain:深度评估问答系统的三种高效方法(示例生成、手动评估与LLM辅助评估)
06-从 0 到 1 掌握 LangChain Agents:自定义工具 + LLM 打造智能工作流!
07-【深度解析】从GPT-1到GPT-4:ChatGPT背后的核心原理全揭秘
08-【万字长文】MCP深度解析:打通AI与世界的“USB-C”,模型上下文协议原理、实践与未来

Python系列文章目录

PyTorch系列文章目录

机器学习系列文章目录

深度学习系列文章目录

Java系列文章目录

JavaScript系列文章目录

Python系列文章目录

Go语言系列文章目录

01-【Go语言-Day 1】扬帆起航:从零到一,精通 Go 语言环境搭建与首个程序
02-【Go语言-Day 2】代码的基石:深入解析Go变量(var, :=)与常量(const, iota)
03-【Go语言-Day 3】从零掌握 Go 基本数据类型:string, runestrconv 的实战技巧
04-【Go语言-Day 4】掌握标准 I/O:fmt 包 Print, Scan, Printf 核心用法详解
05-【Go语言-Day 5】掌握Go的运算脉络:算术、逻辑到位的全方位指南
06-【Go语言-Day 6】掌控代码流:if-else 条件判断的四种核心用法
07-【Go语言-Day 7】循环控制全解析:从 for 基础到 for-range 遍历与高级控制
08-【Go语言-Day 8】告别冗长if-else:深入解析 switch-case 的优雅之道
09-【Go语言-Day 9】指针基础:深入理解内存地址与值传递
10-【Go语言-Day 10】深入指针应用:解锁函数“引用传递”与内存分配的秘密
11-【Go语言-Day 11】深入浅出Go语言数组(Array):从基础到核心特性全解析
12-【Go语言-Day 12】解密动态数组:深入理解 Go 切片 (Slice) 的创建与核心原理
13-【Go语言-Day 13】切片操作终极指南:append、copy与内存陷阱解析
14-【Go语言-Day 14】深入解析 map:创建、增删改查与“键是否存在”的奥秘
15-【Go语言-Day 15】玩转 Go Map:从 for range 遍历到 delete 删除的终极指南
16-【Go语言-Day 16】从零掌握 Go 函数:参数、多返回值与命名返回值的妙用
17-【Go语言-Day 17】函数进阶三部曲:变参、匿名函数与闭包深度解析
18-【Go语言-Day 18】从入门到精通:defer、return 与 panic 的执行顺序全解析
19-【Go语言-Day 19】深入理解Go自定义类型:Type、Struct、嵌套与构造函数实战
20-【Go语言-Day 20】从理论到实践:Go基础知识点回顾与综合编程挑战
21-【Go语言-Day 21】从值到指针:一文搞懂 Go 方法 (Method) 的核心奥秘
22-【Go语言-Day 22】解耦与多态的基石:深入理解 Go 接口 (Interface) 的核心概念
23-【Go语言-Day 23】接口的进阶之道:空接口、类型断言与 Type Switch 详解
24-【Go语言-Day 24】从混乱到有序:Go 语言包 (Package) 管理实战指南
25-【Go语言-Day 25】从go.mod到go.sum:一文彻底搞懂Go Modules依赖管理
26-【Go语言-Day 26】深入解析error:从errors.New到errors.As的演进之路



摘要

在任何健壮的程序中,错误处理都扮演着至关重要的角色。它像一位不知疲倦的守护者,确保程序在遇到意外情况时能够优雅地应对,而不是突然崩溃。Go 语言在设计之初就摒弃了其他语言中常见的 try-catch 异常机制,而是采用了一种更为明确、简洁的错误处理哲学:错误即是值 (Errors are values)。这种设计哲学鼓励开发者正视并显式地处理每一个可能出错的环节。本文将从 Go 语言错误处理的核心理念出发,系统性地介绍 error 接口、标准错误创建方法、自 Go 1.13 引入的现代错误处理利器——错误包装(Wrapping)以及 errors.Iserrors.As 的用法,并深入探讨如何通过自定义错误类型来传递更丰富的上下文信息,最终总结出一套清晰、实用的 Go 错误处理最佳实践。

一、Go 语言的错误处理哲学

在深入了解具体技术之前,我们必须首先理解 Go 语言在错误处理上的核心思想,这有助于我们写出更地道、更可靠的 Go 代码。

1.1 错误即是值 (Errors are Values)

在 Go 中,错误并非一种特殊的语言结构,而是一种实现了 error 接口的普通值。函数或方法在执行过程中如果可能发生错误,通常会将一个 error 类型作为其多个返回值中的最后一个。

这种设计的核心优势在于其显式性。调用者在接收函数返回值时,必须显式地处理 error 值,即使选择忽略它(通过 _),也是一个主动的决定。这从根本上避免了因忘记捕获异常而导致的程序崩溃。

// f, err := os.Open("filename.ext")
// if err != nil {
//     log.Fatal(err)
// }
// // 在这里,我们确信 f 是一个有效的文件句柄

1.2 与异常 (Exception) 的对比

习惯了 Java、Python 等语言中 try-catch 异常机制的开发者初次接触 Go 时可能会感到不适。下面是两者在理念上的主要区别:

特性Go 错误处理 (error)传统异常处理 (try-catch)
本质普通的值,在常规控制流中传递一种特殊的控制流机制
处理方式通过 if err != nil 显式检查通过 try-catch 块捕获
控制流线性、可预测可能发生非线性的控制流跳转
理念鼓励正视和处理预期内的错误通常用于处理意外或“异常”的情况

Go 的方式强制开发者在错误发生的地方立即处理,使得代码逻辑更加清晰和本地化。而异常机制则可能将错误处理逻辑与业务逻辑分离得很远,增加了理解和维护的难度。

二、error 接口:万物皆可为错

Go 错误处理的基石是一个极其简单的接口——error

2.1 error 接口的定义

在 Go 的内置源码中,error 接口的定义如下:

// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
    Error() string
}

这个接口只有一个 Error() 方法,该方法返回一个字符串,用于描述错误信息。任何实现了该接口的类型,都可以被当作一个 error 类型的值来使用。nil 值的 error 表示没有错误发生。

2.2 标准的错误处理范式

Go 语言中最常见、最标准的代码模式就是检查函数返回的 error 值。

package main

import (
	"fmt"
	"strconv"
)

func main() {
	// 尝试将一个非数字字符串转换为整数
	i, err := strconv.Atoi("42a")
	if err != nil {
		// 如果 err 不是 nil,说明发生了错误
		fmt.Printf("An error occurred: %v\n", err)
		return // 终止程序或进行其他错误处理
	}
	// 如果 err 是 nil,说明转换成功
	fmt.Printf("Conversion successful, value is: %d\n", i)
}

这种 if err != nil 的模式在 Go 代码中无处不在,是每个 Go 开发者都必须熟练掌握的核心范式。

三、创建错误:从简单到丰富

Go 标准库提供了两种基本的方式来创建错误。

3.1 使用 errors.New 创建静态错误

当你需要一个简单的、固定的错误信息时,errors 包的 New 函数是最佳选择。它接受一个字符串参数,并返回一个包含该字符串的 error 值。

import "errors"

func checkAge(age int) error {
	if age < 0 {
		// 创建一个新的、简单的错误
		return errors.New("age cannot be negative")
	}
	return nil
}

errors.New 创建的错误适用于那些错误信息永远不会改变的场景。

3.2 使用 fmt.Errorf 创建动态格式化错误

当错误信息需要包含动态的上下文数据(如变量值)时,应使用 fmt 包的 Errorf 函数。它的用法与 fmt.Printf 类似,但它返回的是一个 error 值。

import "fmt"

func openFile(fileName string) error {
	// 模拟文件不存在的错误
	if fileName == "not_exist.txt" {
		// 创建一个包含动态文件名信息的错误
		return fmt.Errorf("file not found: %s", fileName)
	}
	return nil
}

四、错误包装 (Wrapping):构建有迹可循的错误链

在复杂的函数调用链中,底层函数产生的错误被上层函数捕获后,上层函数通常希望在不丢失原始错误信息的情况下,添加更多的上下文信息。这就是错误包装的用武之地。

4.1 为何需要错误包装?

想象一个场景:main -> readFile -> openFile。如果 openFile 失败,它返回一个错误。readFile 捕获这个错误后,希望添加 “failed to read file” 这样的上下文,然后再返回给 main

在 Go 1.13 之前,通常的做法是:

// Go 1.13 之前的做法
err := openFile(path)
if err != nil {
    return fmt.Errorf("failed to read file: %v", err)
}

这里的 %v 会将原始 error 的字符串信息拼接进来,但原始的 error 对象本身丢失了。调用者无法得知错误的根源具体是哪种类型的错误。

4.2 使用 %w 动词进行包装

Go 1.13 在 fmt.Errorf 中引入了新的格式化动词 %w,专门用于包装错误。

package main

import (
	"errors"
	"fmt"
	"os"
)

// dataAccessLayer 模拟数据访问层操作
func dataAccessLayer(path string) error {
	// 模拟打开文件失败
	err := os.ErrNotExist // 这是一个预定义的错误变量
	if err != nil {
		// 使用 %w 包装底层错误
		return fmt.Errorf("data access error: %w", err)
	}
	return nil
}

// businessLogicLayer 模拟业务逻辑层操作
func businessLogicLayer(path string) error {
	err := dataAccessLayer(path)
	if err != nil {
		// 再次包装,添加业务层上下文
		return fmt.Errorf("business logic failed: %w", err)
	}
	return nil
}

func main() {
	err := businessLogicLayer("/path/to/nonexistent/file")
	if err != nil {
		fmt.Println("Error Chain:")
		fmt.Printf("1. Top-level error: %v\n", err)

		// 使用 errors.Unwrap 逐层解开错误
		unwrappedErr := errors.Unwrap(err)
		if unwrappedErr != nil {
			fmt.Printf("2. Unwrapped once: %v\n", unwrappedErr)

			unwrappedErr = errors.Unwrap(unwrappedErr)
			if unwrappedErr != nil {
				fmt.Printf("3. Original root error: %v\n", unwrappedErr)
			}
		}
	}
}

输出:

Error Chain:
1. Top-level error: business logic failed: data access error: file does not exist
2. Unwrapped once: data access error: file does not exist
3. Original root error: file does not exist

使用 %w 后,原始错误被“嵌入”到了新的错误中,形成了一个错误链。我们可以通过 errors.Unwrap 函数逐层解开被包装的错误。

4.3 可视化错误链

我们可以用一个流程图来直观地理解这个错误链:

错误链 (Error Chain)
返回错误
包装错误后返回
wraps
wraps
收到 최종错误
data access error
business logic failed
os.ErrNotExist
main 调用 businessLogicLayer
businessLogicLayer
dataAccessLayer
处理最终错误

五、现代错误处理:errors.Iserrors.As

有了错误链之后,我们需要更强大的工具来检查链中的错误,而不是手动 Unwrap。Go 1.13 为此提供了 errors.Iserrors.As

5.1 errors.Is:判断错误是否为特定实例

errors.Is 函数会沿着错误链进行遍历,判断链中是否有任何一个错误与目标错误(target error)同一个实例。

5.1.1 基本用法

func Is(err, target error) bool

它会检查 err 本身或其包装的任何底层错误是否与 target 相等

5.1.2 示例:检查 io.EOF

一个非常常见的场景是,在读取文件或网络流时,判断错误是否是预期的“读到末尾”(io.EOF)。

package main

import (
	"errors"
	"fmt"
	"io"
)

// readData 模拟一个可能读到文件末尾的操作
func readData() error {
	// 模拟底层IO操作返回了EOF错误
	err := io.EOF
	// 上层包装了这个错误
	return fmt.Errorf("failed to read data: %w", err)
}

func main() {
	err := readData()
	if err != nil {
		// 使用 errors.Is 来检查错误链中是否包含 io.EOF
		if errors.Is(err, io.EOF) {
			fmt.Println("Reached the end of the file, this is an expected 'error'.")
		} else {
			fmt.Printf("An unexpected error occurred: %v\n", err)
		}
	}
}

即使 err 本身是一个包装后的错误,errors.Is 也能准确地“看穿”它,并找到链中的 io.EOF

5.2 errors.As:检查错误链中的特定类型

errors.Is 用于比较错误实例,而 errors.As 则用于检查错误链中是否有某个错误的类型是我们关心的,并将其“取”出来。

5.2.1 基本用法与动机

func As(err error, target interface{}) bool

target 必须是一个指向接口类型或实现了 error 接口的具体类型的指针。如果 err 链中找到了一个可以赋值给 target 的错误,errors.As 会执行赋值并返回 true

这在处理自定义错误类型时尤其有用,因为我们往往需要从错误中提取额外的信息。

5.2.2 自定义错误类型与 errors.As 的结合

我们将在下一节详细介绍自定义错误,这里先看一个预告性的例子,假设我们有一个 MyError 类型:

// 假设有如下自定义错误
type MyError struct {
    Code int
    Msg  string
}

func (e *MyError) Error() string {
    return fmt.Sprintf("code %d: %s", e.Code, e.Msg)
}

// 某个函数返回一个包装后的自定义错误
func doSomething() error {
    baseErr := &MyError{Code: 500, Msg: "database connection failed"}
    return fmt.Errorf("operation failed: %w", baseErr)
}

func main() {
    err := doSomething()
    if err != nil {
        var myErr *MyError
        // 使用 errors.As 检查链中是否有 MyError 类型的错误,并将其赋值给 myErr
        if errors.As(err, &myErr) {
            fmt.Printf("Caught a specific error type!\n")
            fmt.Printf("Error Code: %d\n", myErr.Code)
            fmt.Printf("Error Message: %s\n", myErr.Msg)
        } else {
            fmt.Printf("An unknown error occurred: %v\n", err)
        }
    }
}

errors.As 让我们能够优雅地处理异构的错误链,精确地捕获并操作我们关心的特定错误类型。

六、自定义错误类型:传递更丰富的上下文

有时,一个简单的错误字符串不足以描述全部情况。我们可能需要传递错误码、时间戳或其他元数据。这时就需要自定义错误类型。

6.1 为何需要自定义错误?

  • 结构化数据:可以携带除字符串外的更多信息,如错误码、操作名等。
  • 程序化处理:调用方可以根据错误的具体类型或其内部字段来执行不同的逻辑。
  • 更清晰的API:定义良好的错误类型本身就是一种文档,清晰地告诉调用者可能发生哪些具体的错误。

6.2 实现一个自定义错误类型

实现自定义错误类型很简单:创建一个结构体,并为它实现 Error() string 方法即可。

package main

import (
	"errors"
	"fmt"
	"time"
)

// OperationError 定义了一个操作相关的错误
type OperationError struct {
	Op      string    // 执行的操作,如 "read", "write"
	Path    string    // 操作的文件路径
	Err     error     // 包装的底层错误
	Timestamp time.Time // 错误发生的时间
}

// Error 方法让 OperationError 实现了 error 接口
func (e *OperationError) Error() string {
	return fmt.Sprintf("operation '%s' on path '%s' failed at %v: %v",
		e.Op, e.Path, e.Timestamp.Format(time.RFC3339), e.Err)
}

// Unwrap 方法让自定义错误支持错误链(Go 1.13+)
func (e *OperationError) Unwrap() error {
    return e.Err
}

关键点:实现 Unwrap() error 方法至关重要,它告诉 errors.Iserrors.As 如何在你的自定义错误类型上继续错误链的遍历。

6.3 如何在调用方处理自定义错误

结合 errors.As,我们可以非常方便地处理这种自定义错误。

// 模拟一个会产生 OperationError 的函数
func copyFile(src, dst string) error {
	// 模拟源文件不存在
	underlyingErr := os.ErrNotExist 
	return &OperationError{
		Op:      "copy",
		Path:    src,
		Err:     underlyingErr,
		Timestamp: time.Now(),
	}
}

func main() {
	err := copyFile("/nonexistent/source", "/path/to/dest")

	if err != nil {
		var opErr *OperationError
		// 检查错误链中是否有 OperationError
		if errors.As(err, &opErr) {
			fmt.Println("--- Caught a specific OperationError ---")
			fmt.Printf("Operation: %s\n", opErr.Op)
			fmt.Printf("Path: %s\n", opErr.Path)
			fmt.Printf("Timestamp: %v\n", opErr.Timestamp)
			
			// 我们还可以进一步检查被包装的底层错误
			if errors.Is(opErr.Err, os.ErrNotExist) {
				fmt.Println("Root cause: The source file does not exist.")
			}
		} else {
			fmt.Printf("Caught an unexpected error: %v\n", err)
		}
	}
}

七、错误处理最佳实践与常见误区

7.1 错误应该被处理,而非忽略

最糟糕的做法就是使用空白标识符 _ 忽略错误。除非你百分之百确定函数不会返回错误,否则永远不要这么做。

// 错误示范 👎
value, _ := strconv.Atoi("not a number") // 错误被丢弃,程序可能以意想不到的方式继续执行

7.2 向上层传递时添加上下文

当捕获一个来自下层调用的错误并将其返回给上层时,应使用 fmt.Errorf 配合 %w 添加当前层的上下文信息。这使得调试变得极其容易。

// 推荐做法 👍
data, err := ioutil.ReadAll(r)
if err != nil {
    return nil, fmt.Errorf("failed to read all data from reader: %w", err)
}

7.3 只处理一次错误

一个常见的错误是在一个地方既打印日志又将错误返回。这会导致同一个错误被多次记录,造成日志混乱。正确的原则是:要么处理它(比如重试、提供默认值、或者终止程序),要么包装它并返回给上层

// 错误示范 👎
func process() error {
    err := doSomething()
    if err != nil {
        log.Printf("error doing something: %v", err) // 记录日志
        return err // 又返回错误,上层可能再次记录
    }
    return nil
}

7.4 panic 不是常规的错误处理工具

panic 会立即停止当前函数的执行,并开始沿调用栈向上展开。除非遇到无法恢复的程序性错误(如数组越界、空指针引用)或在程序的 main 函数或顶层 goroutine 中确实希望程序崩溃,否则不应使用 panic 来报告可预期的错误,如文件未找到、网络请求失败等。我们将在下一篇文章中详细探讨 panicrecover

八、总结

本文深入探讨了 Go 语言从基本到现代的错误处理机制。掌握这些知识对于编写高质量、高可靠性的 Go 程序至关重要。

  1. 核心哲学:Go 将错误视为普通值,通过函数多返回值的形式进行传递,强制开发者显式处理,保证了代码的健壮性和清晰性。
  2. 基础工具error 是一个仅包含 Error() string 方法的简单接口。标准库通过 errors.New 创建静态错误,通过 fmt.Errorf 创建动态格式化错误。
  3. 错误包装:自 Go 1.13 起,fmt.Errorf 中的 %w 动词允许我们将一个错误“包装”在另一个错误中,形成一条可追溯的错误链,保留了完整的错误上下文。
  4. 现代检查机制errors.Is() 用于判断错误链中是否存在特定的错误实例(如 io.EOF);errors.As() 则用于检查错误链中是否存在特定类型的错误,并能将其提取出来以访问其内部数据。
  5. 自定义错误:通过创建实现 error 接口的结构体,我们可以定义自己的错误类型,携带丰富的结构化信息。结合 errors.As,可以实现非常灵活和强大的错误处理逻辑。
  6. 最佳实践:关键在于“显式处理、添加上下文、只处理一次”。错误应该被认真对待,通过包装向上传递,直到有足够上下文来恰当处理它的地方。panic 应用于处理真正的、不可恢复的异常情况,而非业务逻辑中的预期错误。

import requests import json from datetime import datetime from 获取div中所有p标签 import get_order_manage_detail from 手机号 import get_passenger_mobile def get_cancelled_orders(driver_name=None, cookie_path='cookie1.txt'): """ 查询指定司机的取消订单 参数: driver_name: str - 司机姓名(可选),不传则查询所有司机 cookie_path: str - cookie文件路径,默认为'cookie.txt' 返回: list - 格式化后的取消订单信息列表 """ def get_cookie_value(cookies, cookie_name): """从cookie列表中提取指定cookie的值""" for cookie in cookies: if cookie['name'] == cookie_name: return cookie['value'] return None def parse_order_detail(order_detail_data): """ 解析订单详情数据,提取关键信息 参数: order_detail_data: dict - 订单详情数据 返回: str - 格式化后的订单信息 """ try: if 'error' in order_detail_data: return f"获取订单详情失败: {order_detail_data['error']}" if order_detail_data.get('code') != 1: return f"API返回错误: {order_detail_data.get('msg', '未知错误')}" data = order_detail_data.get('data', {}) if not data: return "订单详情数据为空" # 提取基本信息 order_base = data.get('orderBaseInfoVO', {}) order_price = data.get('orderPriceVO', {}) states = data.get('states', []) # 司机信息 driver_name = "未知司机" plate_no = "无车牌" driver_mobile = "未知" if states: latest_state = states[-1] # 获取最新状态 driver_name = latest_state.get('driverName', '未知司机') plate_no = latest_state.get('plateNo', '无车牌').replace('*', '') driver_mobile = latest_state.get('driverMobile', '未知') # 乘客信息 passenger_mobile = order_base.get('passengerMobile', '未知') agent_passenger_mobile = order_base.get('agentPassengerMobile', '未知') # 时间信息 create_time = order_base.get('passengerCreateDate', 0) if create_time: from datetime import datetime create_datetime = datetime.fromtimestamp(create_time / 1000) date_str = create_datetime.strftime('%Y-%m-%d') time_str = create_datetime.strftime('%H:%M:%S') else: date_str = "未知日期" time_str = "未知时间" # 车型信息 car_type = order_base.get('orderCategoryTypeName', '未知车型') standard_type = order_base.get('standardUseCarTypeName', '') if standard_type: car_type = f"{car_type}-{standard_type}" # 地址信息 start_point = order_base.get('orderStartPoint', {}) end_point = order_base.get('orderEndPoint', {}) start_address = start_point.get('address', '未知起点').replace('*******', '') end_address = end_point.get('address', '未知终点').replace('*******', '') # 价格信息 trip_price = order_price.get('tripPrice', '0.00') estimate_price = order_price.get('estimatePrice', 0) # 里程和时长 estimate_mileage = order_base.get('estimateMileage', 0) / 1000 # 转换为公里 estimate_duration = order_base.get('estimateDuration', 0) # 渠道信息 channel = order_base.get('standardSourceName', '未知渠道') # 获取orderId order_id = order_base.get('orderId') or order_base.get('etravelOrderId') # 获取driverId driver_id = order_base.get('driverId') # 如果两个值都存在,添加到结果列表 if order_id and driver_id: p = get_passenger_mobile("cookie1.txt", driver_id, order_id) # return f"""司机:{driver}({plate}) s = p # 格式化输出 result = f"""司机:{driver_name}({plate_no}) 暗号:{s} 用车时间:{date_str} {time_str} 用车车型:{car_type} 起点:{start_address} 终点:{end_address} 特惠一口价:{trip_price}元 预估价格:{estimate_price}元 预估里程:{estimate_mileage}km 预估时长:{estimate_duration}分钟 该笔订单来自{channel}""" return result except Exception as e: return f"解析订单详情时发生错误: {str(e)}" # def format_order(order,cookie_name): # # 司机信息 # driver = order.get('userDriverName') or order.get('driverName') or '未知司机' # plate = (order.get('plateNo') or '无车牌').replace('*', '') # # 联系方式 # caller = order.get('agentPassengerMobile') or order.get('passengerMobile') or '未知' # passenger = order.get('passengerMobile') or '未知' # # 时间处理 # create_time = order.get('passCreateDateStr', '').split(' ') # date_part = create_time[0] if create_time else '未知日期' # time_part = create_time[1] if len(create_time) > 1 else '未知时间' # # 车型 # car_type = order.get('orderCategoryTypeName') or '未知车型' # standard_type = order.get('standardUseCarTypeName') or '' # if standard_type: # car_type = f"{car_type}-{standard_type}" # # 地址 # start = (order.get('startAddress') or '未知起点').replace('*******', '') # end = (order.get('endAddress') or '未知终点').replace('*******', '') # # 价格信息 - 确保数值不为None # price = order.get('tripPrice') or 0.0 # # estimate_price = order.get('estimatePrice') or 0.0 # # mileage = order.get('estimateMileage') or order.get('factMileage') or 0.0 # # duration = order.get('estimateDuration') or order.get('factDuration') or 0.0 # # 订单状态 # status = order.get('orderStateName') or '未知状态' # # 渠道 # channel = order.get('standardSourceName') or '未知渠道' # if channel== "高德渠道": # # 获取orderId # order_id = order.get('orderId') or order.get('etravelOrderId') # # 获取driverId # driver_id = order.get('driverId') # # 如果两个值都存在,添加到结果列表 # if order_id and driver_id: # passenger = get_passenger_mobile(cookie_name, driver_id, order_id) # 手机号:{passenger} # 用车时间:{date_part} # {time_part} # 用车车型:{car_type} # 起点:{start} # 终点:{end} # 特惠一口价:{price:.2f}元 # 订单状态:{status} # 渠道:{channel}""" try: # 读取cookie文件 with open(cookie_path, 'r', encoding='utf-8') as f: cookies = json.load(f) # 准备请求 url = "https://admin.yueyuechuxing.cn/bos/admin/v2/order/queryDetail" now = int(datetime.now().timestamp() * 1000) day_ago = now - 1 * 120000000 # 3天前,扩大时间范围 headers = { "Content-Type": "application/json", "Content-Encoding": "gzip", "Referer": "https://admin.yueyuechuxing.cn/order/newOrder/newList", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36", "_admin_tk": get_cookie_value(cookies, 'stk') or '', "_admin_ts": str(int(datetime.now().timestamp())), "_admin_eid": get_cookie_value(cookies, '_assets_tenant_id') or '', "_admin_resource_key": "newOrderList", "_admin_current_page": "/order/newOrder/newList", "_admin_session_eid": get_cookie_value(cookies, '_assets_tenant_id') or '' } data = { "driverName": driver_name , "pageNum": 1, "pageSize": 1, "passCreateEndDate": now, "passCreateStartDate": day_ago, "showEtravelCancelOrderFlag": 0, "specialOrderFlag": 0 } # 发送请求 response = requests.post( url, cookies={c['name']: c['value'] for c in cookies}, headers=headers, json=data, timeout=15 ) if response.status_code != 200: return [f"请求失败,状态码: {response.status_code}"] result = response.json() if result['code'] != 1: return [result.get('msg', '请求失败')] if not result.get('data', {}).get('items'): return [f"未查到[{driver_name}]进行中的订单"] # 获取第一个订单的ID items = result.get('data', {}).get('items', []) if not items: return [f"未查到[{driver_name}]进行中的订单"] order_id = items[0].get('orderId') or items[0].get('etravelOrderId') if order_id: order_detail = get_order_manage_detail(cookie_path, order_id) if isinstance(order_detail, dict) and 'error' not in order_detail and items[0].get("orderStateName")!="付款完成": return [parse_order_detail(order_detail)] else: return [f"获取订单详情失败: {order_detail}"] return [f"未查到[{driver_name}]进行中的订单"] except Exception as e: return [f"发生错误: {str(e)}"] # 使用示例 if __name__ == "__main__": # 查询特定司机 import time # 记录开始时间 start_time = time.time() orders = get_cancelled_orders(driver_name="吴勇") # 这里是你的代码 time.sleep(1) # 模拟耗时操作 # 计算并打印耗时 elapsed_time = time.time() - start_time print(f"代码运行耗时: {elapsed_time:.4f} 秒") if not orders: print("未找到取消订单") else: for i, order in enumerate(orders, 1): # print(f"\n取消订单 {i}:") print(order) print("-" * 60) json解析出错
08-02
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吴师兄大模型

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值