开源 C++ QT Widget 开发(十五)多媒体--音频播放

文章的目的为了记录使用C++ 进行QT Widget 开发学习的经历。临时学习,完成app的开发。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。

相关链接:

开源 C++ QT Widget 开发(一)工程文件结构-CSDN博客

开源 C++ QT Widget 开发(二)基本控件应用-CSDN博客

开源 C++ QT Widget 开发(三)图表--波形显示器-CSDN博客

开源 C++ QT Widget 开发(四)文件--二进制文件查看编辑-CSDN博客

开源 C++ QT Widget 开发(五)通讯--串口调试-CSDN博客

开源 C++ QT Widget 开发(六)通讯--TCP调试-CSDN博客

开源 C++ QT Widget 开发(七)线程--多线程及通讯-CSDN博客

开源 C++ QT Widget 开发(八)网络--Http文件下载-CSDN博客

开源 C++ QT Widget 开发(九)图表--仪表盘-CSDN博客

开源 C++ QT Widget 开发(十)IPC进程间通信--共享内存-CSDN博客

开源 C++ QT Widget 开发(十一)进程间通信--Windows 窗口通信-CSDN博客

开源 C++ QT Widget 开发(十二)图表--环境监测表盘-CSDN博客

开源 C++ QT Widget 开发(十三)IPC通讯--本地套接字 (Local Socket)

开源 C++ QT Widget 开发(十四)多媒体--录音机

开源 C++ QT Widget 开发(十五)多媒体--音频播放

推荐链接:

开源 java android app 开发(一)开发环境的搭建-CSDN博客

开源 java android app 开发(二)工程文件结构-CSDN博客

开源 java android app 开发(三)GUI界面布局和常用组件-CSDN博客

开源 java android app 开发(四)GUI界面重要组件-CSDN博客

开源 java android app 开发(五)文件和数据库存储-CSDN博客

开源 java android app 开发(六)多媒体使用-CSDN博客

开源 java android app 开发(七)通讯之Tcp和Http-CSDN博客

开源 java android app 开发(八)通讯之Mqtt和Ble-CSDN博客

开源 java android app 开发(九)后台之线程和服务-CSDN博客

开源 java android app 开发(十)广播机制-CSDN博客

开源 java android app 开发(十一)调试、发布-CSDN博客

开源 java android app 开发(十二)封库.aar-CSDN博客

推荐链接:

开源C# .net mvc 开发(一)WEB搭建_c#部署web程序-CSDN博客

开源 C# .net mvc 开发(二)网站快速搭建_c#网站开发-CSDN博客

开源 C# .net mvc 开发(三)WEB内外网访问(VS发布、IIS配置网站、花生壳外网穿刺访问)_c# mvc 域名下不可訪問內網,內網下可以訪問域名-CSDN博客

开源 C# .net mvc 开发(四)工程结构、页面提交以及显示_c#工程结构-CSDN博客

开源 C# .net mvc 开发(五)常用代码快速开发_c# mvc开发-CSDN博客

内容:Qt实现的WAV音频播放器应用程序。实现win10系统下,播放wav音频的功能。

目录:

1.功能介绍

2.核心代码分析

3.所有源码

4.显示效果

一.功能介绍

这是一个基于QMediaPlayer的音频播放器,主要功能包括:

音频播放控制:播放、停止、进度控制

文件检查:验证音频文件的存在和有效性

状态监控:实时显示播放状态和进度

错误处理:完善的错误检测和处理机制

日志系统:详细的运行日志记录

二、核心代码分析

头文件 (mainwindow.h)

复制代码
class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_playButton_clicked();                      // 播放按钮点击
    void updatePlaybackProgress();                     // 更新播放进度
    void handlePlayerStateChanged(QMediaPlayer::State state); // 播放状态变化
    void handlePlayerError(QMediaPlayer::Error error); // 错误处理

private:
    Ui::MainWindow *ui;
    QMediaPlayer *player;          // 媒体播放器核心
    QTimer *progressTimer;         // 进度更新计时器
    QString audioFilePath;         // 音频文件路径

    void logMessage(const QString &message); // 日志记录
    bool checkAudioFile();                   // 文件检查
};

函数功能详细分析

  1. 构造函数 MainWindow::MainWindow()

功能:初始化播放器界面和组件

复制代码
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
    , player(new QMediaPlayer(this))        // 创建媒体播放器
    , progressTimer(new QTimer(this))       // 创建进度计时器
{
    ui->setupUi(this);
    setWindowTitle("16K WAV音频播放器 - Qt5.14"); // 设置窗口标题

    audioFilePath = QDir::currentPath() + "/Output.wav"; // 设置默认文件路径
    ui->filePathLabel->setText("文件: " + audioFilePath); // 显示文件路径

    player->setVolume(80); // 设置默认音量(80%)

    // 信号槽连接
    connect(player, SIGNAL(stateChanged(QMediaPlayer::State)),
            this, SLOT(handlePlayerStateChanged(QMediaPlayer::State)));
    connect(player, SIGNAL(error(QMediaPlayer::Error)),
            this, SLOT(handlePlayerError(QMediaPlayer::Error)));
    connect(progressTimer, &QTimer::timeout, this, &MainWindow::updatePlaybackProgress);

    // UI初始化
    ui->playButton->setText("开始播放");
    ui->statusLabel->setText("就绪");
    ui->progressBar->setValue(0);

    // 文件检查
    if (checkAudioFile()) {
        logMessage("音频文件检查正常,准备就绪");
    } else {
        ui->playButton->setEnabled(false); // 文件无效时禁用播放按钮
    }
}
  1. 析构函数 MainWindow::~MainWindow()

功能:安全清理资源

复制代码
MainWindow::~MainWindow()
{
    if (player->state() == QMediaPlayer::PlayingState) {
        player->stop(); // 如果正在播放,先停止
    }
    delete ui;
}
  1. checkAudioFile() - 音频文件检查

功能:验证音频文件的存在和有效性

复制代码
bool MainWindow::checkAudioFile()
{
    QFileInfo fileInfo(audioFilePath);

    if (!fileInfo.exists()) {
        logMessage("错误:音频文件不存在 - " + audioFilePath);
        ui->statusLabel->setText("错误:文件不存在");
        return false;
    }

    if (fileInfo.size() == 0) {
        logMessage("警告:音频文件为空");
        ui->statusLabel->setText("警告:文件为空");
        return false;
    }

    logMessage(QString("找到音频文件:%1 (%2 字节)")
              .arg(audioFilePath)
              .arg(fileInfo.size()));
    return true;
}
  1. on_playButton_clicked() - 播放控制

功能:处理播放/停止按钮点击事件

复制代码
void MainWindow::on_playButton_clicked()
{
    if (player->state() == QMediaPlayer::PlayingState) {
        // 停止播放
        logMessage("用户停止播放");
        player->stop();
        progressTimer->stop();
        ui->playButton->setText("开始播放");
    } else {
        // 开始播放
        if (!checkAudioFile()) {
            logMessage("播放失败:音频文件不可用");
            return;
        }

        logMessage("开始播放音频文件...");
        player->setMedia(QUrl::fromLocalFile(audioFilePath)); // 设置媒体源

        if (player->mediaStatus() == QMediaPlayer::InvalidMedia) {
            logMessage("错误:无法加载媒体文件");
            ui->statusLabel->setText("错误:无效的媒体文件");
            return;
        }

        player->play(); // 开始播放
        progressTimer->start(100); // 启动进度更新计时器(100ms间隔)
        logMessage("播放器启动完成");
    }
}
  1. updatePlaybackProgress() - 进度更新

功能:实时更新播放进度显示

复制代码
void MainWindow::updatePlaybackProgress()
{
    if (player->duration() > 0) {
        qint64 position = player->position(); // 当前位置(毫秒)
        qint64 duration = player->duration(); // 总时长(毫秒)

        int seconds = position / 1000;        // 转换为秒
        int totalSeconds = duration / 1000;

        // 格式化时间显示 (MM:SS / MM:SS)
        ui->timeLabel->setText(
            QString("%1:%2 / %3:%4")
            .arg(seconds / 60, 2, 10, QLatin1Char('0'))  // 当前分钟
            .arg(seconds % 60, 2, 10, QLatin1Char('0'))  // 当前秒
            .arg(totalSeconds / 60, 2, 10, QLatin1Char('0')) // 总分钟
            .arg(totalSeconds % 60, 2, 10, QLatin1Char('0')) // 总秒
        );

        // 计算进度百分比
        int progress = (duration > 0) ? static_cast<int>(position * 100 / duration) : 0;
        ui->progressBar->setValue(progress);

        // 每秒记录一次进度日志(避免过于频繁)
        static int lastSecond = -1;
        if (seconds != lastSecond) {
            lastSecond = seconds;
            logMessage(QString("播放进度: %1% (%2/%3 秒)")
                      .arg(progress)
                      .arg(seconds)
                      .arg(totalSeconds));
        }
    }
}
  1. handlePlayerStateChanged() - 状态变化处理

功能:响应播放器状态变化

复制代码
void MainWindow::handlePlayerStateChanged(QMediaPlayer::State state)
{
    switch (state) {
    case QMediaPlayer::StoppedState:
        logMessage("播放停止");
        ui->statusLabel->setText("播放完成");
        ui->playButton->setText("开始播放");
        progressTimer->stop(); // 停止进度更新
        ui->progressBar->setValue(100); // 进度条置满
        break;

    case QMediaPlayer::PlayingState:
        logMessage("播放进行中...");
        ui->statusLabel->setText("正在播放");
        ui->playButton->setText("停止播放");
        logMessage(QString("媒体时长: %1 毫秒").arg(player->duration()));
        break;

    case QMediaPlayer::PausedState:
        logMessage("播放暂停");
        ui->statusLabel->setText("播放暂停");
        ui->playButton->setText("继续播放");
        break;
    }
}
  1. handlePlayerError() - 错误处理

功能:处理播放器错误

复制代码
void MainWindow::handlePlayerError(QMediaPlayer::Error error)
{
    QString errorMsg;
    switch (error) {
    case QMediaPlayer::NoError:
        return;
    case QMediaPlayer::ResourceError:
        errorMsg = "资源错误:无法访问媒体文件";
        break;
    case QMediaPlayer::FormatError:
        errorMsg = "格式错误:不支持的媒体格式"; // WAV格式可能不支持
        break;
    case QMediaPlayer::NetworkError:
        errorMsg = "网络错误";
        break;
    case QMediaPlayer::AccessDeniedError:
        errorMsg = "访问被拒绝:没有足够的权限";
        break;
    default:
        errorMsg = "未知错误";
        break;
    }

    logMessage(errorMsg);
    ui->statusLabel->setText("播放错误");
    ui->playButton->setText("开始播放");

    QMessageBox::warning(this, "播放错误", errorMsg); // 弹出错误对话框
}
  1. logMessage() - 日志记录

功能:统一的日志记录系统

复制代码
void MainWindow::logMessage(const QString &message)
{
    QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
    QString logEntry = QString("[%1] %2").arg(timestamp).arg(message);

    ui->logTextEdit->append(logEntry); // 添加到UI日志框
    qDebug() << logEntry;              // 输出到控制台

    // 自动滚动到日志底部
    QTextCursor cursor = ui->logTextEdit->textCursor();
    cursor.movePosition(QTextCursor::End);
    ui->logTextEdit->setTextCursor(cursor);
}

三、所有源码

  1. pro文件

    QT += core gui
    QT += core gui multimedia
    greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

    CONFIG += c++11

    The following define makes your compiler emit warnings if you use

    any Qt feature that has been marked deprecated (the exact warnings

    depend on your compiler). Please consult the documentation of the

    deprecated API in order to know how to port your code away from it.

    DEFINES += QT_DEPRECATED_WARNINGS

    You can also make your code fail to compile if it uses deprecated APIs.

    In order to do so, uncomment the following line.

    You can also select to disable deprecated APIs only up to a certain version of Qt.

    #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0

    SOURCES +=
    main.cpp
    mainwindow.cpp

    HEADERS +=
    mainwindow.h

    FORMS +=
    mainwindow.ui

    Default rules for deployment.

    qnx: target.path = /tmp/$${TARGET}/bin
    else: unix:!android: target.path = /opt/$${TARGET}/bin
    !isEmpty(target.path): INSTALLS += target

  2. mainwindow.h文件

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H

    #include <QMainWindow>
    #include <QMediaPlayer>
    #include <QTimer>
    #include <QFileInfo>

    QT_BEGIN_NAMESPACE
    namespace Ui {
    class MainWindow;
    }
    QT_END_NAMESPACE

    class MainWindow : public QMainWindow
    {
    Q_OBJECT

    public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

    private slots:
    void on_playButton_clicked();
    void updatePlaybackProgress();
    void handlePlayerStateChanged(QMediaPlayer::State state);
    void handlePlayerError(QMediaPlayer::Error error);

    private:
    Ui::MainWindow *ui;
    QMediaPlayer *player;
    QTimer *progressTimer;
    QString audioFilePath;

    复制代码
     void logMessage(const QString &message);
     bool checkAudioFile();

    };

    #endif // MAINWINDOW_H

3.mianwindow.cpp文件

复制代码
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDir>
#include <QDateTime>
#include <QFileInfo>
#include <QDebug>
#include <QMessageBox>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
    , player(new QMediaPlayer(this))
    , progressTimer(new QTimer(this))
{
    ui->setupUi(this);

    // 设置窗口标题
    setWindowTitle("16K WAV音频播放器 - Qt5.14");

    // 设置音频文件路径
    audioFilePath = QDir::currentPath() + "/Output.wav";
    ui->filePathLabel->setText("文件: " + audioFilePath);

    // 配置播放器
    player->setVolume(80); // 设置音量 0-100

    // 连接信号槽
    connect(player, SIGNAL(stateChanged(QMediaPlayer::State)),
            this, SLOT(handlePlayerStateChanged(QMediaPlayer::State)));
    connect(player, SIGNAL(error(QMediaPlayer::Error)),
            this, SLOT(handlePlayerError(QMediaPlayer::Error)));
    connect(progressTimer, &QTimer::timeout, this, &MainWindow::updatePlaybackProgress);

    // 初始化UI状态
    ui->playButton->setText("开始播放");
    ui->statusLabel->setText("就绪");
    ui->progressBar->setValue(0);

    // 检查音频文件
    if (checkAudioFile()) {
        logMessage("音频文件检查正常,准备就绪");
    } else {
        ui->playButton->setEnabled(false);
    }
}

MainWindow::~MainWindow()
{
    if (player->state() == QMediaPlayer::PlayingState) {
        player->stop();
    }
    delete ui;
}

bool MainWindow::checkAudioFile()
{
    QFileInfo fileInfo(audioFilePath);

    if (!fileInfo.exists()) {
        logMessage("错误:音频文件不存在 - " + audioFilePath);
        ui->statusLabel->setText("错误:文件不存在");
        return false;
    }

    if (fileInfo.size() == 0) {
        logMessage("警告:音频文件为空");
        ui->statusLabel->setText("警告:文件为空");
        return false;
    }

    logMessage(QString("找到音频文件:%1 (%2 字节)")
              .arg(audioFilePath)
              .arg(fileInfo.size()));
    return true;
}

void MainWindow::on_playButton_clicked()
{
    if (player->state() == QMediaPlayer::PlayingState) {
        // 停止播放
        logMessage("用户停止播放");
        player->stop();
        progressTimer->stop();
        ui->playButton->setText("开始播放");
    } else {
        // 开始播放
        if (!checkAudioFile()) {
            logMessage("播放失败:音频文件不可用");
            return;
        }

        logMessage("开始播放音频文件...");
        player->setMedia(QUrl::fromLocalFile(audioFilePath));

        if (player->mediaStatus() == QMediaPlayer::InvalidMedia) {
            logMessage("错误:无法加载媒体文件");
            ui->statusLabel->setText("错误:无效的媒体文件");
            return;
        }

        player->play();

        // 启动进度更新计时器
        progressTimer->start(100);
        logMessage("播放器启动完成");
    }
}

void MainWindow::updatePlaybackProgress()
{
    if (player->duration() > 0) {
        qint64 position = player->position();
        qint64 duration = player->duration();

        int seconds = position / 1000;
        int totalSeconds = duration / 1000;

        // 更新时间显示
        ui->timeLabel->setText(
            QString("%1:%2 / %3:%4")
            .arg(seconds / 60, 2, 10, QLatin1Char('0'))
            .arg(seconds % 60, 2, 10, QLatin1Char('0'))
            .arg(totalSeconds / 60, 2, 10, QLatin1Char('0'))
            .arg(totalSeconds % 60, 2, 10, QLatin1Char('0'))
        );

        // 更新进度条
        int progress = (duration > 0) ? static_cast<int>(position * 100 / duration) : 0;
        ui->progressBar->setValue(progress);

        // 记录播放进度
        static int lastSecond = -1;
        if (seconds != lastSecond) {
            lastSecond = seconds;
            logMessage(QString("播放进度: %1% (%2/%3 秒)")
                      .arg(progress)
                      .arg(seconds)
                      .arg(totalSeconds));
        }
    }
}

void MainWindow::handlePlayerStateChanged(QMediaPlayer::State state)
{
    switch (state) {
    case QMediaPlayer::StoppedState:
        logMessage("播放停止");
        ui->statusLabel->setText("播放完成");
        ui->playButton->setText("开始播放");
        progressTimer->stop();
        ui->progressBar->setValue(100);
        break;

    case QMediaPlayer::PlayingState:
        logMessage("播放进行中...");
        ui->statusLabel->setText("正在播放");
        ui->playButton->setText("停止播放");
        logMessage(QString("媒体时长: %1 毫秒").arg(player->duration()));
        break;

    case QMediaPlayer::PausedState:
        logMessage("播放暂停");
        ui->statusLabel->setText("播放暂停");
        ui->playButton->setText("继续播放");
        break;
    }
}

void MainWindow::handlePlayerError(QMediaPlayer::Error error)
{
    QString errorMsg;
    switch (error) {
    case QMediaPlayer::NoError:
        return;
    case QMediaPlayer::ResourceError:
        errorMsg = "资源错误:无法访问媒体文件";
        break;
    case QMediaPlayer::FormatError:
        errorMsg = "格式错误:不支持的媒体格式";
        break;
    case QMediaPlayer::NetworkError:
        errorMsg = "网络错误";
        break;
    case QMediaPlayer::AccessDeniedError:
        errorMsg = "访问被拒绝:没有足够的权限";
        break;
    default:
        errorMsg = "未知错误";
        break;
    }

    logMessage(errorMsg);
    ui->statusLabel->setText("播放错误");
    ui->playButton->setText("开始播放");

    QMessageBox::warning(this, "播放错误", errorMsg);
}

void MainWindow::logMessage(const QString &message)
{
    QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
    QString logEntry = QString("[%1] %2").arg(timestamp).arg(message);

    ui->logTextEdit->append(logEntry);
    qDebug() << logEntry;

    // 自动滚动到底部
    QTextCursor cursor = ui->logTextEdit->textCursor();
    cursor.movePosition(QTextCursor::End);
    ui->logTextEdit->setTextCursor(cursor);
}

4.mainwindow.ui文件

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>600</width>
    <height>500</height>
   </rect>
  </property>
  <property name="minimumSize">
   <size>
    <width>600</width>
    <height>500</height>
   </size>
  </property>
  <property name="windowTitle">
   <string>16K WAV音频播放器</string>
  </property>
  <widget class="QWidget" name="centralWidget">
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <widget class="QLabel" name="titleLabel">
      <property name="text">
       <string>16K WAV音频播放器 (Qt5.14)</string>
      </property>
      <property name="alignment">
       <set>Qt::AlignCenter</set>
      </property>
      <property name="font">
       <font>
        <pointsize>16</pointsize>
        <bold>true</bold>
       </font>
      </property>
     </widget>
    </item>
    <item>
     <widget class="QLabel" name="filePathLabel">
      <property name="text">
       <string>文件路径显示</string>
      </property>
      <property name="frameShape">
       <enum>QFrame::Panel</enum>
      </property>
      <property name="frameShadow">
       <enum>QFrame::Sunken</enum>
      </property>
      <property name="wordWrap">
       <bool>true</bool>
      </property>
     </widget>
    </item>
    <item>
     <widget class="QLabel" name="timeLabel">
      <property name="text">
       <string>00:00 / 00:00</string>
      </property>
      <property name="alignment">
       <set>Qt::AlignCenter</set>
      </property>
      <property name="font">
       <font>
        <pointsize>18</pointsize>
        <bold>true</bold>
       </font>
      </property>
     </widget>
    </item>
    <item>
     <widget class="QProgressBar" name="progressBar">
      <property name="value">
       <number>0</number>
      </property>
      <property name="textVisible">
       <bool>true</bool>
      </property>
      <property name="format">
       <string>%p%</string>
      </property>
     </widget>
    </item>
    <item>
     <widget class="QPushButton" name="playButton">
      <property name="text">
       <string>开始播放</string>
      </property>
      <property name="minimumSize">
       <size>
        <width>0</width>
        <height>50</height>
       </size>
      </property>
      <property name="styleSheet">
       <string>QPushButton {
    background-color: #4CAF50;
    color: white;
    font-weight: bold;
    font-size: 14px;
    border-radius: 5px;
    border: none;
}
QPushButton:hover {
    background-color: #45a049;
}
QPushButton:pressed {
    background-color: #3d8b40;
}
QPushButton:disabled {
    background-color: #cccccc;
}</string>
      </property>
     </widget>
    </item>
    <item>
     <widget class="QLabel" name="statusLabel">
      <property name="text">
       <string>就绪</string>
      </property>
      <property name="alignment">
       <set>Qt::AlignCenter</set>
      </property>
      <property name="frameShape">
       <enum>QFrame::Box</enum>
      </property>
      <property name="font">
       <font>
        <pointsize>10</pointsize>
        <bold>true</bold>
       </font>
      </property>
     </widget>
    </item>
    <item>
     <widget class="QGroupBox" name="groupBox">
      <property name="title">
       <string>播放日志</string>
      </property>
      <layout class="QVBoxLayout" name="verticalLayout_2">
       <item>
        <widget class="QTextEdit" name="logTextEdit">
         <property name="maximumHeight">
          <number>200</number>
         </property>
         <property name="readOnly">
          <bool>true</bool>
         </property>
         <property name="placeholderText">
          <string>播放日志将显示在这里...</string>
         </property>
        </widget>
       </item>
      </layout>
     </widget>
    </item>
   </layout>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>

四、显示效果

工程文件夹下需要有Output.wav,点击播放按钮则开始播放。

相关推荐
爱倒腾的老唐5 小时前
02、命令行的介绍
linux·bash
mahuifa5 小时前
C++(Qt)软件调试---Linux动态库链接异常排查(38)
linux·c++·动态库·ldd·异常排查
Juan_20125 小时前
P3051题解
c++·数学·算法·题解
深思慎考6 小时前
LinuxC++——etcd分布式键值存储系统API(libetcd-cpp-api3)下载与二次封装
linux·c++·etcd
Jiezcode6 小时前
LeetCode 138.随机链表的复制
数据结构·c++·算法·leetcode·链表
前方一片光明6 小时前
Linux—升级openssh常见的问题与解决方案
linux·运维·服务器
小-黯7 小时前
VSCode+QT开发环境配置
ide·vscode·qt
那个什么黑龙江7 小时前
关于C++中的“类中的特殊成员函数”
开发语言·c++
ajassi20007 小时前
开源 java android app 开发(十七)封库--混淆源码
android·java·开源
siriuuus7 小时前
Linux rsyslog 日志服务及日志转发实践
linux·rsyslog