PHP中常规通用接口验签加密规则设计

文章目录

在分布式系统或跨公司业务对接中,API 的安全性始终是首要考虑的问题。如何确保请求的身份真实、数据完整、防重放,同时保护敏感信息,是每个开发人员必须面对的挑战。

本文以 PHP(ThinkPHP 6) 作为实现案例,结合本人在实际项目开发中的实际经验,分享一套基于 HMAC-SHA256 签名 + AES-256-GCM 可选加密 的通用方案。文章将详细讲解设计思路,并给出服务端验证与客户端调用的完整代码,帮助读者快速落地。

为什么需要签名与加密?

在我的实际业务开发中,我需要对外提供一个下单接口,对方用 Java 开发,我司用 PHP 7。如果只依赖 HTTPS 传输,仍然存在以下风险:

  • 身份伪造:任何知道接口地址的人都可以发起请求。
  • 数据篡改:虽然 HTTPS 防窃听,但无法防止中间人修改请求内容(如修改订单金额)。
  • 重放攻击:攻击者截获合法请求后,反复发送相同请求。

解决方案

  • 签名 :使用 HMAC-SHA256 对请求关键参数(包括请求体摘要)进行签名,服务端验证签名,确保请求由合法方发出且内容未被修改。
  • 防重放:通过时间戳和一次性随机数(Nonce)限制请求的有效期和唯一性。
  • 加密(可选) :对敏感数据(如身份证号)进行 AES-256-GCM 加密,保证机密性。

这套方案不依赖特定语言特性,所有语言的标准库都能完美支持。

总体设计

方案概述

机制 作用 关键点
签名 验证请求身份及完整性 使用 HMAC-SHA256,待签名字符串包含 AccessKey、BodyMD5、Nonce、Secret、Timestamp,按字典序排序后拼接
防重放 防止重复请求 时间戳(300秒差值) + Nonce 缓存(10分钟)
加密 保护请求体机密性 AES-256-GCM,IV 12 字节随机,认证标签 16 字节,密文与 IV、标签拼接后 Base64 编码
签名与加密关系 签名基于原始明文计算,加密仅对请求体生效 确保签名能覆盖加密前的数据完整性

接入凭证

为每个调用方分配一对凭证:

参数名 说明
AccessKey 标识调用方身份,明文传输,可公开。
SecretKey 签名密钥,绝对保密,不通过网络传输,仅双方存储。长度建议 32 字节(256 位)。

请求头要求

所有请求必须携带以下 HTTP Header:

Header 类型 必填 说明
Content-Type string application/json; charset=utf-8
X-Access-Key string 调用方 AccessKey
X-Timestamp int 请求发起时的 Unix 时间戳(秒级)
X-Nonce string 随机字符串(建议 16 位以上),用于防重放
X-Signature string 签名值(小写十六进制)
Content-MD5 string 请求体(JSON 字符串)的 MD5 值(小写十六进制)
X-Encrypted string 如果请求体经过 AES-GCM 加密,则设为 true

签名生成步骤

  1. 准备参数 :需要参与签名的参数有 5 个:AccessKeyBodyMD5NonceSecretTimestamp
  2. 计算 BodyMD5 :无论是否加密,BodyMD5 都是对原始 JSON 字符串(即加密前的明文)进行 MD5 计算。
  3. 构造待签名字符串 :将上述 5 个参数按参数名 ASCII 升序排序 ,拼接成 key=value 的形式,用 & 连接。
    示例:AccessKey=hello_test_001&BodyMD5=abc123&Nonce=xyz789&Secret=mysecret&Timestamp=1734567890
  4. 计算签名 :使用 HMAC-SHA256 算法,密钥为 SecretKey,对上一步字符串进行加密,结果转为小写十六进制字符串。

防重放机制

  • 时间戳校验 :服务端检查 X-Timestamp 与当前时间的差值,允许误差 ≤ 300 秒(可配置)。
  • Nonce 缓存 :服务端将 X-Nonce 存入缓存(如 Redis),设置 10 分钟过期。同一 Nonce 在有效期内不可重复使用。

加密实现

若请求体包含敏感数据(如身份证号、银行卡号等),可进行对称加密 ,确保数据在传输过程中的机密性。我们采用 AES-256-GCM 模式,它集成了加密与认证,是目前最安全、跨语言支持最好的对称加密方案之一。

备注:以下模块的内容由Ai生成,仅供科普和学习。

不同加密模式对比

AES(Advanced Encryption Standard)是一种分组密码,固定处理 16 字节数据块。为了加密任意长度的数据,需要配合不同的工作模式,每种模式在安全性、性能、适用场景上各有差异。下表对比了几种常见模式:

模式 特点 安全性 是否需要填充 是否带认证 跨语言兼容性
ECB 最简单的模式,相同明文块产生相同密文块 不安全,存在明显模式特征 需要 容易,但强烈不建议使用
CBC 最经典的模式,每个明文块与前一个密文块异或后再加密 安全,但需额外保证完整性 需要(PKCS#7) 很好,需配合 HMAC 使用
CTR 流密码模式,将 AES 转化为流式加密 安全,但需保证完整性 不需要 很好,需配合 HMAC 使用
GCM 认证加密模式,加密的同时生成认证标签 非常安全,防篡改 不需要 是(内建) 优秀,主流语言均原生支持

为什么 AES-GCM 是最佳选择?

  1. 认证加密(AEAD) :GCM 模式在加密的同时生成一个认证标签(Tag),解密时会自动校验该标签,确保密文未被篡改。这一特性使得我们不需要再额外增加 HMAC 来保护数据完整性(但请求头的签名仍然保留,用于防重放和身份认证)。
  2. 无需填充:作为流式模式,GCM 可以直接处理任意长度的数据,无需进行 PKCS#7 填充。这避免了因填充不一致导致的跨语言兼容性问题(例如 Java 的 PKCS5Padding 与 PHP 的 PKCS#7 本质相同,但仍有细微差别)。
  3. 标准化与跨语言支持:AES-GCM 被广泛采纳为 TLS 1.3 的推荐加密套件,主流编程语言(PHP、Java、Go、Python、Node.js 等)均通过原生库或标准扩展提供支持,实现代码简洁且不易出错。
  4. 性能优秀:在支持 AES-NI 指令集的硬件上,AES-GCM 速度非常快。即使在纯软件实现中,其性能也优于同等级别的非对称加密或老旧模式(如 3DES)。

与其他对称加密算法的对比

算法 类型 密钥长度 特点 跨语言支持 推荐度
AES-256-GCM 对称加密 32 字节 标准、安全、性能高 极好 ⭐⭐⭐⭐⭐
ChaCha20-Poly1305 流加密 32 字节 软件实现快,适合移动端 良好(需特定库) ⭐⭐⭐⭐
SM4-GCM 国密算法 16 字节 中国标准,用于合规项目 一般(需额外库) ⭐⭐⭐(特定场景)
RSA 非对称 2048+ 性能差,不适合大数据加密 仅用于密钥交换
3DES 对称 168 位 已淘汰,存在理论攻击 ❌ 不推荐

小结AES-256-GCM 在安全性、性能、跨语言支持三方面均表现优异,是 API 数据加密的首选。因此,我们确定采用该模式作为可选加密方案。

加密和解密的流程步骤

加密流程步骤

  1. 生成随机 IV :IV(初始化向量)长度固定为 12 字节(96 位),使用安全随机数生成器(如 random_bytesSecureRandom)产生,每次加密都不同。
  2. 加密数据:使用 AES-256-GCM 模式对原始 JSON 字符串进行加密,获得密文(Ciphertext)和一个 16 字节的认证标签(Tag)。
  3. 组装密文包 :将 IV、Tag、密文按顺序拼接:IV(12) + Tag(16) + Ciphertext。这一结构是自描述的,方便解密时分离。
  4. Base64 编码:将拼接后的二进制数据转换为 Base64 字符串,作为最终的请求体(Body)。
  5. 请求头标识 :在 HTTP 头部添加 X-Encrypted: true,告知服务端该请求体已被加密,需要进行解密处理。

解密流程步骤

  1. Base64 解码:收到请求体后,先进行 Base64 解码,得到二进制数据。
  2. 分离 IV、Tag、密文:根据固定长度提取 IV(前 12 字节)、Tag(接着 16 字节)和剩余密文。
  3. 解密:使用相同的 SecretKey、IV、Tag,调用 AES-256-GCM 解密函数,获取原始明文。
  4. JSON 校验:解密后的内容必须是合法的 JSON 字符串,否则视为非法请求。

PHP代码示例

php 复制代码
// 加密函数
private function encryptAesGcm($plaintext, $key)
{
    $iv = random_bytes(12);
    $tag = null;
    $ciphertext = openssl_encrypt($plaintext, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag, '', 16);
    if ($ciphertext === false) {
        return false;
    }
    return [$ciphertext, $iv, $tag];
}

// 组装并编码
list($ciphertext, $iv, $tag) = encryptAesGcm($jsonBody, $secretKey);
$bodyToSend = base64_encode($iv . $tag . $ciphertext);

// 解密函数
$decoded = base64_decode($rawBody);
$iv = substr($decoded, 0, 12);
$tag = substr($decoded, 12, 16);
$ciphertext = substr($decoded, 28);
$plaintext = openssl_decrypt($ciphertext, 'aes-256-gcm', $secretKey, OPENSSL_RAW_DATA, $iv, $tag);

关键注意事项:

  • IV 不可重复使用:对于同一密钥,每次加密必须使用不同的 IV,否则会严重削弱安全性。我们使用随机生成即可保证。
  • 标签长度固定为 16 字节:虽然 GCM 支持多种标签长度,但为了跨语言统一,我们固定使用 128 位(16 字节)。
  • 签名基于原始明文Content-MD5 和签名计算时,都必须使用加密前的原始 JSON 字符串,而不是加密后的密文。这样才能确保签名能够覆盖数据的真实内容。
  • 加密与签名的关系:加密只保护请求体的机密性,签名保护整个请求的完整性和身份。两者各司其职,共同构建多层安全防护。

通过上述设计,即便在不加密的情况下,签名机制也能保证请求不被篡改;而在需要传输敏感信息时,加密层提供了额外的机密性保障。

PHP 服务端实现

这里,我将基于ThinkPHP6框架来实现一个完整的案例Demo,仅供参考。因为我在之前已经有过多次PHP项目案例的分享,代码就放在我的 https://gitee.com/rxbook/rx-php-box 这个项目里面。

配置文件

在项目中,我们将这两项配置在 .env 文件中,假设我们这里的业务是对接Hello API(注意区分测试与正式环境):

复制代码
[HELLO_API]
CLIENT_ACCESS_KEY = hello_test_001
CLIENT_SECRET_KEY = 941b0b2d05dee26f5a5cc759ff358ee7d85a4106784df35cd91a75cb3676074d
TIMESTAMP_TOLERANCE = 300
ENABLE_ENCRYPTION = true

创建配置文件,用于读取 .env 中的凭证和参数:config/hello_api.php

php 复制代码
<?php
return [
    'client_access_key' => env('HELLO_API.CLIENT_ACCESS_KEY'),
    'client_secret_key' => env('HELLO_API.CLIENT_SECRET_KEY'),
    'timestamp_tolerance' => env('HELLO_API.TIMESTAMP_TOLERANCE', 300),
    'enable_encryption' => env('HELLO_API.ENABLE_ENCRYPTION', true),
];

中间件实现

中间件负责统一处理验签、防重放和解密,并将解析后的参数注入请求对象。

文件位置app/middleware/api/HelloApiAuth.php

完整代码:https://gitee.com/rxbook/rx-php-box/blob/master/app/middleware/api/HelloApiAuth.php

核心逻辑实现:

1、获取请求头

首先从 HTTP 头部获取签名相关的字段,并检查必要字段是否存在,若缺失则返回 400 错误。

php 复制代码
$accessKey = $request->header('X-Access-Key');
$timestamp = $request->header('X-Timestamp');
$nonce     = $request->header('X-Nonce');
$signature = $request->header('X-Signature');
$contentMd5 = $request->header('Content-MD5');
$isEncrypted = $request->header('X-Encrypted') === 'true';
MyLog::writeLog($request->header(), 'HelloApiAuth_handle_header');

if (empty($accessKey) || empty($timestamp) || empty($nonce) || empty($signature)) {
    return $this->error(400, 'Missing required headers');
}

2、校验时间戳

检查请求时间戳与当前时间差值是否在允许范围内,防止重放攻击。

php 复制代码
$now = time();
$tolerance = config('hello_api.timestamp_tolerance');
if (abs($now - $timestamp) > $tolerance) {
    return $this->error(403, 'Timestamp expired');
}

3、校验 Nonce

将 Nonce 存入 Redis(或任何缓存),设置 10 分钟过期,确保同一 Nonce 不被重复使用。

php 复制代码
$nonceKey = 'api_nonce_' . $accessKey . '_' . $nonce;
if (RedisUtils::init()->get($nonceKey)) {
    return $this->error(403, 'Nonce already used');
}
RedisUtils::init()->set($nonceKey, 1, 600);

4、获取请求体并校验 Content-MD5

计算请求体的 MD5,与客户端提供的 Content-MD5 头比对,确保请求体在传输过程中未被篡改。

php 复制代码
$rawBody = $request->getContent();
$bodyMd5 = md5($rawBody);
if (!empty($contentMd5) && $contentMd5 !== $bodyMd5) {
    return $this->error(422, 'Content-MD5 mismatch');
}

5、验证 AccessKey 并获取 SecretKey

根据 AccessKey 获取对应的 SecretKey。实际项目中可能从数据库读取,这里简化从配置读取。

php 复制代码
$configAccessKey = config('hello_api.api_client_access_key');
if ($accessKey !== $configAccessKey) {
    return $this->error(401, 'Invalid AccessKey');
}
$secretKey = config('hello_api.api_client_secret_key');

6、验签

按照相同规则构造待签名字符串,计算签名,并与请求头中的签名比对。使用 hash_equals 防止时序攻击。

php 复制代码
$signParams = [
    'AccessKey' => $accessKey,
    'BodyMD5'   => $bodyMd5,
    'Nonce'     => $nonce,
    'Secret'    => $secretKey,
    'Timestamp' => $timestamp,
];
ksort($signParams);
$signStr = http_build_query($signParams, '', '&');
$expectedSignature = hash_hmac('sha256', $signStr, $secretKey);
if (!hash_equals($expectedSignature, $signature)) {
    return $this->error(401, 'Signature verification failed');
}

7、解密请求体(可选)

如果请求头携带 X-Encrypted: true,则对请求体进行 Base64 解码,分离 IV、标签、密文,然后使用 AES-256-GCM 解密。解密后的明文必须是一个合法的 JSON。

php 复制代码
if ($isEncrypted && config('hello_api.enable_encryption', false)) {
    $decoded = base64_decode($rawBody);
    if (strlen($decoded) < 28) {
        return $this->error(400, 'Invalid encrypted data');
    }
    $iv = substr($decoded, 0, 12);
    $tag = substr($decoded, 12, 16);
    $ciphertext = substr($decoded, 28);
    $plaintext = openssl_decrypt($ciphertext, 'aes-256-gcm', $secretKey, OPENSSL_RAW_DATA, $iv, $tag);
    if ($plaintext === false) {
        return $this->error(422, 'Decryption failed');
    }
    $rawBody = $plaintext;
    if (json_decode($rawBody) === null) {
        return $this->error(422, 'Invalid JSON after decryption');
    }
}

8、注入参数并继续

将解密后的 JSON 解析为数组,通过 withParsedBody 注入到请求对象中,这样控制器就可以通过 $request->post() 获取参数。

php 复制代码
$request->withParsedBody(json_decode($rawBody, true) ?: []);
return $next($request);

控制器实现

文件位置app/admin/controller/demo/CreateHelloData.php

控制器中无需关心签名和加密细节,直接使用 $request->post() 获取参数即可。这里忽略后续业务逻辑,只负责保证中间件鉴权通过后,打印接收到的请求参数。

php 复制代码
public function createOrders(Request $request){
    try {
      // 获取解密后的参数(数组格式)
      $params = $request->post();
      MyLog::writeLog($params, 'hello_createOrders_params');

      return json([
        'code' => 200,
        'msg' => '接口正在开发中...',
        'data' => 'hello', //todo
      ]);
    } catch (\Exception $e) {
      MyLog::record()->writeExceptionLog($e, 'hello_createOrders_exception');
      return json([
        'code' => 999,
        'msg' => '请求失败',
        'data' => $e->getMessage(),
      ]);
    }
}

添加路由

文件位置:app/admin/route/Demo.php

记得给这个路由设置中间件,也就是->middleware(HelloApiAuth::class)

php 复制代码
<?php
use think\facade\Route;
use app\middleware\api\HelloApiAuth;
Route::group('demo', function () {
    Route::post('createHelloOrders', 'demo.CreateHelloData/createOrders')->middleware(HelloApiAuth::class);
});

接下来,如果直接通过postman请求,肯定是不会通过的:

客户端模拟调用

为了方便测试,我这里编写一个 ThinkPHP 命令行指令,模拟对方客户端的调用行为,包含签名生成和可选加密。

代码实现

config/console.php 中添加:

php 复制代码
'commands' => [
    'testCreateHelloOrders' => 'app\command\test\TestCreateHelloOrders',
],

指令实现:在app/command/dajia/TestCreateOrders.php中编写实现逻辑。

完整代码:https://gitee.com/rxbook/rx-php-box/blob/master/app/command/test/TestCreateHelloOrders.php

php 复制代码
$access_key = config('hello_api.client_access_key');
$secret_key = config('hello_api.client_secret_key');
$apiUrl = 'http://rx-php-box.com/admin/demo/createHelloOrders';   //你的接口地址
$enableEncryption = true; // 是否启用加密
$testJson = '{"goods_id":123,"buy_num":2}';

准备原始 JSON 并加密(可选)

php 复制代码
$rawBodyJson = json_encode($testData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$bodyToSend = $rawBodyJson;

if ($enableEncryption) {
    $encrypted = $this->encryptAesGcm($rawBodyJson, $secretKey);
    list($ciphertext, $iv, $tag) = $encrypted;
    $bodyToSend = base64_encode($iv . $tag . $ciphertext);
}

加密函数使用 openssl_encrypt 实现 AES-256-GCM

php 复制代码
private function encryptAesGcm($plaintext, $key)
{
    $iv = random_bytes(12);
    $tag = null;
    $ciphertext = openssl_encrypt($plaintext, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag, '', 16);
    if ($ciphertext === false) {
        return false;
    }
    return [$ciphertext, $iv, $tag];
}

计算 BodyMD5 并生成签名

php 复制代码
$bodyMd5 = md5($rawBodyJson);  // 基于原始明文
$timestamp = time();
$nonce = bin2hex(random_bytes(8));

$signParams = [
    'AccessKey' => $accessKey,
    'BodyMD5'   => $bodyMd5,
    'Nonce'     => $nonce,
    'Secret'    => $secretKey,
    'Timestamp' => $timestamp,
];
ksort($signParams);
$signStr = http_build_query($signParams, '', '&');
$signature = hash_hmac('sha256', $signStr, $secretKey);

构造请求头并发送HTTP请求

php 复制代码
$headers = [
    'Content-Type: application/json; charset=utf-8',
    'X-Access-Key: ' . $accessKey,
    'X-Timestamp: ' . $timestamp,
    'X-Nonce: ' . $nonce,
    'X-Signature: ' . $signature,
    'Content-MD5: ' . $bodyMd5,
];
if ($enableEncryption) {
    $headers[] = 'X-Encrypted: true';
}

$response = $this->sendCurl($apiUrl, $bodyToSend, $headers);

发送 cURL 请求的方法封装

php 复制代码
private function sendCurl($apiUrl, $bodyToSend, $headers)
{
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $apiUrl);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $bodyToSend);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HEADER, true);
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
    $responseHeaders = substr($response, 0, $headerSize);
    $responseBody = substr($response, $headerSize);
    curl_close($ch);
    return ['http_code' => $httpCode, 'headers' => $responseHeaders, 'body' => $responseBody];
}

运行效果

在命令行通过如下命令执行:

bash 复制代码
 php think testCreateHelloOrders
---------------------------------------------------- 
任务描述: 测试创建Hello订单 
---------------------------------------------------- 
成功获取到结果: {"code":100,"msg":"接口正在开发中...","data":"hello"}

观察打印的日志:

关键点总结

【签名与加密的协作】

  • 签名的 BodyMD5 始终基于原始明文计算,无论是否加密。

  • 加密后,请求体为密文,但 Content-MD5 头依然填写原始明文的 MD5。

  • 服务端收到请求后,先验签(使用 Content-MD5),再解密(若 X-Encrypted: true)。

  • 签名算法 HMAC-SHA256、加密算法 AES-256-GCM 均为业界标准,Java、Go、Python 等语言均有原生支持。

【防重放机制】

  • 时间戳允许偏差 300 秒(可配置)。
  • Nonce 缓存使用 Redis,有效期 10 分钟,确保同一 Nonce 不被重复使用。

【AES-256-GCM 的组装格式】

  • 加密时:IV(12字节) + TAG(16字节) + 密文 → Base64 编码。
  • 解密时:Base64 解码 → 分离 IV、TAG、密文 → 调用 openssl_decrypt

特别说明:本文部分内容由DeepSeek生成,但是核心思路是我自己想出来滴!

原本我还想基于Go和Java再实现一套类似的功能,但由于时间原因,暂时先不写了,等以后有时间了再说。

本文涉及的源代码已经上传到了我的代码仓库:https://gitee.com/rxbook/rx-php-box

个人感悟

个人的一点感悟:目前Ai能帮助程序员解决很多底层的问题,但是「人类大脑的思维方式和对于业务需求的拆解与落地」可能是现在Ai所不能及的,我认为这也是当下形势程序员更大的价值!

比如我之前想到写技术博客的时候上传的图片的问题,我研究了怎么使用"图床":<blog.csdn.net/rxbook/article/details/143838674>

一开始使用的是Gitee图床,本来用的好好地,突然有一天不能用了,我就改为了Github图床(虽然网络不稳定,但也能用);

后来发现,发布到CSDN的图片,转存Github图片的时候会出问题:

对于这个问题,我问了DeepSeek和豆包,还有好几个Ai平台,它们大部分给我的解决办法都是围绕着「如何实现CSDN转存Github的图片」、「如何实现markdown上传图片到Gitee图床」、「有没有更好的图床的实现方案」...

后来我也疲倦了,累了,突然脑子灵光一现:既然Github可以作为markdown上传图片的图床,Gitee的图片可以成功转存到CSDN,那么直接把这两者结合就可以了!所以,我就继续使用Github作为上传图片的图床,文章写完后,把本篇文章的图片从Github仓库pull下来,再复制到Gitee的仓库,再push到Gitee的仓库,然后再把文章中的图片链接的前缀从Github改成Gitee,问题就解决了啊!

好吧,我可真是的大聪明!

在和DeepSeek的沟通中,我是意外的有所收获:

一个本地仓库可以拥有多个不同的远程仓库别名,例如:

复制代码
git remote add origin https://github.com/用户名/图片仓库.git   # 第一个
git remote add gitee https://gitee.com/用户名/图片仓库.git     # 第二个

这样,你的本地仓库就有了两个远程:origin(指向 GitHub)和 gitee(指向 Giee)。

好吧,之前我都是手动的把图片从Github复制到Gitee,感谢DeepSeek告知我的这个功能,我现在可以直接设置两个远程仓库了。

相关推荐
chushiyunen2 小时前
python单例模式、大模型一次加载多次复用
开发语言·python·单例模式
skywalk81632 小时前
训推一体化的AI飞桨套件:paddlex初探,还是不太顺利
开发语言·paddle
Victor3562 小时前
MongoDB(63)如何配置数据压缩?
后端
lly2024062 小时前
《其他 W3C 活动》
开发语言
毕设源码-钟学长2 小时前
【开题答辩全过程】以 基于Springboot的在线考试系统为例,包含答辩的问题和答案
java·spring boot·后端
福楠2 小时前
现代C++ | 智能指针
c语言·开发语言·c++
神奇小汤圆2 小时前
京东大模型二面:RAG系统在实际部署中可能面临哪些挑战?
后端
ruxingli2 小时前
GoLang channel管道
开发语言·后端·golang
Risehuxyc2 小时前
PHP 的缓存机制
开发语言·缓存·php