订单总丢?PHP队列的正确打开方式


写在前面

上周有个朋友深夜给我打电话,声音里全是焦虑:"我们的订单系统又出事了------用户下单后支付成功,但订单状态没更新,客服电话被打爆了!"

我一问,原来他们所有订单处理逻辑都写在接口的同步流程里:高并发下一超时,订单就丢了。

这不是个例。我见过太多 PHP 项目在业务增长期被"同步处理"这把钝刀割得遍体鳞伤。今天这篇文章,不讲概念堆砌,只讲为什么你的订单会丢、怎么用队列真正解决,以及那些年我踩过的坑


一、为什么你的订单总丢?------ 同步处理的三大原罪

1.1 超时:接口等不起,业务伤不起

php 复制代码
// 很多项目的订单处理是这样的
public function createOrder($orderData)
{
    // 1. 插入订单
    $orderId = $this->orderModel->create($orderData);

    // 2. 库存扣减
    $this->inventoryService->deduct($orderId);

    // 3. 发送通知
    $this->notifyService->send($orderId);

    // 4. 记录日志
    $this->logService->record($orderId);

    return $orderId;
}

这段代码看起来没问题,但如果第 3 步"发送通知"是个 HTTP 调用且耗时 3 秒,请问:

  • 用户的感受是什么?接口等了 3 秒才返回。
  • 如果第 4 步又花了 2 秒呢?5 秒,用户早就关页面了。
  • 如果_notifyService_ 在第 3 步抛了异常呢?整个事务回滚,订单都没了。

更极端一点:用户支付成功 → 回调接口触发 → 库存服务超时 → 订单回滚 → 用户付了钱但没订单。投诉、客诉、退款,一个不少。

1.2 并发:并发一来,Race Condition 伺候

php 复制代码
// 库存扣减的原始写法
$stock = $this->redis->get("goods:{$goodsId}:stock");
if ($stock > 0) {
    $this->redis->decr("goods:{$goodsId}:stock"); // 库存超卖!
    $this->orderModel->create($orderData);
}

在高并发下,get → 判断 → decr 这三步不是原子的,两个请求同时读到 stock = 1,都通过判断,都扣了,结果库存变成 -1,超卖两个。

你以为这是库存专属的问题?支付回调、优惠券发放、积分扣除......所有涉及"读-改-写"的场景,并发都是定时炸弹

1.3 崩溃:进程挂了,任务也没了

PHP-FPM 工作模式:请求来 → 启动进程 → 处理 → 返回 → 进程销毁

如果处理到一半,PHP-FPM 进程被 kill 了(OOM Kill、超时强制终止),正在执行的任务直接蒸發。用户看到接口返回了 200,但后端业务根本没执行完。

典型的"表面成功,实际失败"。


二、队列核心概念:三分钟搞懂

在说实现方案之前,先把几个核心概念捋清楚,这些概念是所有队列方案的基石。

2.1 生产者(Producer):任务的制造者

php 复制代码
// 生产者:接收用户请求,往队列里扔一个任务
$queue->push("order_process", [
    "order_id" => 10086,
    "user_id" => 9527,
    "amount" => 299.00,
    "created_at" => time()
]);

生产者的职责就一个:把任务安全地送进队列,然后快速返回。它不应该关心任务什么时候被处理、谁来处理。

2.2 消费者(Consumer):任务的执行者

php 复制代码
// 消费者:从队列里取任务,执行它
while (true) {
    $job = $queue->pop("order_process"); // 阻塞式获取
    $this->processOrder($job["order_id"]);
    $queue->ack($job["id"]); // 确认处理完成
}

消费者的职责:从队列取任务、执行任务、确认完成。如果执行失败,要能重试或者放到死信队列。

2.3 消息持久化:不让消息死于内存

这是最容易忽略的一点。

如果没有持久化:队列服务重启(比如 Redis 宕机重启),队列里的消息全部丢失------相当于你往 ATM 里存了钱,但银行说"服务器重启了,存款记录没了"。

正确的做法

  • Redis :开启 AOF 持久化(appendfsync everysec 以上)
  • RabbitMQ:消息落盘 + 队列持久化
  • 数据库:消息存储在表里(最原始但最保险)

三、PHP 队列实现方案对比

3.1 方案一:文件队列(低配版,玩具级)

把任务写到文件里,消费者定时扫描文件。

php 复制代码
// 生产者:写文件
file_put_contents("/tmp/queue.txt", json_encode($job) . "\n", FILE_APPEND);

// 消费者:读文件
$lines = file("/tmp/queue.txt");
foreach ($lines as $line) {
    $job = json_decode($line, true);
    $this->process($job);
    // 处理完删除行?不好意思,文件不支持原子删除......
}

优点:零依赖,代码简单。

致命缺点

  • 无并发支持(文件锁性能差)
  • 无持久化保证(服务器重启文件可能损坏)
  • 无重试机制
  • 无监控
  • 生产环境用这个,迟早出事。

3.2 方案二:Redis 队列(主流,简单易用)

Redis 的 List 结构天生就是队列:LPUSH 入队,BRPOP 出队。配合 RPOPLPUSH(或 BRPOPLPUSH)还能实现安全的队列转移(从原队列取出的同时备份到处理队列,防止消息丢失)。

php 复制代码
// Redis 队列核心操作
// 入队
$redis->lpush("queue:order", json_encode($job));

// 出队(阻塞,最常用)
$job = $redis->brpop("queue:order", 10); // 阻塞10秒

优点:部署简单(大多数 PHP 项目本来就用 Redis),性能极高,持久化可配置,支持原子操作。

缺点:不支持多消费者共享同一个队列(需要自己做广播),不支持消息确认(需手动实现),cluster 模式下部分命令有坑。

适用场景:中小型项目,单个应用内的异步任务处理。

3.3 方案三:RabbitMQ / Redis Stream(企业级)

特性 Redis 队列 RabbitMQ Redis Stream
消息持久化 AOF/RDB 磁盘 AOF
消息确认 手动实现 ACK 原生支持 XACK 原生支持
多消费者 需 BRPOPLPUSH Exchange 路由原生支持 Consumer Group 原生支持
延迟消息 需 Sorted Set 延迟插件 延迟消息
集群 Redis Cluster 镜像集群 Redis Cluster
运维成本
适用规模 中小 中大

RabbitMQ:功能最全,支持各种路由规则、死信队列、优先级队列。但配置复杂,运维成本高。

Redis Stream (推荐):Redis 5.0 引入,弥补了 List 的不足,支持 Consumer Group(多消费者分片消费)、消息持久化、ACK 确认。我目前项目中的首选。


四、Redis 队列实战代码

下面给出一个基于原生 PHP + Redis 的完整队列实现,包含生产者和消费者两部分。建议收藏,直接抄。

4.1 生产者(投递任务)

php 复制代码
<?php
/**
 * 订单队列 - 生产者
 * 负责将订单任务投递到 Redis 队列
 */

class OrderProducer
{
    private Redis $redis;
    private string $queueName = "queue:order";

    public function __construct()
    {
        $this->redis = new Redis();
        $this->redis->connect("127.0.0.1", 6379);
        $this->redis->auth("your_password"); // 生产环境务必设置密码
    }

    /**
     * 投递订单处理任务
     * @param array $orderData 订单数据
     * @return bool 是否投递成功
     */
    public function dispatch(array $orderData): bool
    {
        $job = [
            "id" => uniqid("order_", true),       // 唯一任务ID,用于去重
            "data" => $orderData,
            "attempts" => 0,                       // 初始重试次数
            "created_at" => date("Y-m-d H:i:s"),
        ];

        try {
            // LPUSH 入队,JSON 序列化存储
            $result = $this->redis->lpush(
                $this->queueName,
                json_encode($job, JSON_UNESCAPED_UNICODE)
            );

            if ($result === false) {
                // 记录投递失败日志
                $this->log("ERROR", "订单投递失败: " . json_encode($orderData, JSON_UNESCAPED_UNICODE));
                return false;
            }

            $this->log("INFO", "订单已投递队列: {$job['id']}");
            return true;

        } catch (RedisException $e) {
            $this->log("ERROR", "Redis异常: " . $e->getMessage());
            return false;
        }
    }

    /**
     * 投递延迟订单处理任务(使用 Redis Sorted Set 实现延迟队列)
     * @param array $orderData 订单数据
     * @param int $delaySeconds 延迟秒数
     * @return bool
     */
    public function dispatchDelay(array $orderData, int $delaySeconds = 30): bool
    {
        $job = [
            "id" => uniqid("order_", true), "data" => $orderData,
            "attempts" => 0, "created_at" => date("Y-m-d H:i:s"),
        ];
        $delayQueueName = "queue:order:delay";
        $executeTime = time() + $delaySeconds; // 延迟到指定时间才执行

        try {
            // ZADD 的正确调用方式:zadd(key, score, member)
            $result = $this->redis->zadd(
                $delayQueueName, $executeTime,
                json_encode($job, JSON_UNESCAPED_UNICODE)
            );

            if ($result === false) {
                $this->log("ERROR", "延迟订单投递失败: " . json_encode($orderData, JSON_UNESCAPED_UNICODE));
                return false;
            }

            $this->log("INFO", "延迟订单已投递: {$job['id']}, 将在 {$delaySeconds} 秒后执行");
            return true;
        } catch (RedisException $e) {
            $this->log("ERROR", "延迟投递失败: " . $e->getMessage());
            return false;
        }
    }

    private function log(string $level, string $message): void
    {
        $timestamp = date("Y-m-d H:i:s");
        echo "[{$timestamp}] [{$level}] {$message}" . PHP_EOL;
    }
}

// ============ 使用示例 ============
$producer = new OrderProducer();

// 模拟用户下单接口中调用
$orderData = [
    "order_id" => 10086,
    "user_id" => 9527,
    "goods" => [
        ["id" => 1, "name" => "iPhone 16", "num" => 1, "price" => 8999.00],
        ["id" => 2, "name" => "AirPods Pro", "num" => 1, "price" => 1999.00],
    ],
    "total_amount" => 10998.00,
    "pay_method" => "wechat",
];

$producer->dispatch($orderData);
// 立即返回,用户无需等待库存、通知、日志等操作

4.2 消费者(处理任务)

php 复制代码
<?php
/**
 * 订单队列 - 消费者
 * 持续从 Redis 队列获取任务并执行
 * 建议使用 nohup 或 supervisor 在后台运行
 */

class OrderConsumer
{
    private Redis $redis;
    private string $queueName = "queue:order";
    private string $processingQueueName = "queue:order:processing"; // 处理中队列(安全备份)
    private int $maxAttempts = 3; // 最大重试次数
    private int $popTimeout = 10;  // BRPOPLPUSH 阻塞时间(秒)

    public function __construct()
    {
        $this->redis = new Redis();
        $this->redis->connect("127.0.0.1", 6379);
        $this->redis->auth("your_password");
    }

    /**
     * 启动消费者(阻塞循环)
     */
    public function run(): void
    {
        $this->log("INFO", "订单消费者已启动,监听队列: {$this->queueName}");

        while (true) {
            try {
                // BRPOPLPUSH:从 order 队列取任务,同时放到 processing 队列做备份
                // 这样即使进程崩溃,任务也不会丢失(从 processing 队列恢复)
                $jobJson = $this->redis->brpoplpush(
                    $this->queueName,
                    $this->processingQueueName,
                    $this->popTimeout
                );

                if ($jobJson === null) {
                    // 超时,继续等待
                    continue;
                }

                $job = json_decode($jobJson, true);

                if ($job === null) {
                    $this->log("WARNING", "任务JSON解析失败: " . $jobJson);
                    $this->redis->lrem($this->processingQueueName, 1, $jobJson);
                    continue;
                }

                $this->processJob($job);

            } catch (RedisException $e) {
                $this->log("ERROR", "Redis异常: " . $e->getMessage());
                sleep(5); // 异常时短暂休眠,避免疯狂重试
            } catch (Exception $e) {
                $this->log("ERROR", "处理异常: " . $e->getMessage());
                sleep(1);
            }

            // 处理延迟队列中的到期任务(可以放到定时任务中)
            $this->processDelayQueue();
        }
    }

    /**
     * 处理单个任务
     */
    private function processJob(array $job): void
    {
        $jobId = $job["id"] ?? "unknown";
        $orderData = $job["data"] ?? [];
        $attempts = ($job["attempts"] ?? 0) + 1;

        $this->log("INFO", "开始处理任务: {$jobId} (第{$attempts}次尝试)");

        try {
            // ========== 这里是具体业务逻辑 ==========

            // 1. 校验订单合法性
            $this->validateOrder($orderData);

            // 2. 库存扣减(使用 Lua 脚本保证原子性)
            $this->deductInventory($orderData);

            // 3. 创建订单记录
            $orderId = $this->createOrderRecord($orderData);

            // 4. 发送通知
            $this->sendNotification($orderId, $orderData);

            // 5. 记录操作日志
            $this->logOrder($orderId, $orderData);

            // ========== 业务逻辑结束 ==========

            // 成功:从 processing 队列中移除(确认完成)
            $this->redis->lrem($this->processingQueueName, 1, json_encode($job, JSON_UNESCAPED_UNICODE));
            $this->log("INFO", "任务处理成功: {$jobId}");

        } catch (Exception $e) {
            $this->handleFailure($job, $attempts, $e);
        }
    }

    /**
     * 处理失败逻辑:重试 or 死信
     */
    private function handleFailure(array $job, int $attempts, Exception $e): void
    {
        $jobId = $job["id"] ?? "unknown";
        $this->log("WARNING", "任务处理失败: {$jobId}, 错误: {$e->getMessage()}");

        if ($attempts < $this->maxAttempts) {
            // 还没到最大重试次数,需要指数退避避免无限重试循环
            $backoff = (int) pow(2, $attempts - 1); // 1s, 2s, 4s...
            $this->log("INFO", "任务将在 {$backoff} 秒后重试 ({$attempts}/{$this->maxAttempts}): {$jobId}");
            sleep($backoff);

            // 更新重试计数和最后重试时间
            $job["attempts"] = $attempts;
            $job["last_retry_at"] = date("Y-m-d H:i:s");

            // 从处理队列移除后重新放入主队列
            $this->redis->lrem($this->processingQueueName, 1, json_encode($job, JSON_UNESCAPED_UNICODE));
            $this->redis->lpush($this->queueName, json_encode($job, JSON_UNESCAPED_UNICODE));
        } else {
            // 超过最大重试次数,进入死信队列
            $this->redis->lrem($this->processingQueueName, 1, json_encode($job, JSON_UNESCAPED_UNICODE));
            $this->redis->lpush("queue:order:dead", json_encode([
                "job" => $job,
                "failed_at" => date("Y-m-d H:i:s"),
                "reason" => $e->getMessage(),
                "attempts" => $attempts,
            ], JSON_UNESCAPED_UNICODE));
            $this->log("ERROR", "任务进入死信队列: {$jobId}");
        }
    }

    /**
     * 处理延迟队列中已到期的任务
     * 使用 Sorted Set 实现延迟队列,score 存储到期时间戳
     */
    private function processDelayQueue(): void
    {
        $delayQueueName = "queue:order:delay";
        $now = time();

        // 获取所有到期任务(score <= 当前时间),每次最多取10个
        $jobs = $this->redis->zrangebyscore($delayQueueName, 0, $now, ["limit" => [0, 10]]);

        foreach ($jobs as $jobJson) {
            // 先从延迟队列中移除(原子操作,防止重复消费)
            $removed = $this->redis->zrem($delayQueueName, $jobJson);
            if ($removed > 0) {
                // 成功移除后再投放到主队列
                $this->redis->lpush($this->queueName, $jobJson);
            }
        }
    }

    // ========== 业务方法(示例)==========

    private function validateOrder(array $orderData): void
    {
        if (empty($orderData["order_id"]) || empty($orderData["user_id"])) {
            throw new InvalidArgumentException("订单数据不完整");
        }
    }

    private function deductInventory(array $orderData): void
    {
        // 使用 Lua 脚本保证库存扣减的原子性,防止超卖
        // Lua脚本返回值意义:
        // 大于0:扣减成功(返回剩余库存)
        // 0:库存不足
        // -1:key不存在(商品不存在)
        $luaScript = <<<'LUA'
local stock = redis.call('GET', KEYS[1])
if stock == false then
    return -1  -- 库存 key 不存在
end
stock = tonumber(stock)
if stock < tonumber(ARGV[1]) then
    return 0  -- 库存不足
end
local remaining = redis.call('DECRBY', KEYS[1], ARGV[1])
return remaining  -- 返回剩余库存
LUA;

        foreach ($orderData["goods"] ?? [] as $goods) {
            $goodsId = $goods["id"];
            $num = $goods["num"];
            $key = "goods:{$goodsId}:stock";

            $remaining = $this->redis->eval($luaScript, [$key], 1, $num);

            // PHP判断逻辑:大于0才是成功
            if ($remaining === false || $remaining < 0) {
                throw new RuntimeException("商品 {$goodsId} 库存扣减失败");
            }
            if ((int)$remaining === 0) {
                throw new RuntimeException("商品 {$goodsId} 库存不足");
            }
            // 只有 remaining > 0 才是成功
        }
    }

    private function createOrderRecord(array $orderData): int
    {
        // 实际项目中应该是数据库插入
        // 这里用日志模拟
        $this->log("INFO", "订单记录已创建: order_id={$orderData['order_id']}");
        return $orderData["order_id"];
    }

    private function sendNotification(int $orderId, array $orderData): void
    {
        // 实际项目中是调用短信/推送服务
        // 这里用 sleep 模拟耗时操作
        $this->log("INFO", "通知已发送: order_id={$orderId}");
    }

    private function logOrder(int $orderId, array $orderData): void
    {
        $this->log("INFO", "操作日志已记录: order_id={$orderId}");
    }

    private function log(string $level, string $message): void
    {
        $timestamp = date("Y-m-d H:i:s");
        echo "[{$timestamp}] [{$level}] {$message}" . PHP_EOL;
    }
}

// ============ 启动消费者 ============
// 推荐使用 supervisor 管理进程
// 命令行运行:php order_consumer.php
// 守护进程运行:nohup php order_consumer.php > /var/log/order_consumer.log 2>&1 &

$consumer = new OrderConsumer();
$consumer->run();

4.3 ThinkPHP6 队列用法(补充)

如果你使用 ThinkPHP6,可以使用官方 topthink/think-queue 组件,封装度更高:

php 复制代码
<?php
// TP6 生产者
use think\facade\Queue;

public function createOrder()
{
    $orderData = [
        "order_id" => 10086,
        "user_id" => 9527,
        "total_amount" => 10998.00,
    ];

    // 1. 立即执行
    Queue::push(\app\job\OrderJob::class, $orderData, "order");

    // 2. 延迟 30 秒执行(订单超时取消场景)
    Queue::later(30, \app\job\OrderJob::class, $orderData, "order");

    return "订单已提交";
}
php 复制代码
<?php
// TP6 消费者 Job 类
namespace app\job;

use think\queue\Job;

class OrderJob
{
    public function fire(Job $job, array $data): void
    {
        $orderId = $data["order_id"] ?? 0;

        try {
            // 处理订单逻辑
            $this->processOrder($data);

            // 删除任务
            $job->delete();

        } catch (\Exception $e) {
            // 失败后最多重试3次
            if ($job->attempts() > 3) {
                $job->delete(); // 放弃
            } else {
                $job->release(10); // 10秒后重试
            }
        }
    }

    private function processOrder(array $data): void
    {
        // 业务逻辑
    }
}
bash 复制代码
# 命令行启动消费者(TP6)
php think queue:work --queue order --tries 3

五、常见坑及解决方案

坑一:消息丢失------没 ACK,挂了就没了

问题:消费者处理任务时进程崩溃,任务没处理完,但已经从队列里删了。

解决

  1. 使用 BRPOPLPUSH (或 BRPOP)将任务转移到处理中队列,处理完成后再手动删除。
  2. 开启 Redis AOF 持久化appendfsync everysecalways
  3. 重要任务用数据库做消息表 (最保险),状态机:待处理 → 处理中 → 已完成/失败

坑二:消息重复消费------幂等没做,同一个订单扣了两次钱

问题:消费者处理超时,触发了重试,同一个任务被执行了两遍。

解决幂等设计,每个任务带唯一 ID,处理前先查重:

php 复制代码
private function processJob(array $job): void
{
    $jobId = $job["id"];

    // 用 Redis SETNX 做分布式锁,防止并发处理
    $lockKey = "lock:job:{$jobId}";
    $lock = $this->redis->set($lockKey, 1, ["NX", "EX" => 60]);

    if (!$lock) {
        $this->log("WARNING", "任务正在被其他进程处理: {$jobId}");
        return; // 幂等:重复任务直接跳过
    }

    try {
        // 检查是否已处理过(用 Redis 或数据库记录)
        $processedKey = "processed:job:{$jobId}";
        if ($this->redis->exists($processedKey)) {
            $this->log("INFO", "任务已处理过,跳过: {$jobId}");
            return;
        }

        // 执行业务逻辑......
        $this->doRealWork($job["data"]);

        // 标记已处理(设置过期时间,防止内存泄漏)
        $this->redis->setex($processedKey, 86400 * 7, 1); // 7天后自动清除

    } finally {
        $this->redis->del($lockKey);
    }
}

坑三:队列积压------消费者太少,任务越堆越多

问题:高峰期任务大量涌入,消费者处理不过来,队列越来越长,用户感受到明显延迟。

解决

  1. 增加消费者数量 :部署多个消费者实例(使用 supervisor 或 K8s HPA 自动扩缩容)。
  2. 分区(Stream Consumer Group):多个消费者分工处理不同消息。
  3. 监控告警:设置队列长度阈值,超过阈值触发报警。
  4. 限流保护:在入口处做流量控制,避免瞬时洪峰。
php 复制代码
// 监控脚本示例
$queueLength = $redis->llen("queue:order");
if ($queueLength > 10000) {
    // 触发告警(接入钉钉/飞书/Sentry)
    $this->sendAlert("队列积压警告!当前长度: {$queueLength}");
}

坑四:顺序问题------先下单后发货,结果先发了货

问题 :Redis List 是 FIFO 队列,但多消费者并发处理时,先入队的消息不一定先处理完

解决

  1. 单消费者:严格的 FIFO,但吞吐量受限。
  2. 按订单 ID 哈希分区:相同订单的消息始终路由到同一个消费者。
  3. 依赖关系设计 :发货依赖下单完成,用延迟队列状态机控制顺序。
php 复制代码
// 按订单ID哈希确保同一订单消息到同一消费者
$consumerGroup = "order_group_" . (crc32($orderId) % $consumerCount);

六、生产环境 Checklist

上线队列功能前,逐项检查:

  • Redis 开启持久化 (AOF + RDB 双保险,appendfsync everysec
  • Redis 设置密码,禁止公网访问**
  • 消费者进程使用 Supervisor 管理,配置自动重启
  • 消息 ACK 机制已实现(BRPOPLPUSH 方案或手动 ACK)
  • 幂等去重已实现(防重复消费)
  • 死信队列已建立(失败任务有归宿)
  • 队列长度监控告警(超过阈值报警)
  • 消费者处理耗时监控(Prometheus + Grafana)
  • 重试次数限制(避免无限重试)
  • 延迟队列方案已设计(超时取消、延时发货等场景)
  • 消费者支持水平扩展(多个实例部署)
  • 环境配置文件分离(生产密码不通用于代码仓库)

总结

回到开头那个问题:订单总丢,怎么办?

答案不是"多加点日志",也不是"换个更强的服务器"。

答案是在架构层面做异步化:用队列把"用户下单"和"业务处理"解耦。

  • 同步处理:用户等得起,业务等不起。
  • 异步队列:用户秒响应,业务慢慢来。

队列不是什么高大上的技术,但它解决的是最实在的问题:让你的系统在高峰期不崩溃、在故障后不丢数据、在扩容时不多花冤枉钱

当然,队列不是银弹。它引入了复杂性:需要监控、运维、重试机制、幂等设计。但这些都是值得付出的代价------因为相比用户丢单、赔偿纠纷,队列的运维成本低太多了。


行动项清单

  1. 今晚:review 现有代码,找出所有同步处理的高风险点(库存、支付回调、通知)
  2. 本周:引入 Redis,为核心流程配置异步队列(参考文中的生产者/消费者代码)
  3. 本周:实现消息 ACK + 幂等去重机制(参考"坑二"解决方案)
  4. 两周内:配置 Supervisor 管理消费者进程,设置队列长度监控告警
  5. 上线前:完成 Checklist 逐项检查,确认 Redis 持久化和安全配置

相关阅读推荐


💡 有问题?评论区见! 关注作者,第一时间获取 PHP 工程化实战系列文章。

相关推荐
红尘散仙2 小时前
我把终端小说阅读器接上了 AI Agent:TRNovel 现在能用 skill 生成书源了
人工智能·后端·rust
卷毛的技术笔记4 小时前
告别硬编码!Spring AI Alibaba 实现 AI Agent 智能工具调用(Tool Calling)
java·人工智能·后端·python·spring·ai编程
会编程的土豆4 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
喵个咪4 小时前
GoWind Toolkit Go后端代码生成 完整全流程实战
后端·go·orm
basketball6165 小时前
Go 语言从入门到进阶:4. 数组和MAP使用方法总结
开发语言·后端·golang
qq_2518364575 小时前
SpringBoot+Vue 共享电池柜管理系统 完整实现 前后端分离项目实战 完整代码
vue.js·spring boot·后端
zhangxingchao5 小时前
AI 大模型核心六:量化、Workflow 与 Agent、多轮 RAG
前端·人工智能·后端
IT_陈寒6 小时前
Vite打包时遇到的坑,原来问题出在这里
前端·人工智能·后端
ayqy贾杰7 小时前
基层管理的三板斧,在AI时代行不通了
前端·后端·团队管理
Apifox7 小时前
Apifox 5 月更新|Postman 导入优化、Runner 支持非 root 运行、请求代码自动带鉴权
前端·后端·安全