- 更新注释说明relativePath参数已包含/uploads/前缀 - 移除硬编码的/uploads/路径拼接 - 添加注释解释FastAdmin存储路径结构 - 优化URL构建避免重复路径分隔符
148 lines
4.8 KiB
PHP
148 lines
4.8 KiB
PHP
<?php
|
||
|
||
namespace app\api\controller;
|
||
|
||
use app\common\controller\Api;
|
||
use app\common\model\OtaVersion;
|
||
use think\Exception;
|
||
use think\Log;
|
||
|
||
/**
|
||
* OTA 远程升级接口
|
||
*
|
||
* 无需鉴权,公开接口
|
||
*/
|
||
class Ota extends Api
|
||
{
|
||
// 无需登录
|
||
protected $noNeedLogin = ['checkVersion'];
|
||
// 无需鉴权
|
||
protected $noNeedRight = ['*'];
|
||
|
||
/**
|
||
* 检查设备版本更新
|
||
*
|
||
* GET /api/ota/checkVersion
|
||
*
|
||
* @param string $imei 设备 Android ID
|
||
* @param string $version 当前版本名
|
||
* @param string $build 当前构建号
|
||
*/
|
||
public function checkVersion()
|
||
{
|
||
$imei = $this->request->get('imei', '');
|
||
$version = $this->request->get('version', '');
|
||
$build = $this->request->get('build', '');
|
||
|
||
if (empty($imei)) {
|
||
$this->error('设备标识不能为空');
|
||
}
|
||
|
||
// 记录检查日志
|
||
Log::info("OTA检查: imei={$imei}, version={$version}, build={$build}");
|
||
|
||
// 查询最新已发布版本
|
||
$latestVersion = OtaVersion::where('status', 'published')
|
||
->order('version_code', 'desc')
|
||
->find();
|
||
|
||
if (!$latestVersion) {
|
||
$this->error('暂无可用版本');
|
||
}
|
||
|
||
// 转换为整数比较版本号
|
||
$localBuild = intval($build);
|
||
$serverBuild = intval($latestVersion['version_code']);
|
||
|
||
// 如果云端版本不高于本地版本,返回无更新
|
||
if ($serverBuild <= $localBuild) {
|
||
$this->success('已是最新版本', null);
|
||
}
|
||
|
||
// 检查最低版本限制
|
||
if (!empty($latestVersion['min_version'])) {
|
||
// 简单字符串比较版本名
|
||
if (version_compare($version, $latestVersion['min_version'], '<')) {
|
||
$this->error('当前版本过低,请先升级到 ' . $latestVersion['min_version']);
|
||
}
|
||
}
|
||
|
||
// 检查设备白名单(如果设置了 target_devices)
|
||
if (!empty($latestVersion['target_devices'])) {
|
||
$targets = array_map('trim', explode(',', $latestVersion['target_devices']));
|
||
if (!in_array($imei, $targets)) {
|
||
$this->error('该设备不在此次升级范围内');
|
||
}
|
||
}
|
||
|
||
// 构建返回数据
|
||
$apkUrl = $this->buildApkUrl($latestVersion['apk_file']);
|
||
$sha256 = $latestVersion['sha256'] ?: '';
|
||
|
||
// 如果 sha256 为空或 fileSize 为空,尝试从文件计算
|
||
if (empty($sha256) || empty($latestVersion['file_size'])) {
|
||
$fileInfo = $this->calculateFileInfo($latestVersion['apk_file']);
|
||
if ($fileInfo) {
|
||
if (empty($sha256)) {
|
||
$sha256 = $fileInfo['sha256'];
|
||
// 可选:自动更新数据库
|
||
// OtaVersion::update(['sha256' => $sha256], ['id' => $latestVersion['id']]);
|
||
}
|
||
if (empty($latestVersion['file_size'])) {
|
||
$latestVersion['file_size'] = $fileInfo['file_size'];
|
||
}
|
||
}
|
||
}
|
||
|
||
$data = [
|
||
'versionName' => $latestVersion['version_name'],
|
||
'versionCode' => (int)$latestVersion['version_code'],
|
||
'downloadUrl' => $apkUrl,
|
||
'updateLog' => $latestVersion['update_log'],
|
||
'fileSize' => (int)($latestVersion['file_size'] ?? 0),
|
||
'sha256' => $sha256,
|
||
'isForceUpdate' => (bool)$latestVersion['is_force_update'],
|
||
];
|
||
|
||
$this->success('发现新版本', $data);
|
||
}
|
||
|
||
/**
|
||
* 构建 APK 下载 URL
|
||
*
|
||
* 客户端要求必须使用 HTTPS 协议,此处强制转换为 HTTPS。
|
||
*
|
||
* @param string $relativePath 相对路径(FastAdmin 已含 /uploads/ 前缀)
|
||
* @return string 完整 HTTPS URL
|
||
*/
|
||
private function buildApkUrl($relativePath)
|
||
{
|
||
$domain = request()->domain();
|
||
// 强制使用 HTTPS,客户端要求 downloadUrl 必须以 https:// 开头
|
||
$domain = preg_replace('/^http:\/\//i', 'https://', $domain);
|
||
// FastAdmin 存储的路径已含 /uploads/ 前缀,直接拼接即可
|
||
return rtrim($domain, '/') . '/' . ltrim($relativePath, '/');
|
||
}
|
||
|
||
/**
|
||
* 计算 APK 文件的 SHA-256 和文件大小
|
||
*
|
||
* @param string $relativePath 相对路径
|
||
* @return array|null ['sha256' => string, 'file_size' => int]
|
||
*/
|
||
private function calculateFileInfo($relativePath)
|
||
{
|
||
$fullPath = ROOT_PATH . 'public' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . ltrim($relativePath, '/');
|
||
if (!file_exists($fullPath)) {
|
||
return null;
|
||
}
|
||
|
||
$fileSize = filesize($fullPath);
|
||
$sha256 = hash_file('sha256', $fullPath);
|
||
|
||
return [
|
||
'sha256' => $sha256,
|
||
'file_size' => $fileSize,
|
||
];
|
||
}
|
||
} |