This commit is contained in:
zimoyin
2026-04-09 13:55:39 +08:00
parent 13fd9c5f0a
commit 398e014128
12 changed files with 360 additions and 47 deletions
+2
View File
@@ -6,3 +6,5 @@
.env
/tests/tmp
/tests/.phpunit.result.cache
/ZLM/
/ffmpeg_cache/
+8 -2
View File
@@ -19,6 +19,10 @@ class Config
get => $this->errorLogRotationTimeByDay;
}
public bool $consoleLog {
get => $this->consoleLog;
}
public DatabaseConfig $database {
get => $this->database;
}
@@ -52,14 +56,14 @@ class Config
/**
* TCP Server 进程数量
*/
public int $tcpServerProcessNum = 1 {
public int $tcpServerProcessNum {
get => $this->tcpServerProcessNum;
}
/**
* TCP_SERVER_PORT
*/
public int $tcpServerPort = 50000 {
public int $tcpServerPort {
get => $this->tcpServerPort;
}
@@ -97,6 +101,7 @@ class Config
$this->database = new DatabaseConfig();
$this->dbDebug = self::getBoolEnv('DB_DEBUG');
$this->logFilter = self::getStringArrayEnv('LOG_FILTER', []);
$this->consoleLog = self::getBoolEnv('CONSOLE_LOG', false);
$this->logLevel = match (strtoupper(self::getStringEnv('LOG_LEVEL', 'DEBUG'))) {
'INFO' => \Monolog\Logger::INFO,
'WARNING' => \Monolog\Logger::WARNING,
@@ -116,6 +121,7 @@ class Config
$this->zlm = new ZLMConfig();
$this->personDetectionCallback = null;
$this->enableRequestTimeLog = self::getBoolEnv('ENABLE_REQUEST_TIME_LOG', false);
$this->tcpServerProcessNum = self::getIntEnv('TCP_SERVER_PROCESS_NUM', 1);
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' && $this->tcpServerProcessNum > 1) {
$this->tcpServerProcessNum = 1;
echo "Warning: TCP_SERVER_PROCESS_NUM set to 1 on Windows.\n";
+49 -17
View File
@@ -9,32 +9,64 @@ use support\Db;
class DatabaseConfig
{
public string $host = 'localhost' {
get {
return $this->host;
}
get => $this->host;
}
public string $username = 'root' {
get {
return $this->username;
}
get => $this->username;
}
public string $password = '' {
get {
return $this->password;
}
get => $this->password;
}
public string $database = 'opm_ectms' {
get {
return $this->database;
}
public string $database = 'ds2' {
get => $this->database;
}
public int $port = 3306{
get => $this->port;
}
public string $charset = 'utf8mb4'{
get => $this->charset;
}
//collation
public string $collation = 'utf8mb4_general_ci'{
get => $this->collation;
}
//driver
public string $driver = 'mysql'{
get => $this->driver;
}
public int $max_connections = 5{
get => $this->max_connections;
}
public int $min_connections = 1{
get => $this->min_connections;
}
public int $wait_timeout = 3{
get => $this->wait_timeout;
}
public int $idle_timeout = 60{
get => $this->idle_timeout;
}
public int $heartbeat_interval = 50{
get => $this->heartbeat_interval;
}
public function __construct()
{
$this->host = getenv('DB_HOST');
$this->username = getenv('DB_USER');
$this->password = getenv('DB_PASSWORD');
$this->database = getenv('DB_NAME');
$this->host = getenv('DB_HOST', $this->host);
$this->username = getenv('DB_USER', $this->username);
$this->password = getenv('DB_PASSWORD', $this->password);
$this->database = getenv('DB_NAME', $this->database);
$this->port = getenv('DB_PORT', $this->port);
$this->charset = getenv('DB_CHARSET', $this->charset);
$this->collation = getenv('DB_COLLATION', $this->collation);
$this->driver = getenv('DB_DRIVER', $this->driver);
$this->max_connections = getenv('DB_MAX_CONNECTIONS', $this->max_connections);
$this->min_connections = getenv('DB_MIN_CONNECTIONS', $this->min_connections);
$this->wait_timeout = getenv('DB_WAIT_TIMEOUT', $this->wait_timeout);
$this->idle_timeout = getenv('DB_IDLE_TIMEOUT', $this->idle_timeout);
$this->heartbeat_interval = getenv('DB_HEARTBEAT_INTERVAL', $this->heartbeat_interval);
}
/**
+5 -5
View File
@@ -7,11 +7,11 @@ return [
'connections' => [
'mysql' => [
'driver' => 'mysql',
'host' => Config::getStringEnv('DB_HOST', 'localhost'),
'port' => Config::getIntEnv('DB_PORT',3306),
'database' => Config::getStringEnv('DB_NAME', 'opm_ectms'),
'username' => Config::getStringEnv('DB_USER', 'root'),
'password' => Config::getStringEnv('DB_PASSWORD'),
'host' => Config::getInstance()->database->host,
'port' => Config::getInstance()->database->port,
'database' => Config::getInstance()->database->database,
'username' => Config::getInstance()->database->username,
'password' => Config::getInstance()->database->password,
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_general_ci',
'prefix' => '',
+1 -1
View File
@@ -15,7 +15,7 @@ return [
[
'class' => StreamHandler::class,
'constructor' => [
'php://stdout',
Config::getInstance()->consoleLog ? 'php://stdout' : '/dev/null',
Config::getInstance()->logLevel,
],
'formatter' => [
+103
View File
@@ -0,0 +1,103 @@
@echo off
setlocal enabledelayedexpansion
chcp 65001 >nul
title RTMP推流工具 - 图片幻灯片推流(带时间戳)
:: ================== 配置区域 ==================
set "PICTURE_DIR=C:\Users\zimoa\Pictures" :: 图片目录
set "STREAM_URL=rtmp://127.0.0.1/live/stream" :: RTMP推流地址
set "DURATION_PER_IMAGE=5" :: 每张图片显示秒数
set "WIDTH=1280" :: 输出宽度
set "HEIGHT=720" :: 输出高度
set "VIDEO_BITRATE=3000k" :: 视频码率
set "AUDIO_BITRATE=160k" :: 音频码率
:: 时间显示配置
set "FONT_FILE=C\:/Windows/Fonts/Arial.ttf" :: 字体文件路径(Windows 常见字体)
set "FONT_SIZE=24" :: 字体大小
set "FONT_COLOR=white" :: 字体颜色
set "TIME_X=10" :: 水平位置(像素)
set "TIME_Y=10" :: 垂直位置(像素)
:: =============================================
:: 获取脚本所在目录
set "SCRIPT_DIR=%~dp0"
set "CACHE_DIR=%SCRIPT_DIR%ffmpeg_cache"
set "LIST_FILE=%CACHE_DIR%\slideshow_list.txt"
:: 创建缓存目录(如果不存在)
if not exist "%CACHE_DIR%" mkdir "%CACHE_DIR%"
:: 检查FFmpeg是否可用
ffmpeg -version >nul 2>&1
if %errorlevel% neq 0 (
echo [错误] 未找到FFmpeg,请确保已添加到系统环境变量!
pause
exit /b
)
:: 检查图片目录是否存在
if not exist "%PICTURE_DIR%" (
echo [错误] 目录不存在: %PICTURE_DIR%
pause
exit /b
)
:: 如果缓存列表文件不存在,则生成(只生成一次,自动跳过无效图片)
if not exist "%LIST_FILE%" (
echo [信息] 未找到缓存列表,正在生成(首次运行,将跳过无效图片)...
echo [信息] 扫描图片目录: %PICTURE_DIR%
pushd "%PICTURE_DIR%" 2>nul || (
echo [错误] 无法进入目录: %PICTURE_DIR%
pause
exit /b
)
set "TEMP_LIST=%CACHE_DIR%\temp_list_%RANDOM%.txt"
set "VALID_COUNT=0"
for %%f in (*.jpg *.jpeg *.png *.bmp *.gif *.webp) do (
:: 使用 ffprobe 检查图片有效性
ffprobe -v error -select_streams v:0 -show_entries stream=codec_type -of default=noprint_wrappers=1:nokey=1 "%%f" >nul 2>&1
if !errorlevel! equ 0 (
echo file '%%~ff' >> "!TEMP_LIST!"
echo duration %DURATION_PER_IMAGE% >> "!TEMP_LIST!"
set /a VALID_COUNT+=1
) else (
echo [警告] 跳过无效或损坏的图片: %%f
)
)
popd
if !VALID_COUNT! equ 0 (
echo [错误] 没有找到任何有效的图片文件!
del "!TEMP_LIST!" 2>nul
pause
exit /b
)
move /y "!TEMP_LIST!" "%LIST_FILE%" >nul
echo [信息] 已生成缓存列表: %LIST_FILE%
echo [信息] 共找到 !VALID_COUNT! 张有效图片
) else (
echo [信息] 使用已有缓存列表: %LIST_FILE%
for /f "tokens=2 delims= " %%a in ('find /c "file " "%LIST_FILE%"') do set "IMG_COUNT=%%a"
echo [信息] 缓存中包含 %IMG_COUNT% 张有效图片(如需重新扫描请手动删除 %LIST_FILE%
)
echo [信息] 每张图片显示 %DURATION_PER_IMAGE%
echo [信息] 正在启动RTMP推流(幻灯片循环播放,画面叠加当前时间)...
echo [提示] 按 Ctrl+C 可停止推流
:: 执行推流(添加 drawtext 显示当前时间)
ffmpeg -stream_loop -1 -f concat -safe 0 -i "%LIST_FILE%" ^
-f lavfi -i "aevalsrc=sin(440*2*PI*t):sample_rate=44100" ^
-vf "scale=%WIDTH%:%HEIGHT%:force_original_aspect_ratio=1,pad=%WIDTH%:%HEIGHT%:(ow-iw)/2:(oh-ih)/2,fps=30,format=yuv420p,drawtext=fontfile='%FONT_FILE%':text='%%{localtime\:%%Y-%%m-%%d %%H\:%%M\:%%S}':x=%TIME_X%:y=%TIME_Y%:fontsize=%FONT_SIZE%:fontcolor=%FONT_COLOR%:shadowx=2:shadowy=2" ^
-c:v libx264 -preset veryfast -maxrate %VIDEO_BITRATE% -bufsize 6000k ^
-g 60 -c:a aac -b:a %AUDIO_BITRATE% -ar 44100 ^
-f flv "%STREAM_URL%"
echo [信息] 推流已停止。
pause
+160
View File
@@ -0,0 +1,160 @@
@echo off
setlocal enabledelayedexpansion
:: 定义基础变量
set "BUILD_DIR=build"
set "OPM_ECTMS=opm_ectms"
set "WEBMAN_BIN=webman.bin"
@REM set "PHP_PATH=php" :: 默认值(系统环境变量中的 PHP)
set "PHP_PATH=D:\AppData\sdk\php-8.4\php.exe" :: 默认值(系统环境变量中的 PHP)
:: 关键修复:处理 PowerShell 传递的参数(自动清理引号、兼容特殊路径)
if not "%~1"=="" (
:: 接收传入参数,自动去除 PowerShell 可能添加的单/双引号
set "INPUT_PHP=%~1"
set "INPUT_PHP=!INPUT_PHP:'=!" :: 去除单引号
set "INPUT_PHP=!INPUT_PHP:"=!" :: 去除双引号
set "PHP_PATH=!INPUT_PHP!"
:: 验证 PHP 路径是否存在(兼容带空格/特殊字符的路径)
if not exist "!PHP_PATH!" (
echo ❌ 错误:传入的 PHP 路径不存在!实际路径:!PHP_PATH!
pause
exit /b 1
)
echo ℹ️ 已使用传入的 PHP 路径:!PHP_PATH!
) else (
echo ℹ️ 未传入 PHP 路径,使用系统默认 PHP(需添加到环境变量)
)
:: 切换到脚本所在目录(兼容 PowerShell 中调用的路径问题)
cd /d "%~dp0" 2>nul || (
echo ❌ 错误:无法切换到脚本所在目录,请检查脚本路径是否正确!
pause
exit /b 1
)
:: 检查并创建 build 文件夹
if not exist "%BUILD_DIR%" (
echo ⚠️ 未找到 build 目录,正在自动创建...
md "%BUILD_DIR%" 2>nul || (
echo ❌ 错误:build 目录创建失败,请检查权限!
pause
exit /b 1
)
echo ✅ build 目录创建成功!
)
:: 1. 清理旧文件
echo.
echo 1 🧹 清理旧文件...
if exist "%BUILD_DIR%\%WEBMAN_BIN%" (
del /f /q "%BUILD_DIR%\%WEBMAN_BIN%" 2>nul && (
echo ✅ 已删除旧的 %WEBMAN_BIN% 文件
) || (
echo ⚠️ 警告:无法删除旧的 %WEBMAN_BIN% 文件(可能被占用)
)
) else (
echo ️ 未找到旧的 %WEBMAN_BIN% 文件,无需清理
)
:: 2. 运行 PHP 构建命令(核心修复:用 call 确保 errorlevel 正确传递)
echo.
echo 2 🔧 执行构建命令:"!PHP_PATH!" -d phar.readonly=0 ./webman build:bin
echo ℹ️ 当前使用的 PHP 路径:!PHP_PATH!
call "!PHP_PATH!" -d phar.readonly=0 ./webman build:bin
:: 关键修复:用更兼容的 errorlevel 判断方式(避免 PowerShell 解析冲突)
if errorlevel 1 (
echo.
echo ❌ 错误:PHP 构建命令执行失败!
echo ️ 可能原因:
echo 1. PHP 路径不正确(当前路径:!PHP_PATH!)
echo 2. PHP 未添加到系统环境变量(未传入路径时)
echo 3. webman 脚本不存在或项目依赖缺失
echo 4. 缺少 phar 扩展或权限不足
pause
exit /b 1
)
:: 检查构建结果
if not exist "%BUILD_DIR%\%WEBMAN_BIN%" (
echo.
echo ❌ 错误:构建失败!未生成 %WEBMAN_BIN% 文件
pause
exit /b 1
)
echo ✅ 构建成功,已生成 %WEBMAN_BIN% 文件
:: 3. 备份旧文件
echo.
echo 3 🔧 备份旧的 %OPM_ECTMS% 文件...
if exist "%BUILD_DIR%\%OPM_ECTMS%" (
:: 获取文件修改时间(兼容 PowerShell 中的日期格式)
for /f "delims=" %%a in ('powershell -Command "(Get-Item -Path '%BUILD_DIR%\%OPM_ECTMS%' -ErrorAction SilentlyContinue).LastWriteTime.ToString('yyyyMMdd-HH')"') do (
set "FILE_MOD_TIME=%%a"
)
:: 处理时间获取失败的情况
if "!FILE_MOD_TIME!"=="" set "FILE_MOD_TIME=%date:~0,4%%date:~5,2%%date:~8,2%-%time:~0,2%"
set "OLD_OPM_ECTMS=%OPM_ECTMS%.!FILE_MOD_TIME!"
:: 避免备份文件重名
if exist "%BUILD_DIR%\!OLD_OPM_ECTMS!" set "OLD_OPM_ECTMS=!OLD_OPM_ECTMS!.bak"
:: 执行备份(兼容带空格的文件名)
ren "%BUILD_DIR%\%OPM_ECTMS%" "!OLD_OPM_ECTMS!" 2>nul && (
echo ✅ 旧文件已备份为:!OLD_OPM_ECTMS!
) || (
echo ⚠️ 警告:旧文件备份失败(可能被占用),将直接覆盖!
)
) else (
echo ️ 未找到旧的 %OPM_ECTMS% 文件,无需备份
)
:: 4. 重命名文件
echo.
echo 4 🔄 重命名文件:%WEBMAN_BIN% -> %OPM_ECTMS%
ren "%BUILD_DIR%\%WEBMAN_BIN%" "%OPM_ECTMS%" 2>nul && (
echo ✅ 重命名成功!
) || (
echo ❌ 错误:重命名失败(目标文件可能被占用)
pause
exit /b 1
)
:: 5. 权限提示(Windows 环境)
echo.
echo 5 🔑 配置文件权限...
echo ✅ Windows 环境下可执行权限已默认启用
echo ️ 文件路径:%BUILD_DIR%\%OPM_ECTMS%
:: 6. 计算 SHA256 哈希(兼容 PowerShell 中的文件路径)
echo.
echo 6 📊 计算 %OPM_ECTMS% 的 SHA256 哈希值...
set "HASH_VALUE="
for /f "delims=" %%a in ('powershell -Command "(Get-FileHash -Path '%BUILD_DIR%\%OPM_ECTMS%' -Algorithm SHA256 -ErrorAction SilentlyContinue).Hash"') do (
set "HASH_VALUE=%%a"
)
if "!HASH_VALUE!"=="" (
echo ⚠️ 警告:哈希值计算失败(文件可能不存在或无权限)
) else (
echo ✅ 哈希值计算完成:
echo SHA256: !HASH_VALUE!
)
:: (可选)保存哈希值到文件
:: if "!HASH_VALUE!" neq "" (
:: echo !HASH_VALUE! > "%BUILD_DIR%\%OPM_ECTMS%.sha256"
:: echo ️ 哈希值已保存到:%BUILD_DIR%\%OPM_ECTMS%.sha256
:: )
:: 最终结果展示
echo.
echo.
echo ==============================================
echo ✅ 所有操作已全部执行完毕!
echo 📁 最终文件位置:%BUILD_DIR%\%OPM_ECTMS%
echo 📊 文件 SHA256!HASH_VALUE!
echo 🖥️ 使用的 PHP 路径:!PHP_PATH!
echo ==============================================
pause
@@ -50,6 +50,21 @@ class DeviceController extends Crud
return raw_view('device/update');
}
/**
* 设备下拉选项(可按 hospital_id 筛选)
*/
public function options(Request $request): Response
{
$query = $this->repository->scopedQuery();
if ($hospitalId = $request->get('hospital_id')) {
$query->where('hospital_id', $hospitalId);
}
$data = $query->select(['id', 'name'])->get()
->map(fn($item) => ['id' => $item->id, 'name' => $item->name])
->toArray();
return json(['code' => 0, 'msg' => 'ok', 'data' => $data]);
}
protected function afterQuery($items)
{
$statusMap = [1 => '正常', 2 => '离线', 3 => '报警'];
@@ -129,12 +129,13 @@ class DeviceStatusRepository extends BaseRepository
}
/**
* 获取设备所有周期列表(操作记录)
* 获取设备周期列表(操作记录,限制最多200条避免数据过大
*/
public function getAllCyclesByDevice(int $deviceId): \Illuminate\Database\Eloquent\Collection
public function getAllCyclesByDevice(int $deviceId, int $limit = 200): \Illuminate\Database\Eloquent\Collection
{
return OpmDsCycle::where('device_id', $deviceId)
->orderBy('id', 'desc')
->limit($limit)
->get();
}
}
@@ -127,15 +127,17 @@ class DeviceStatusService extends BaseService
$deviceId = $this->resolveDeviceId($deviceId);
if (!$deviceId) return [];
// 先查一次设备名称,避免 N+1
$device = OpmDsDevice::find($deviceId);
$deviceName = $device->name ?? '';
$cycles = $this->repository->getAllCyclesByDevice($deviceId);
return $cycles->map(function ($item) {
$device = OpmDsDevice::find($item->device_id);
return $cycles->map(function ($item) use ($deviceName) {
return [
'id' => $item->id,
'operation' => $item->operation,
'name' => $device->name ?? '',
'name' => $deviceName,
'created_at' => $item->created_at,
'start_time' => $item->start_time,
'pack' => $item->pack,
+8 -5
View File
@@ -1,14 +1,17 @@
<?php
use app\config\Config;
return [
'default' => 'mysql',
'connections' => [
'mysql' => [
'driver' => 'mysql',
'host' => '127.0.0.1',
'port' => '3306',
'database' => 'ds2',
'username' => 'root',
'password' => 'user',
'host' => Config::getInstance()->database->host,
'port' => Config::getInstance()->database->port,
'database' => Config::getInstance()->database->database,
'username' => Config::getInstance()->database->username,
'password' => Config::getInstance()->database->password,
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_general_ci',
'prefix' => '',
-11
View File
@@ -1,11 +0,0 @@
访问某个接口或者页面
先经过 AccessData 表,如果可以的话,就走到 AccessDataControl
AccessDataControl 用于获取账户的数据权限列表,写入到