一、sync_helper.php 总览
sync_helper.php 是整个项目的基础设施层,提供了 Token 生命周期管理、API 调用封装、数据库连接、日志记录等通用功能。所有 6 个同步模块都依赖它,无需重复实现。

二、配置管理
2.1 config.json 结构
{
"appKey": "IERNZ8N49YRWRM6OBPCDQKE0WY9AK7UZ",
"appSecret": "YOUR_APP_SECRET",
"certificate": "YOUR_CERTIFICATE",
"secretKey": "YOUR_SECRET_KEY",
"openToken": "eyJhbG...",
"refreshToken": "def502...",
"token_expiry": 1718640000,
"refresh_expiry": 1739404800,
"db": {
"host": "127.0.0.1",
"port": "3306",
"name": "sq_t1",
"user": "root",
"pass": "your_password"
}
}
2.2 动态更新配置
function loadConfig() {
$json = file_get_contents(__DIR__ . '/config.json');
return json_decode($json, true);
}
function saveConfig($config) {
file_put_contents(
__DIR__ . '/config.json',
json_encode($config, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
);
}
💡 设计要点 :
getValidToken()使用引用传递&$config,在刷新 Token 后直接更新配置数组,再调用saveConfig()持久化。外部调用方无需关心 Token 更新细节。
三、Token 生命周期管理
3.1 智能刷新策略

3.2 关键实现
function getValidToken(&$config) {
$now = time();
$tokenExpiry = $config['token_expiry'] ?? 0;
// 提前 10 分钟刷新,避免边缘情况
if (!empty($config['openToken']) && $now < ($tokenExpiry - 600)) {
return $config['openToken'];
}
// 优先使用 refreshToken(减少 API 调用)
if (!empty($config['refreshToken']) && $now < ($config['refresh_expiry'] ?? 0)) {
try {
$newToken = refreshToken($config);
if ($newToken) return $newToken;
} catch (Exception $e) {
logMsg("refreshToken 失败,走完整流程: " . $e->getMessage());
}
}
// 完整流程
return fullTokenFlow($config);
}
四、AES 解密实现
4.1 算法参数
| 参数 | 值 |
|---|---|
| 算法 | AES-128-ECB |
| 密钥长度 | 128 bit (16 byte) |
| 填充方式 | PKCS5 Padding |
| 输入格式 | Base64 编码密文 |
| 输出格式 | JSON(含 bizContent.appTicket) |
4.2 完整解密函数
function decryptAppTicket($encryptMsg, $secretKey) {
// 1. Base64 解码
$decoded = base64_decode($encryptMsg);
// 2. AES-128-ECB 解密
$decrypted = openssl_decrypt(
$decoded,
'AES-128-ECB',
$secretKey,
OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING
);
if ($decrypted === false) {
// 降级尝试:使用 PKCS7 填充模式
$decrypted = openssl_decrypt(
$decoded,
'AES-128-ECB',
$secretKey,
OPENSSL_RAW_DATA
);
}
if ($decrypted === false) {
throw new Exception("AES 解密失败");
}
// 3. 手动去除 PKCS5 尾部填充
$pad = ord($decrypted[strlen($decrypted) - 1]);
if ($pad > 0 && $pad <= 16) {
$decrypted = substr($decrypted, 0, -$pad);
}
// 4. 解析 JSON 提取 appTicket
$data = json_decode($decrypted, true);
if (!isset($data['bizContent']['appTicket'])) {
throw new Exception("appTicket 缺失");
}
return $data['bizContent']['appTicket'];
}
4.3 多密钥兼容机制

五、API 调用封装
5.1 通用 HTTP 调用
function callApi($url, $data, $headers = []) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => is_array($data) ? json_encode($data) : $data,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 60,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_SSL_VERIFYPEER => false, // 内网环境
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
throw new Exception("cURL 错误: {$error}");
}
$data = json_decode($response, true);
if ($httpCode >= 400) {
throw new Exception("HTTP {$httpCode}: " . ($data['message'] ?? $response));
}
return $data;
}
5.2 T+ API 专用封装
function callTplusApi($appKey, $appSecret, $openToken, $url, $body) {
$headers = [
'Content-Type: application/json',
"appKey: {$appKey}",
"appSecret: {$appSecret}",
"openToken: {$openToken}",
];
return callApi($url, $body, $headers);
}
💡 自动在 Header 中注入
appKey、appSecret、openToken三个认证头,调用方无需手动拼接。
六、数据处理工具
6.1 extractRows - 兼容多种响应格式
不同 API 返回格式各异,extractRows() 统一处理:
function extractRows($res) {
// 格式1: Data 键直接包含数组(Query 接口)
if (isset($res['Data']) && is_array($res['Data'])) {
return $res['Data'];
}
// 格式2: result.Data(某些接口有 result 包裹)
if (isset($res['result']['Data']) && is_array($res['result']['Data'])) {
return $res['result']['Data'];
}
// 格式3: 直接是数组列表
if (is_array($res) && isset($res[0])) {
return $res;
}
return [];
}
6.2 toBool - 布尔值标准化
畅捷通 API 返回的布尔值可能是 true/false(JSON)、"true"/"false"(字符串)、1/0(整数):
function toBool($val) {
if (is_bool($val)) return $val;
if (is_string($val)) return strtolower($val) === 'true' || $val === '1';
if (is_numeric($val)) return intval($val) === 1;
return (bool) $val;
}
七、数据库连接
7.1 PDO 单例
function getDB($dbConfig) {
static $pdo = null;
if ($pdo === null) {
$dsn = "mysql:host={$dbConfig['host']};port={$dbConfig['port']};dbname={$dbConfig['name']};charset=utf8mb4";
$pdo = new PDO($dsn, $dbConfig['user'], $dbConfig['pass'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
}
return $pdo;
}
💡
static $pdo = null确保同一请求内只创建一次数据库连接,避免重复连接开销。
八、日志系统
function logMsg($msg) {
$time = date('Y-m-d H:i:s');
echo "[{$time}] {$msg}\n";
// 也可扩展写入文件
}
输出格式:
[2026-06-17 08:09:45] 开始同步存货(QueryPage 分页)...
[2026-06-17 08:10:32] 存货 QueryPage 第1页: 写入 200 条, 累计 200 / 7988
九、设计原则总结
| 原则 | 实现方式 |
|---|---|
| 单一职责 | Token管理/API调用/数据处理各司其职 |
| DRY | 6 个同步模块复用同一套 helper |
| 容错性 | 多密钥兼容、双重解密模式、降级重试 |
| 可观测性 | 每步操作均有日志输出 |
| 幂等性 | INSERT ON DUPLICATE KEY UPDATE 保证重复执行安全 |
| 安全性 | 敏感信息存 config.json,不硬编码 |