mirror of
https://github.com/crmeb/CRMEB.git
synced 2026-03-25 14:53:52 +00:00
473 lines
17 KiB
PHP
473 lines
17 KiB
PHP
<?php
|
||
// +----------------------------------------------------------------------
|
||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||
// +----------------------------------------------------------------------
|
||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||
// +----------------------------------------------------------------------
|
||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||
// +----------------------------------------------------------------------
|
||
// | Author: CRMEB Team <admin@crmeb.com>
|
||
// +----------------------------------------------------------------------
|
||
namespace app\services\system;
|
||
|
||
use app\services\BaseServices;
|
||
use crmeb\exceptions\AdminException;
|
||
use think\facade\Log;
|
||
|
||
/**
|
||
* Node.js 环境管理服务类
|
||
*
|
||
* 功能概述:
|
||
* 本服务类负责检测和管理小程序 CI 上传所需的运行环境,
|
||
* 包括 Node.js、npm 和 miniprogram-ci 工具的检测与安装指导。
|
||
*
|
||
* 主要功能:
|
||
* 1. 环境检测 - 检测 Node.js、npm、miniprogram-ci 的安装状态和版本
|
||
* 2. 系统识别 - 识别操作系统类型 (CentOS/Ubuntu/macOS/Windows)
|
||
* 3. 安装指南 - 根据操作系统提供对应的安装命令
|
||
* 4. exec 函数检测 - 检查 PHP 的 exec() 函数是否可用
|
||
*
|
||
* 环境要求:
|
||
* - Node.js >= 14.0.0 (推荐 18.x LTS)
|
||
* - npm (随 Node.js 一起安装)
|
||
* - miniprogram-ci (通过 npm install -g miniprogram-ci 全局安装)
|
||
* - PHP exec() 函数未被禁用
|
||
*
|
||
* @package app\services\system
|
||
*/
|
||
class NodeEnvironmentServices extends BaseServices
|
||
{
|
||
/**
|
||
* Node.js 最低版本要求
|
||
*
|
||
* miniprogram-ci 工具需要 Node.js 14.0.0 或更高版本。
|
||
*/
|
||
const MIN_NODE_VERSION = '14.0.0';
|
||
|
||
/**
|
||
* 推荐的 Node.js 版本
|
||
*
|
||
* 推荐使用 Node.js 18 LTS 版本,提供更好的性能和安全性。
|
||
*/
|
||
const RECOMMENDED_NODE_VERSION = '18';
|
||
|
||
/**
|
||
* 获取完整的环境状态信息
|
||
*
|
||
* 检测并返回小程序 CI 上传所需的所有环境信息,
|
||
* 前端根据这些信息展示环境就绪状态或引导用户完成环境配置。
|
||
*
|
||
* @return array 完整的环境状态信息,包含:
|
||
* - os: 操作系统信息 {family, type, version}
|
||
* - node: Node.js 状态 {installed, version, path, meets_requirement}
|
||
* - npm: npm 状态 {installed, version}
|
||
* - miniprogram_ci: CI工具状态 {installed, version}
|
||
* - ready: 环境是否完全就绪
|
||
* - can_install: 是否支持自动安装
|
||
* - exec_enabled: exec 函数是否可用
|
||
* - message: 提示信息
|
||
*/
|
||
public function getEnvironmentStatus(): array
|
||
{
|
||
// 检测各项环境
|
||
$nodeInfo = $this->checkNodeInstalled(); // Node.js 状态
|
||
$npmInfo = $this->checkNpmInstalled(); // npm 状态
|
||
$ciInfo = $this->checkMiniprogramCIInstalled(); // miniprogram-ci 状态
|
||
$osInfo = $this->getOsInfo(); // 操作系统信息
|
||
$execEnabled = $this->isExecEnabled(); // exec 函数可用性
|
||
|
||
return [
|
||
'os' => $osInfo, // 操作系统信息
|
||
'node' => $nodeInfo, // Node.js 状态
|
||
'npm' => $npmInfo, // npm 状态
|
||
'miniprogram_ci' => $ciInfo, // miniprogram-ci 状态
|
||
// 环境就绪条件: Node.js + npm + miniprogram-ci 均已安装且 exec 可用
|
||
'ready' => $nodeInfo['installed'] && $npmInfo['installed'] && $ciInfo['installed'] && $execEnabled,
|
||
'can_install' => $this->canAutoInstall(), // 是否支持自动安装
|
||
'exec_enabled' => $execEnabled, // exec 函数是否可用
|
||
'message' => $execEnabled ? '' : '服务器禁用了 exec 函数,无法使用小程序上传功能。请联系服务器管理员启用 exec 函数。',
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 检查 PHP exec() 函数是否可用
|
||
*
|
||
* 小程序 CI 上传功能依赖 PHP 的 exec() 函数来执行命令行工具。
|
||
* 很多服务器出于安全考虑会禁用该函数,需要检测其可用性。
|
||
*
|
||
* 检测步骤:
|
||
* 1. 检查 exec 函数是否存在
|
||
* 2. 检查 disable_functions 配置中是否包含 exec
|
||
* 3. 尝试执行简单命令验证实际可用性
|
||
*
|
||
* @return bool exec 函数可用返回 true,否则返回 false
|
||
*/
|
||
public function isExecEnabled(): bool
|
||
{
|
||
// 检查 exec 函数是否存在
|
||
if (!function_exists('exec')) {
|
||
return false;
|
||
}
|
||
|
||
// 检查 disable_functions 配置中是否禁用了 exec
|
||
$disabled = explode(',', ini_get('disable_functions'));
|
||
$disabled = array_map('trim', $disabled);
|
||
if (in_array('exec', $disabled)) {
|
||
return false;
|
||
}
|
||
|
||
// 尝试执行一个简单命令验证实际可用性
|
||
$output = [];
|
||
$code = 0;
|
||
@exec('echo test 2>&1', $output, $code);
|
||
|
||
return $code === 0 && !empty($output);
|
||
}
|
||
|
||
/**
|
||
* 检查 Node.js 是否已安装
|
||
*
|
||
* 通过执行 node -v 命令获取 Node.js 的安装状态和版本信息。
|
||
*
|
||
* @return array Node.js 状态信息,包含:
|
||
* - installed: 是否已安装
|
||
* - version: 版本号 (如 18.17.0)
|
||
* - path: 可执行文件路径
|
||
* - meets_requirement: 是否满足最低版本要求
|
||
*/
|
||
public function checkNodeInstalled(): array
|
||
{
|
||
$result = [
|
||
'installed' => false,
|
||
'version' => '',
|
||
'path' => '',
|
||
'meets_requirement' => false,
|
||
];
|
||
|
||
// 执行 node -v 获取版本信息
|
||
$output = $this->execCommand('node -v 2>&1');
|
||
if ($output && preg_match('/v?(\d+\.\d+\.\d+)/', $output, $matches)) {
|
||
$result['installed'] = true;
|
||
$result['version'] = $matches[1];
|
||
// 检查版本是否满足最低要求 (>= 14.0.0)
|
||
$result['meets_requirement'] = version_compare($matches[1], self::MIN_NODE_VERSION, '>=');
|
||
|
||
// 获取 node 可执行文件的完整路径
|
||
$path = $this->execCommand('which node 2>&1');
|
||
$result['path'] = trim($path);
|
||
}
|
||
|
||
return $result;
|
||
}
|
||
|
||
/**
|
||
* 检查 npm 是否已安装
|
||
*
|
||
* npm 是 Node.js 的包管理器,通常随 Node.js 一起安装。
|
||
* 用于安装 miniprogram-ci 等 npm 包。
|
||
*
|
||
* @return array npm 状态信息,包含:
|
||
* - installed: 是否已安装
|
||
* - version: 版本号
|
||
*/
|
||
public function checkNpmInstalled(): array
|
||
{
|
||
$result = [
|
||
'installed' => false,
|
||
'version' => '',
|
||
];
|
||
|
||
// 执行 npm -v 获取版本信息
|
||
$output = $this->execCommand('npm -v 2>&1');
|
||
if ($output && preg_match('/(\d+\.\d+\.\d+)/', $output, $matches)) {
|
||
$result['installed'] = true;
|
||
$result['version'] = $matches[1];
|
||
}
|
||
|
||
return $result;
|
||
}
|
||
|
||
/**
|
||
* 检查 miniprogram-ci 是否已全局安装
|
||
*
|
||
* miniprogram-ci 是微信官方提供的小程序代码上传工具。
|
||
* 需要通过 npm install -g miniprogram-ci 全局安装。
|
||
*
|
||
* @return array miniprogram-ci 状态信息,包含:
|
||
* - installed: 是否已安装
|
||
* - version: 版本号
|
||
*/
|
||
public function checkMiniprogramCIInstalled(): array
|
||
{
|
||
$result = [
|
||
'installed' => false,
|
||
'version' => '',
|
||
];
|
||
|
||
// 通过 npm list -g 检查全局安装的包
|
||
$output = $this->execCommand('npm list -g miniprogram-ci --depth=0 2>&1');
|
||
if ($output && preg_match('/miniprogram-ci@(\d+\.\d+\.\d+)/', $output, $matches)) {
|
||
$result['installed'] = true;
|
||
$result['version'] = $matches[1];
|
||
}
|
||
|
||
return $result;
|
||
}
|
||
|
||
/**
|
||
* 获取操作系统信息
|
||
*
|
||
* 识别服务器的操作系统类型,用于提供对应的安装指南。
|
||
* 使用命令行方式检测,避免受 open_basedir 限制影响。
|
||
*
|
||
* 支持识别的系统:
|
||
* - Linux: CentOS/RHEL/Rocky/Ubuntu/Debian/Alpine 等
|
||
* - macOS: 通过 sw_vers 获取版本
|
||
* - Windows: 通过 PHP_OS_FAMILY 识别
|
||
*
|
||
* @return array 操作系统信息,包含:
|
||
* - family: 系统家族 (Linux/Darwin/Windows)
|
||
* - type: 具体类型 (centos/ubuntu/debian/macos/windows)
|
||
* - version: 系统版本号
|
||
*/
|
||
public function getOsInfo(): array
|
||
{
|
||
$os = PHP_OS_FAMILY; // 获取 PHP 识别的操作系统家族
|
||
$type = 'unknown';
|
||
$version = '';
|
||
|
||
if ($os === 'Linux') {
|
||
// Linux 系统: 通过命令行读取 /etc/os-release 获取发行版信息
|
||
$osRelease = $this->execCommand('cat /etc/os-release 2>/dev/null');
|
||
|
||
if ($osRelease) {
|
||
// 解析 os-release 文件内容
|
||
$lines = explode("\n", $osRelease);
|
||
$osInfo = [];
|
||
foreach ($lines as $line) {
|
||
if (strpos($line, '=') !== false) {
|
||
list($key, $value) = explode('=', $line, 2);
|
||
$osInfo[trim($key)] = trim($value, '"\' ');
|
||
}
|
||
}
|
||
|
||
$id = strtolower($osInfo['ID'] ?? '');
|
||
$version = $osInfo['VERSION_ID'] ?? '';
|
||
|
||
// 映射操作系统类型
|
||
if (in_array($id, ['centos', 'rhel', 'rocky', 'almalinux', 'fedora'])) {
|
||
$type = 'centos'; // Red Hat 系列
|
||
} elseif ($id === 'ubuntu') {
|
||
$type = 'ubuntu';
|
||
} elseif (in_array($id, ['debian', 'raspbian'])) {
|
||
$type = 'debian';
|
||
} elseif ($id === 'alpine') {
|
||
$type = 'alpine';
|
||
} else {
|
||
$type = $id ?: 'linux';
|
||
}
|
||
} else {
|
||
// 备用方案: 使用 uname 命令
|
||
$uname = $this->execCommand('uname -a 2>/dev/null');
|
||
$type = 'linux';
|
||
$version = $uname ?: '';
|
||
}
|
||
} elseif ($os === 'Darwin') {
|
||
// macOS 系统
|
||
$type = 'macos';
|
||
$version = $this->execCommand('sw_vers -productVersion 2>&1') ?: '';
|
||
} elseif ($os === 'Windows') {
|
||
// Windows 系统
|
||
$type = 'windows';
|
||
}
|
||
|
||
return [
|
||
'family' => $os, // 系统家族
|
||
'type' => $type, // 具体类型
|
||
'version' => trim($version), // 版本号
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 检查是否支持自动安装
|
||
*
|
||
* 检查服务器环境是否支持自动安装 Node.js 和相关工具。
|
||
* 自动安装功能依赖于操作系统类型和 PHP 函数可用性。
|
||
*
|
||
* 支持的操作系统: CentOS/RHEL、Ubuntu、Debian、Alpine、macOS
|
||
*
|
||
* @return bool 支持自动安装返回 true
|
||
*/
|
||
public function canAutoInstall(): bool
|
||
{
|
||
$os = $this->getOsInfo();
|
||
|
||
// 支持自动安装的操作系统列表
|
||
$supportedOs = ['centos', 'rhel', 'ubuntu', 'debian', 'alpine', 'macos'];
|
||
|
||
if (!in_array($os['type'], $supportedOs)) {
|
||
return false;
|
||
}
|
||
|
||
// 检查是否有执行命令的权限 (exec 或 shell_exec)
|
||
if (!function_exists('exec') && !function_exists('shell_exec')) {
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 执行命令并返回输出
|
||
*
|
||
* 封装的命令执行方法,优先使用 exec,如果不可用则尝试 shell_exec。
|
||
*
|
||
* @param string $command 要执行的命令
|
||
* @return string|null 命令输出,执行失败返回 null
|
||
*/
|
||
protected function execCommand(string $command): ?string
|
||
{
|
||
// 优先使用 exec 函数
|
||
if (function_exists('exec')) {
|
||
$output = [];
|
||
exec($command, $output);
|
||
return implode("\n", $output);
|
||
}
|
||
// 备用: 使用 shell_exec 函数
|
||
elseif (function_exists('shell_exec')) {
|
||
return shell_exec($command);
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 获取一键安装脚本 URL
|
||
*
|
||
* 返回用于自动安装 Node.js 环境的 Shell 脚本地址。
|
||
*
|
||
* @return string 安装脚本的 URL 地址
|
||
*/
|
||
public function getInstallScriptUrl(): string
|
||
{
|
||
// return sys_config('site_url', '') . '/statics/scripts/install_node_env.sh';
|
||
return 'https://www.crmeb.com/static/upgrade/install_node_env.sh';
|
||
}
|
||
|
||
/**
|
||
* 获取安装指南 (手动安装说明)
|
||
*
|
||
* 根据服务器操作系统类型返回对应的 Node.js 和 miniprogram-ci 安装步骤。
|
||
* 每个操作系统都有专门优化的安装命令。
|
||
*
|
||
* 支持的操作系统:
|
||
* - CentOS/RHEL: 使用 NodeSource 的 rpm 仓库
|
||
* - Ubuntu/Debian: 使用 NodeSource 的 deb 仓库
|
||
* - macOS: 使用 Homebrew 包管理器
|
||
* - Windows: 从 Node.js 官网下载安装包
|
||
*
|
||
* @return array 安装指南信息,包含:
|
||
* - title: 指南标题 (如 "CentOS/RHEL 安装指南")
|
||
* - steps: 安装步骤数组,包含具体的命令行指令
|
||
* - script_url: 一键安装脚本的 URL 地址
|
||
*/
|
||
public function getInstallGuide(): array
|
||
{
|
||
// 获取当前操作系统信息
|
||
$os = $this->getOsInfo();
|
||
|
||
// 各操作系统的安装指南
|
||
$guides = [
|
||
// CentOS/RHEL 系列 - 使用 yum 包管理器
|
||
'centos' => [
|
||
'title' => 'CentOS/RHEL 安装指南',
|
||
'steps' => [
|
||
'1. 添加 NodeSource 仓库:',
|
||
' curl -fsSL https://rpm.nodesource.com/setup_18.x | sudo bash -',
|
||
'2. 安装 Node.js:',
|
||
' sudo yum install -y nodejs',
|
||
'3. 验证安装:',
|
||
' node -v && npm -v',
|
||
'4. 安装 miniprogram-ci:',
|
||
' sudo npm install miniprogram-ci -g',
|
||
],
|
||
],
|
||
// Ubuntu - 使用 apt 包管理器
|
||
'ubuntu' => [
|
||
'title' => 'Ubuntu/Debian 安装指南',
|
||
'steps' => [
|
||
'1. 添加 NodeSource 仓库:',
|
||
' curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -',
|
||
'2. 安装 Node.js:',
|
||
' sudo apt-get install -y nodejs',
|
||
'3. 验证安装:',
|
||
' node -v && npm -v',
|
||
'4. 安装 miniprogram-ci:',
|
||
' sudo npm install miniprogram-ci -g',
|
||
],
|
||
],
|
||
// Debian - 与 Ubuntu 相同
|
||
'debian' => [
|
||
'title' => 'Ubuntu/Debian 安装指南',
|
||
'steps' => [
|
||
'1. 添加 NodeSource 仓库:',
|
||
' curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -',
|
||
'2. 安装 Node.js:',
|
||
' sudo apt-get install -y nodejs',
|
||
'3. 验证安装:',
|
||
' node -v && npm -v',
|
||
'4. 安装 miniprogram-ci:',
|
||
' sudo npm install miniprogram-ci -g',
|
||
],
|
||
],
|
||
// macOS - 使用 Homebrew
|
||
'macos' => [
|
||
'title' => 'macOS 安装指南',
|
||
'steps' => [
|
||
'1. 安装 Homebrew (如果未安装):',
|
||
' /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"',
|
||
'2. 安装 Node.js:',
|
||
' brew install node@18',
|
||
'3. 验证安装:',
|
||
' node -v && npm -v',
|
||
'4. 安装 miniprogram-ci:',
|
||
' npm install miniprogram-ci -g',
|
||
],
|
||
],
|
||
// Windows - 从官网下载安装
|
||
'windows' => [
|
||
'title' => 'Windows 安装指南',
|
||
'steps' => [
|
||
'1. 下载 Node.js 安装包:',
|
||
' 访问 https://nodejs.org/zh-cn/download/',
|
||
'2. 运行安装程序,按提示完成安装',
|
||
'3. 打开命令提示符,验证安装:',
|
||
' node -v && npm -v',
|
||
'4. 安装 miniprogram-ci:',
|
||
' npm install miniprogram-ci -g',
|
||
],
|
||
],
|
||
];
|
||
|
||
// 根据操作系统类型获取对应指南,未知系统使用通用指南
|
||
$type = $os['type'] ?: 'unknown';
|
||
$guide = isset($guides[$type]) ? $guides[$type] : [
|
||
'title' => '通用安装指南',
|
||
'steps' => [
|
||
'1. 访问 Node.js 官网下载安装包:',
|
||
' https://nodejs.org/zh-cn/download/',
|
||
'2. 按照官方文档完成安装',
|
||
'3. 验证安装:',
|
||
' node -v && npm -v',
|
||
'4. 安装 miniprogram-ci:',
|
||
' npm install miniprogram-ci -g',
|
||
],
|
||
];
|
||
|
||
// 添加一键安装脚本 URL
|
||
$guide['script_url'] = $this->getInstallScriptUrl();
|
||
|
||
return $guide;
|
||
}
|
||
}
|