Forráskód Böngészése

V1.2.1
1、修复了内存泄漏的bug
2、修改了线程退出的检查

Apple 3 hete
szülő
commit
e1c7415bf1
37 módosított fájl, 392 hozzáadás és 187 törlés
  1. 44 31
      RTPServer/RtpOneRoadThread.cpp
  2. 5 1
      RTPServer/RtpOneRoadThread.h
  3. 1 1
      RTPServer/RtpServer.cpp
  4. 1 1
      RTPServer/RtpServer.h
  5. 1 1
      SQL/ACAServer.sql
  6. 44 31
      SQL/CreateTable-GBase.sqlbook
  7. 15 12
      Server/ACAServer.cpp
  8. 1 1
      Server/ACAServer.h
  9. 10 10
      Server/ThreadCalculate/BaseCalculateThread.cpp
  10. 4 4
      Server/ThreadCalculate/BaseCalculateThread.h
  11. 38 27
      Server/ThreadCalculate/CompareItemThread.cpp
  12. 6 3
      Server/ThreadCalculate/CompareItemThread.h
  13. 2 1
      Server/ThreadCalculate/ConsistencyCompareThread.cpp
  14. 2 0
      Server/ThreadCalculate/ConsistencyCompareThread.h
  15. 1 6
      Server/ThreadCalculate/NoiseDetectThread.cpp
  16. 2 2
      Server/ThreadManager/ThreadCompareItemManager.cpp
  17. 41 24
      Server/ThreadManager/ThreadManager.cpp
  18. 6 2
      Server/ThreadManager/ThreadManager.h
  19. 16 1
      Server/ThreadRecord/AssignSrcDataThread.cpp
  20. 3 1
      Server/ThreadRecord/AssignSrcDataThread.h
  21. 13 0
      Server/ThreadRecord/AudioRecord/AudioRecord.cpp
  22. 2 0
      Server/ThreadRecord/AudioRecord/AudioRecord.h
  23. 26 2
      Server/ThreadRecord/BaseRecordThread.cpp
  24. 5 2
      Server/ThreadRecord/BaseRecordThread.h
  25. 24 0
      Server/ThreadRecord/CreateDBThread.cpp
  26. 4 0
      Server/ThreadRecord/CreateDBThread.h
  27. 4 8
      Server/ThreadRecord/CreateRecordFileThread.cpp
  28. 3 7
      Server/ThreadRecord/CreateWAVThread.cpp
  29. 1 0
      Server/ThreadRecord/CreateWAVThread.h
  30. 8 6
      Server/ThreadRecord/RecordThread.cpp
  31. 1 0
      Server/ThreadRecord/RecordThread.h
  32. 1 1
      Server/main.cpp
  33. 21 1
      show1/widget.cpp
  34. 6 0
      show1/widget.h
  35. 0 0
      文档/ACA服务程序设计说明.md
  36. 30 0
      文档/debian创建ftp服务.md
  37. 0 0
      文档/旧程序说明.md

+ 44 - 31
RTPServer/RtpOneRoadThread.cpp

@@ -9,8 +9,8 @@ RTPOneRoadThread::RTPOneRoadThread(RecordThreadInfo_t& threadInfo)
     : BaseRecordThread(threadInfo),
       m_eventLoop()
 {
-    m_logger = spdlog::get("RTPServer");
-    if(m_logger == nullptr)
+    m_logger_local = spdlog::get("RTPServer");
+    if(m_logger_local == nullptr)
     {
         fmt::print("RTPServer 日志记录器未初始化,请先初始化日志记录器");
         return;
@@ -28,18 +28,11 @@ RTPOneRoadThread::~RTPOneRoadThread()
 /**
  * @brief 停止线程
  */
-void RTPOneRoadThread::stopThread()
+void RTPOneRoadThread::thread_stop()
 {
-    if(m_sendTimer.isActive())
-    {
-        m_sendTimer.stop();
-    }
-    if(m_eventLoop.isRunning())
-    {
-        m_eventLoop.quit();
-        m_eventLoop.processEvents(QEventLoop::AllEvents, 10);
-    }
-    while(m_isRunning.load())
+    m_isRunning.store(false);
+    /* 等待事件循环停止 */
+    while(m_isStoped.load())
     {
         std::this_thread::sleep_for(std::chrono::milliseconds(10));
     }
@@ -63,7 +56,7 @@ bool RTPOneRoadThread::setData(const AudioSrcData& srcData)
     auto oldData = m_ringQueue.push_pop(pData);
     if(oldData != nullptr)
     {
-        SPDLOG_LOGGER_ERROR(m_logger, "{} 环形队列已满,无法添加新的音频数据", m_logBase);
+        SPDLOG_LOGGER_ERROR(m_logger_local, "{} 环形队列已满,无法添加新的音频数据", m_logBase);
         delete oldData; // 删除被覆盖的数据
         oldData = nullptr;
     }
@@ -79,11 +72,11 @@ bool RTPOneRoadThread::addUdpSession(const RtpSendClientInfo_t& udpSession)
     {
         if(session.clientIP == udpSession.clientIP && session.clientPort == udpSession.clientPort)
         {
-            SPDLOG_LOGGER_DEBUG(m_logger, "{} 已存在相同的UDP会话: {}:{}", m_logBase, udpSession.clientIP.toStdString(), udpSession.clientPort);
+            SPDLOG_LOGGER_DEBUG(m_logger_local, "{} 已存在相同的UDP会话: {}:{}", m_logBase, udpSession.clientIP.toStdString(), udpSession.clientPort);
             return false;
         }
     }
-    SPDLOG_LOGGER_INFO(m_logger, "{} 添加UDP会话: {}:{}", m_logBase, udpSession.clientIP.toStdString(), udpSession.clientPort);
+    SPDLOG_LOGGER_INFO(m_logger_local, "{} 添加UDP会话: {}:{}", m_logBase, udpSession.clientIP.toStdString(), udpSession.clientPort);
 
     /* 绑定UDP本地端口 */
     if(m_udpState == eUDPState::eUDP_None)
@@ -115,7 +108,7 @@ bool RTPOneRoadThread::removeUdpSession(QString clientIP, quint16 clientPort)
             //     delete it->udpSocket;
             //     it->udpSocket = nullptr;
             // }
-            SPDLOG_LOGGER_INFO(m_logger, "{} 删除UDP会话: {}:{}", m_logBase, it->clientIP.toStdString(), it->clientPort);
+            SPDLOG_LOGGER_INFO(m_logger_local, "{} 删除UDP会话: {}:{}", m_logBase, it->clientIP.toStdString(), it->clientPort);
             // 从列表中删除该会话
             m_listClients.erase(it);
             break;
@@ -136,7 +129,7 @@ bool RTPOneRoadThread::removeUdpSession(QString clientIP, quint16 clientPort)
         }
         m_udpState = eUDPState::eUDP_Closed;
         
-        SPDLOG_LOGGER_DEBUG(m_logger, "{} UDP会话列表为空,不再接收音频原始数据", m_logBase);
+        SPDLOG_LOGGER_DEBUG(m_logger_local, "{} UDP会话列表为空,不再接收音频原始数据", m_logBase);
 
         /* 触发一次定时器槽函数,执行处理UDP状态的函数 */
         emit signal_timerSendData();
@@ -148,10 +141,10 @@ bool RTPOneRoadThread::removeUdpSession(QString clientIP, quint16 clientPort)
 /* 发送数据的线程函数 */
 void RTPOneRoadThread::task()
 {
-    SPDLOG_LOGGER_INFO(m_logger, "➢ {} 开启RTP发送数据线程 ", m_logBase);
+    SPDLOG_LOGGER_INFO(m_logger_local, "➢ {} 开启RTP发送数据线程 ", m_logBase);
     if(!initData())
     {
-        SPDLOG_LOGGER_ERROR(m_logger, "{} 初始化数据失败", m_logBase);
+        SPDLOG_LOGGER_ERROR(m_logger_local, "{} 初始化数据失败", m_logBase);
         return;
     }
 
@@ -163,12 +156,14 @@ void RTPOneRoadThread::task()
     m_sendTimer.start();
 
     m_isRunning.store(true); // 设置为运行状态
+    m_isStoped.store(false);
     /* 将线程控制权交给Qt的事件循环 */
     m_eventLoop.exec();
     /* 清空数据 */
     clearData();
     m_isRunning.store(false);
-    SPDLOG_LOGGER_WARN(m_logger, "➢ {} RTP发送数据线程结束 ", m_logBase);
+    m_isStoped.store(true);
+    SPDLOG_LOGGER_WARN(m_logger_local, "➢ {} RTP发送数据线程结束 ", m_logBase);
 }
 
 
@@ -220,7 +215,7 @@ bool RTPOneRoadThread::processUdpState()
         m_udpSocket = new QUdpSocket();
         if(m_udpSocket == nullptr)
         {
-            SPDLOG_LOGGER_ERROR(m_logger, "{} 创建UDP套接字失败", m_logBase);
+            SPDLOG_LOGGER_ERROR(m_logger_local, "{} 创建UDP套接字失败", m_logBase);
             return false;
         }
         m_udpSocket->setSocketOption(QAbstractSocket::LowDelayOption, 1); // 设置低延迟选项
@@ -231,10 +226,10 @@ bool RTPOneRoadThread::processUdpState()
     {
         if(!m_udpSocket->bind(QHostAddress(m_localIP)))
         {
-            SPDLOG_LOGGER_ERROR(m_logger, "{} 绑定UDP套接字失败: {}:{}", m_logBase, m_localIP.toStdString(), m_udpSocket->localPort());
+            SPDLOG_LOGGER_ERROR(m_logger_local, "{} 绑定UDP套接字失败: {}:{}", m_logBase, m_localIP.toStdString(), m_udpSocket->localPort());
             return false;
         }
-        SPDLOG_LOGGER_INFO(m_logger, "{} 绑定UDP套接字到本地IP: {}:{}", m_logBase, 
+        SPDLOG_LOGGER_INFO(m_logger_local, "{} 绑定UDP套接字到本地IP: {}:{}", m_logBase, 
                             m_udpSocket->localAddress().toString().toStdString(), m_udpSocket->localPort());
         /* 设置为接收数据状态 */
         m_isRecvData.store(true);
@@ -282,7 +277,7 @@ bool RTPOneRoadThread::sendData()
 {
     while(m_ringQueue.QueueSize() > 0)
     {
-        // SPDLOG_LOGGER_TRACE(m_logger, "{} 发送音频数据", m_logBase);
+        // SPDLOG_LOGGER_TRACE(m_logger_local, "{} 发送音频数据", m_logBase);
         auto pData = m_ringQueue.front_pop_noBlock();
         if(pData == nullptr)
         {
@@ -304,10 +299,10 @@ bool RTPOneRoadThread::sendData()
                                                                 QHostAddress(session.clientIP), session.clientPort);
             if(bytesSent == -1)
             {
-                SPDLOG_LOGGER_ERROR(m_logger, "{} 发送数据失败: {}:{}", m_logBase, session.clientIP.toStdString(), session.clientPort);
+                SPDLOG_LOGGER_ERROR(m_logger_local, "{} 发送数据失败: {}:{}", m_logBase, session.clientIP.toStdString(), session.clientPort);
             } else
             {
-            //     SPDLOG_LOGGER_TRACE(m_logger, "{} 发送数据成功: {}:{}, 本地IP: {}:{}, 大小: {}", m_logBase, 
+            //     SPDLOG_LOGGER_TRACE(m_logger_local, "{} 发送数据成功: {}:{}, 本地IP: {}:{}, 大小: {}", m_logBase, 
             //             session.clientIP.toStdString(), session.clientPort, 
             //             m_udpSocket->localAddress().toString().toStdString(), m_udpSocket->localPort(), bytesSent);
             }
@@ -320,9 +315,27 @@ bool RTPOneRoadThread::sendData()
     return true;
 }
 
+/* 停止线程 */
+void RTPOneRoadThread::stop_thread()
+{
+    if(m_sendTimer.isActive())
+    {
+        m_sendTimer.stop();
+    }
+    if(m_eventLoop.isRunning())
+    {
+        m_eventLoop.quit();
+        m_eventLoop.processEvents(QEventLoop::AllEvents, 10);
+    }
+}
+
 /* 定时发送数据 */
 void RTPOneRoadThread::do_timerSendData()
 {
+    if(m_isRunning.load() == false)
+    {
+        stop_thread();
+    }
     if(m_udpState == eUDPState::eUDP_None)
     {
         return;
@@ -339,15 +352,15 @@ void RTPOneRoadThread::do_timerSendData()
 void RTPOneRoadThread::do_udpError(QAbstractSocket::SocketError socketError)
 {
     auto senderSocket = qobject_cast<QUdpSocket*>(sender());
-    // SPDLOG_LOGGER_ERROR(m_logger, "{} UDP套接字错误: {}", m_logBase, static_cast<int>(socketError));
+    // SPDLOG_LOGGER_ERROR(m_logger_local, "{} UDP套接字错误: {}", m_logBase, static_cast<int>(socketError));
     /* 这里可以根据需要处理不同的错误 */
     if(socketError == QAbstractSocket::SocketError::SocketTimeoutError)
     {
-        SPDLOG_LOGGER_WARN(m_logger, "{} UDP连接超时", m_logBase);
+        SPDLOG_LOGGER_WARN(m_logger_local, "{} UDP连接超时", m_logBase);
     } else
     {
-        SPDLOG_LOGGER_ERROR(m_logger, "{} UDP套接字发生错误: {}", m_logBase, static_cast<int>(socketError));
-        SPDLOG_LOGGER_ERROR(m_logger, "{} 错误信息: {}", m_logBase, senderSocket->errorString().toStdString());
+        SPDLOG_LOGGER_ERROR(m_logger_local, "{} UDP套接字发生错误: {}", m_logBase, static_cast<int>(socketError));
+        SPDLOG_LOGGER_ERROR(m_logger_local, "{} 错误信息: {}", m_logBase, senderSocket->errorString().toStdString());
     }
     
     m_udpState = eUDPState::eUDP_Error;

+ 5 - 1
RTPServer/RtpOneRoadThread.h

@@ -38,7 +38,7 @@ public:
 
 
     /* 停止线程 */
-    void stopThread() override;
+    void thread_stop() override;
 
     /* 设置数据 */
     bool setData(const AudioSrcData& srcData) override;
@@ -66,6 +66,9 @@ protected:
     /* 发送数据 */
     bool sendData();
 
+    /* 停止线程 */
+    void stop_thread();
+
 private slots:
     /* 定时发送数据 */
     void do_timerSendData();
@@ -74,6 +77,7 @@ private slots:
     
 
 private:
+    std::shared_ptr<spdlog::logger> m_logger_local = nullptr;  /* 日志记录器,这里使用独立的logger */
     /* 事件循环 */
     QEventLoop m_eventLoop;
     QTimer m_sendTimer;                             /* 定时器,用于定时发送数据 */

+ 1 - 1
RTPServer/RtpServer.cpp

@@ -81,7 +81,7 @@ bool RTPServer::thread_task(int port)
 }
 
 /* 停止线程 */
-void RTPServer::stopThreadBlock()
+void RTPServer::thread_stopBlock()
 {
     if(m_eventLoop.isRunning())
     {

+ 1 - 1
RTPServer/RtpServer.h

@@ -39,7 +39,7 @@ public:
      */
     bool thread_task(int port);
     /* 停止线程 */
-    void stopThreadBlock();
+    void thread_stopBlock();
 
 
 private slots:

+ 1 - 1
SQL/ACAServer.sql

@@ -1,4 +1,4 @@
--- Active: 1752920752747@@192.1.2.61@5236@EQM_CESHI
+-- Active: 1752718919967@@192.1.2.61@5236@EQM_CESHI
 
 
 #对比项

+ 44 - 31
SQL/CreateTable-GBase.sqlbook

@@ -5,21 +5,21 @@ CREATE TABLE tACACompareItems
 (
     ItemID SERIAL NOT NULL,
     ItemName VARCHAR(64) NOT NULL,
-    ItemEnable INTEGER NOT NULL DEFAULT 1,
+    ItemEnable BOOLEAN NOT NULL,
     RoadCount INTEGER DEFAULT 0,
-    SilentEnable INTEGER NOT NULL DEFAULT 0,
+    SilentEnable BOOLEAN NOT NULL,
     SilentThreshold INTEGER DEFAULT 0,
     SilentDuration INTEGER DEFAULT 0,
     SilentSensitivity INTEGER DEFAULT 0,
-    OverloadEnable INTEGER NOT NULL DEFAULT 0,
+    OverloadEnable BOOLEAN NOT NULL,
     OverloadThreshold INTEGER DEFAULT 0,
     OverloadDuration INTEGER DEFAULT 0,
     OverloadSensitivity INTEGER DEFAULT 0,
-    PhaseEnable INTEGER NOT NULL DEFAULT 0,
+    PhaseEnable BOOLEAN NOT NULL,
     PhaseThreshold FLOAT DEFAULT 0,
     PhaseDuration INTEGER DEFAULT 0,
     PhaseSensitivity INTEGER DEFAULT 0,
-    CONSTRAINT tACACompareItems PRIMARY KEY (ItemID)
+    PRIMARY KEY (ItemID)
 );
 
 COMMENT ON TABLE tACACompareItems IS 'ACA对比项表';
@@ -56,11 +56,9 @@ CREATE TABLE tACACompareItemRoad
     ItemID SERIAL NOT NULL,
     RoadNum INTEGER NOT NULL,
     RoadName VARCHAR(64) NOT NULL,
-    RoadRecordEnable SMALLINT NOT NULL DEFAULT 1,
-    SoundCardNum INTEGER NOT NULL DEFAULT 0,
-    SoundCardID VARCHAR(64),
+    RoadRecordEnable BOOLEAN NOT NULL,
     SoundCardName VARCHAR(64),
-    SoundCardRoadNum INTEGER NOT NULL DEFAULT 0,
+    SoundCardPCMName VARCHAR(64),
     ChannelID INTEGER,
     ChannelName VARCHAR(64),
     PRIMARY KEY (ItemID, RoadNum),
@@ -74,10 +72,8 @@ COMMENT ON COLUMN tACACompareItemRoad.ItemID IS '对比项ID';
 COMMENT ON COLUMN tACACompareItemRoad.RoadNum IS '通道编号,1是主通道,其他对比通道依次向后排';
 COMMENT ON COLUMN tACACompareItemRoad.RoadName IS '通道名称';
 COMMENT ON COLUMN tACACompareItemRoad.RoadRecordEnable IS '通道录音是否启用';
-COMMENT ON COLUMN tACACompareItemRoad.SoundCardNum IS '声卡编号,在系统中的编号';
-COMMENT ON COLUMN tACACompareItemRoad.SoundCardID IS '声卡ID,可以使用声卡ID来打开声卡';
 COMMENT ON COLUMN tACACompareItemRoad.SoundCardName IS '声卡名称';
-COMMENT ON COLUMN tACACompareItemRoad.SoundCardRoadNum IS '声卡通道编号,使用声卡编号和声卡通道编号开始录音';
+COMMENT ON COLUMN tACACompareItemRoad.SoundCardPCMName IS '声卡PCM通道名称,使用这个名称打开录音通道';
 COMMENT ON COLUMN tACACompareItemRoad.ChannelID IS '频道ID';
 COMMENT ON COLUMN tACACompareItemRoad.ChannelName IS '频道名称';
 -- SQLBook: Code
@@ -85,15 +81,15 @@ COMMENT ON COLUMN tACACompareItemRoad.ChannelName IS '频道名称';
 CREATE TABLE tACADetectPeriod
 (
     ItemID INTEGER NOT NULL,
-    IsDetect SMALLINT NOT NULL DEFAULT 1, -- 是否检测
+    IsDetect BOOLEAN NOT NULL, -- 是否检测
     WeekType INTEGER NOT NULL,
     CDate VARCHAR(32),
     TimeStart VARCHAR(32) NOT NULL,
     TimeEnd VARCHAR(32) NOT NULL,
-    ApplySlient SMALLINT NOT NULL DEFAULT 0,  -- 是否应用静音
-    ApplyOverload SMALLINT NOT NULL DEFAULT 0, -- 是否应用超载
-    ApplyPhase SMALLINT NOT NULL DEFAULT 0,   -- 是否应用反相
-    ApplyNoise SMALLINT NOT NULL DEFAULT 0,   -- 是否应用噪音
+    ApplySlient BOOLEAN NOT NULL,  -- 是否应用静音
+    ApplyOverload BOOLEAN NOT NULL, -- 是否应用超载
+    ApplyPhase BOOLEAN NOT NULL,   -- 是否应用反相
+    ApplyNoise BOOLEAN NOT NULL,   -- 是否应用噪音
     FOREIGN KEY (ItemID) REFERENCES tACACompareItems(ItemID)
         ON DELETE CASCADE
 );
@@ -147,9 +143,8 @@ CREATE TABLE tACAAlarmInfo
     ItemID INTEGER NOT NULL,
     ItemName VARCHAR(64) NOT NULL,
     AlarmType INTEGER NOT NULL, -- 报警类型,1-静音,2-过载,3-反相,4-噪音,5-不一致
-    SoundCardNum VARCHAR(64) NOT NULL, -- 声卡编号(在系统中的编号)
     SoundCardName VARCHAR(64), -- 声卡名称
-    SoundCardRoadNum INTEGER NOT NULL, -- 声卡通道编号
+    SoundCardPCMName VARCHAR(64) NOT NULL, -- 声卡PCM通道名称
     CompareRoadNum INTEGER NOT NULL, -- 对比通道编号
     CompareRoadName VARCHAR(64), -- 对比通道名称
     CompareRoadType INTEGER, -- 通道类型:1、主输出,2、空收,3、主输出空收
@@ -170,9 +165,8 @@ COMMENT ON COLUMN tACAAlarmInfo.PKID IS '主键ID';
 COMMENT ON COLUMN tACAAlarmInfo.ItemID IS '对比项ID';
 COMMENT ON COLUMN tACAAlarmInfo.ItemName IS '对比项名称';
 COMMENT ON COLUMN tACAAlarmInfo.AlarmType IS '报警类型,1-静音,2-过载,3-反相,4-噪音,5-不一致';
-COMMENT ON COLUMN tACAAlarmInfo.SoundCardNum IS '声卡编号(在系统中的编号)';
 COMMENT ON COLUMN tACAAlarmInfo.SoundCardName IS '声卡名称';
-COMMENT ON COLUMN tACAAlarmInfo.SoundCardRoadNum IS '声卡通道编号';
+COMMENT ON COLUMN tACAAlarmInfo.SoundCardPCMName IS '声卡PCM通道名称';
 COMMENT ON COLUMN tACAAlarmInfo.CompareRoadNum IS '对比通道编号';
 COMMENT ON COLUMN tACAAlarmInfo.CompareRoadName IS '对比通道名称';
 COMMENT ON COLUMN tACAAlarmInfo.CompareRoadType IS '通道类型:1、主输出,2、空收,3、主输出空收';
@@ -187,8 +181,6 @@ COMMENT ON COLUMN tACAAlarmInfo.FileState IS '报警文件状态,1-录音中
 -- 创建索引
 CREATE INDEX idx_tACAAlarmInfo_ItemID ON tACAAlarmInfo (ItemID);
 CREATE INDEX idx_tACAAlarmInfo_AlarmType ON tACAAlarmInfo (AlarmType);
-CREATE INDEX idx_tACAAlarmInfo_SoundCardNum ON tACAAlarmInfo (SoundCardNum);
-CREATE INDEX idx_tACAAlarmInfo_SoundCardRoadNum ON tACAAlarmInfo (SoundCardRoadNum);
 CREATE INDEX idx_tACAAlarmInfo_CompareRoadNum ON tACAAlarmInfo (CompareRoadNum);
 CREATE INDEX idx_tACAAlarmInfo_AlarmStartTime ON tACAAlarmInfo (AlarmStartTime);
 CREATE INDEX idx_tACAAlarmInfo_AlarmEndTime ON tACAAlarmInfo (AlarmEndTime);
@@ -200,15 +192,15 @@ CREATE TABLE tACARecordFile
     ItemName VARCHAR(64) NOT NULL,    -- 对比项名称
     ItemRoadNum INTEGER NOT NULL,           -- 通道编号
     ItemRoadName VARCHAR(64) NOT NULL, -- 通道名称
-    SoundCardNum INTEGER NOT NULL,          -- 声卡编号
-    SoundCardRoadNum INTEGER NOT NULL,      -- 声卡通道编号
+    SoundCardName VARCHAR(64) ,          -- 声卡名称
+    SoundCardPCMName VARCHAR(64) NOT NULL,      -- 声卡PCM通道名称
     FileStartTime VARCHAR(32) NOT NULL, -- 录音开始时间 (格式:YYYY-MM-DD HH:MM:SS)
     FileEndTime VARCHAR(32) NOT NULL,   -- 录音结束时间 (格式: YYYY-MM-DD HH:MM:SS)
     FileDuration INTEGER NOT NULL,          -- 录音文件持续时间(秒数)
     FilePath VARCHAR(256) NOT NULL,     -- 录音文件路径
     FileState INTEGER DEFAULT 0,            -- 录音文件状态,0-未知状态,1-正在录音,2-录音完成,3-文件已删除
 
-    CONSTRAINT tACARecordFile PRIMARY KEY (PKID),
+    PRIMARY KEY (PKID),
     FOREIGN KEY (ItemID) REFERENCES tACACompareItems(ItemID)
         ON DELETE CASCADE
 );
@@ -221,8 +213,8 @@ COMMENT ON COLUMN tACARecordFile.ItemID IS '对比项ID';
 COMMENT ON COLUMN tACARecordFile.ItemName IS '对比项名称';
 COMMENT ON COLUMN tACARecordFile.ItemRoadNum IS '通道编号';
 COMMENT ON COLUMN tACARecordFile.ItemRoadName IS '通道名称';
-COMMENT ON COLUMN tACARecordFile.SoundCardNum IS '声卡编号';
-COMMENT ON COLUMN tACARecordFile.SoundCardRoadNum IS '声卡通道编号';
+COMMENT ON COLUMN tACARecordFile.SoundCardName IS '声卡名称';
+COMMENT ON COLUMN tACARecordFile.SoundCardPCMName IS '声卡PCM通道名称';
 COMMENT ON COLUMN tACARecordFile.FileStartTime IS '录音开始时间 (格式:YYYY-MM-DD HH:MM:SS)';
 COMMENT ON COLUMN tACARecordFile.FileEndTime IS '录音结束时间 (格式: YYYY-MM-DD HH:MM:SS)';
 COMMENT ON COLUMN tACARecordFile.FileDuration IS '录音文件持续时间(秒数)';
@@ -231,8 +223,29 @@ COMMENT ON COLUMN tACARecordFile.FileState IS '录音文件状态,0-未知状
 -- 创建索引
 CREATE INDEX idx_tACARecordFile_ItemID ON tACARecordFile (ItemID);
 CREATE INDEX idx_tACARecordFile_ItemRoadNum ON tACARecordFile (ItemRoadNum);
-CREATE INDEX idx_tACARecordFile_SoundCardNum ON tACARecordFile (SoundCardNum);
-CREATE INDEX idx_tACARecordFile_SoundCardRoadNum ON tACARecordFile (SoundCardRoadNum);
 CREATE INDEX idx_tACARecordFile_FileStartTime ON tACARecordFile (FileStartTime);
 CREATE INDEX idx_tACARecordFile_FileEndTime ON tACARecordFile (FileEndTime);
-CREATE INDEX idx_tACARecordFile_FileDuration ON tACARecordFile (FileDuration);
+CREATE INDEX idx_tACARecordFile_FileDuration ON tACARecordFile (FileDuration);
+-- SQLBook: Code
+#创建存储声卡PCM通道的表格
+CREATE TABLE tACASoundCardPCMChannels
+(
+    -- PKID INT PRIMARY KEY AUTO_INCREMENT,
+    SoundCardName VARCHAR(64) NOT NULL,   -- 声卡驱动名称,自定义的,用来表示PCM通道的
+    PCMName VARCHAR(64) NOT NULL,           -- PCM通道名称
+    PCMDesc VARCHAR(256) NOT NULL,          -- PCM通道描述
+    IOID VARCHAR(32),                       -- 输入输出
+    SoundCardNum INTEGER,                   -- 声卡编号,在系统中的编号,这个不是必须的,有些PCM通道是虚拟的,获取不到属于哪个声卡
+    SoundCardRoadNum INTEGER                -- 声卡通道编号,同上
+);
+
+-- 表注释
+COMMENT ON TABLE tACASoundCardPCMChannels IS '存储声卡PCM通道信息的表格';
+-- 字段注释
+-- COMMENT ON COLUMN tACASoundCardPCMChannels.PKID IS '主键ID';
+COMMENT ON COLUMN tACASoundCardPCMChannels.SoundCardName IS '声卡名称';
+COMMENT ON COLUMN tACASoundCardPCMChannels.PCMName IS 'PCM通道名称';
+COMMENT ON COLUMN tACASoundCardPCMChannels.PCMDesc IS 'PCM通道描述';
+COMMENT ON COLUMN tACASoundCardPCMChannels.IOID IS '输入输出标识,表示这个PCM通道是输入还是输出';
+COMMENT ON COLUMN tACASoundCardPCMChannels.SoundCardNum IS '声卡编号';
+COMMENT ON COLUMN tACASoundCardPCMChannels.SoundCardRoadNum IS '声卡通道编号';

+ 15 - 12
Server/ACAServer.cpp

@@ -36,7 +36,7 @@ ACAServer::~ACAServer()
 {
     if(m_rtpServer != nullptr)
     {
-        m_rtpServer->stopThreadBlock();
+        m_rtpServer->thread_stopBlock();
         delete m_rtpServer;
         m_rtpServer = nullptr;
     }
@@ -76,6 +76,8 @@ bool ACAServer::initGlobalInfo()
     {
         return false;
     }
+    SPDLOG_INFO("WebAPI URL: {}", m_webAPIUrl.toStdString());
+    SPDLOG_INFO("WebAPI DBID: {}", m_webAPIID.toStdString());
 
     /* 设置WebAPI信息 */
     // m_webAPIID = "cf6b57fa3d9841e22c3c897e6b8e66b8";
@@ -177,8 +179,9 @@ bool ACAServer::startService()
     // }
     /* 开启对比项管理线程 */
     CPPTP.add_task(&ThreadCompareItemManager::thread_CompareItemManager, &CompareItemManager);
-    /* 开启录音通道检查线程 */
-    CPPTP.add_task(&ACAServer::thread_deleteRecordThread, this);
+    /* 开启录音通道销毁线程 */
+    // CPPTP.add_task(&ACAServer::thread_deleteRecordThread, this);
+    CPPTP.add_task(&ThreadManager::thread_destroyeRecordThread, &ThreadMan);
     /* 开启处理报警信息的线程 */
     CPPTP.add_task(&ThreadWriteDBManager::thread_task, &WriteDB);
     /* 开启RTP监听服务线程 */
@@ -188,15 +191,15 @@ bool ACAServer::startService()
 }
 
 /* 线程函数,定时删除录音线程的实例 */
-void ACAServer::thread_deleteRecordThread()
-{
-    while(m_isRunning)
-    {
-        std::this_thread::sleep_for(std::chrono::seconds(5));
-        /* 销毁录音线程 */
-        ThreadMan.destroyeRecordThread();
-    }
-}
+// void ACAServer::thread_deleteRecordThread()
+// {
+//     while(m_isRunning)
+//     {
+//         std::this_thread::sleep_for(std::chrono::seconds(5));
+//         /* 销毁录音线程 */
+//         ThreadMan.destroyeRecordThread();
+//     }
+// }
 
 /* 线程函数,开启RTP监听服务,一个壳 */
 void ACAServer::thread_RTPServer()

+ 1 - 1
Server/ACAServer.h

@@ -47,7 +47,7 @@ public:
 
 private:
     /* 线程函数,定时删除录音线程的实例 */
-    void thread_deleteRecordThread();
+    // void thread_deleteRecordThread();
     /* 线程函数,开启RTP监听服务,一个壳 */
     void thread_RTPServer();
 

+ 10 - 10
Server/ThreadCalculate/BaseCalculateThread.cpp

@@ -17,14 +17,14 @@ BaseCalculateThread::BaseCalculateThread(CalculateThreadInfo_t& threadInfo)
 /**
  * @brief 线程任务函数,创建线程时会调用此函数
  */
-void BaseCalculateThread::threadTask()
+void BaseCalculateThread::thread_task()
 {
     m_logBase = fmt::format("对比项: {} 线程类型: {}", 
                             m_threadInfo.compareItemInfo.strName.toStdString(),
                             static_cast<int>(m_threadInfo.threadType));
     m_threadInfo.threadState = EThreadState::State_Running;
     m_isRunning = true;
-    m_isStop = false;
+    m_isStoped = false;
     /* 执行任务,并阻塞到这个函数中,直到任务退出 */
     task();
 
@@ -32,8 +32,8 @@ void BaseCalculateThread::threadTask()
     /* 清理资源 */
     /* 设置全局的线程状态 */
     m_threadInfo.threadState = EThreadState::State_Stopped;
-    m_isStop.store(true);
-    SPDLOG_LOGGER_INFO(m_logger, "★ {} 执行结束", m_logBase);
+    m_isStoped.store(true);
+    SPDLOG_INFO("★ {} 执行结束", m_logBase);
 }
 
 /* 更新线程信息 */
@@ -41,7 +41,7 @@ void BaseCalculateThread::updateThreadInfo(const CalculateThreadInfo_t& threadIn
 {
     if(threadInfo.compareItemInfo.nID != m_threadInfo.compareItemInfo.nID)
     {
-        SPDLOG_LOGGER_WARN(m_logger, "新传入的对比项ID {} 和当前线程的对比项ID {} 不匹配,无法更新线程信息",
+        SPDLOG_WARN("新传入的对比项ID {} 和当前线程的对比项ID {} 不匹配,无法更新线程信息",
                            threadInfo.compareItemInfo.nID, m_threadInfo.compareItemInfo.nID);
     }
     m_threadInfoNew = threadInfo;
@@ -49,17 +49,17 @@ void BaseCalculateThread::updateThreadInfo(const CalculateThreadInfo_t& threadIn
 }
 
 /* 停止线程,只设置个停止标志,不阻塞等待 */
-void BaseCalculateThread::stopThread()
+void BaseCalculateThread::thread_stop()
 {
     m_isRunning = false;
-    SPDLOG_LOGGER_INFO(m_logger, "{} 线程设置为停止状态", m_logBase);
+    SPDLOG_INFO("{} 线程设置为停止状态", m_logBase);
 }
 
 /* 停止线程 */
-void BaseCalculateThread::stopThreadBlock()
+void BaseCalculateThread::thread_stopBlock()
 {
     m_isRunning = false;
-    while(m_isStop.load() == false) 
+    while(m_isStoped.load() == false) 
     {
         /* 100us检查一次 */
         std::this_thread::sleep_for(std::chrono::microseconds(100));
@@ -76,7 +76,7 @@ bool BaseCalculateThread::updateThreadInfoInternal()
     /* 只更新对比项的信息 */
     m_threadInfo.compareItemInfo = m_threadInfoNew.compareItemInfo;
     m_isUpdate = false;
-    // SPDLOG_LOGGER_INFO(m_logger, "{} 更新线程信息", m_logBase);
+    // SPDLOG_INFO(m_logger, "{} 更新线程信息", m_logBase);
 
     return true;
 }

+ 4 - 4
Server/ThreadCalculate/BaseCalculateThread.h

@@ -22,7 +22,7 @@ public:
     /**
      * @brief 线程任务函数,创建线程时会调用此函数
      */
-    virtual void threadTask();
+    virtual void thread_task();
 
     /* 获取线程信息 */
     const CalculateThreadInfo_t& getThreadInfo() { return m_threadInfo; }
@@ -30,9 +30,9 @@ public:
     void updateThreadInfo(const CalculateThreadInfo_t& threadInfo);
 
     /* 停止线程,只设置个停止标志,不阻塞等待 */
-    virtual void stopThread();
+    virtual void thread_stop();
     /* 停止线程 */
-    virtual void stopThreadBlock();
+    virtual void thread_stopBlock();
 
     
 
@@ -55,7 +55,7 @@ protected:
     std::string m_logBase;                                  /* 日志基础信息 */
 
     std::atomic_bool m_isRunning = true;            /* 线程运行标志 */
-    std::atomic_bool m_isStop = false;              /* 线程停止标志 */
+    std::atomic_bool m_isStoped = false;            /* 线程停止标志 */
 
     CalculateThreadInfo_t m_threadInfo;             /* 线程信息 */
     std::atomic_bool m_isUpdate = false;            /* 是否需要更新线程信息 */

+ 38 - 27
Server/ThreadCalculate/CompareItemThread.cpp

@@ -29,7 +29,7 @@ CompareItemThread::~CompareItemThread()
 }
 
 /* 重写父类的线程函数,这里重新实现 */
-void CompareItemThread::threadTask()
+void CompareItemThread::thread_task()
 {
     m_logBase = fmt::format("对比项: {}", m_threadInfo.compareItemInfo.strName.toStdString());
     SPDLOG_LOGGER_INFO(m_logger, "----------------------------------------------------------------");
@@ -50,7 +50,7 @@ void CompareItemThread::threadTask()
     
     /* 更新线程状态标志 */
     m_threadInfo.threadState = EThreadState::State_Running;
-    m_isStop = false;
+    m_isStoped = false;
 
 
 
@@ -79,33 +79,22 @@ void CompareItemThread::threadTask()
     /* 线程结束,清理数据 */
     clearData();
     m_threadInfo.threadState = EThreadState::State_Stopped;
-    m_isStop = true;
+    m_isStoped = true;
     SPDLOG_LOGGER_WARN(m_logger, "★ {} 线程结束运行", m_logBase);
 }
 
 
 /* 停止线程函数 */
-void CompareItemThread::stopThread()
+void CompareItemThread::thread_stop()
 {
-    if(m_pTimer != nullptr)
-    {
-        if(m_pTimer->isActive())
-        {
-            m_pTimer->stop();
-        }
-        disconnect(m_pTimer, &QTimer::timeout, this, &CompareItemThread::do_timeout);
-        delete m_pTimer;
-        m_pTimer = nullptr;
-    }
-    /* 停止事件循环 */
-    m_eventLoop.quit();
+    m_isRunning.store(false);
     
 }
 
-void CompareItemThread::stopThreadBlock()
+void CompareItemThread::thread_stopBlock()
 {
-    stopThread();
-    while(!m_isStop) // 等待线程停止
+    thread_stop();
+    while(!m_isStoped) // 等待线程停止
     {
         std::this_thread::sleep_for(std::chrono::milliseconds(1));
     }
@@ -282,10 +271,10 @@ void CompareItemThread::clearData()
 {
     /* 停止所有的比对线程 */
     destroyCompareThreads();
-    /* 销毁音量报警线程 */
-    destroyCalculateDBThreads();
     /* 销毁噪音检测线程 */
     destroyNoiseDetectThreads();
+    /* 销毁音量报警线程 */
+    destroyCalculateDBThreads();
 
     /* 移除使用到的录音通道 */
     for(auto& it : m_threadInfo.compareItemInfo.mapRoad)
@@ -307,6 +296,10 @@ void CompareItemThread::clearData()
 /* 定时器槽函数 */
 void CompareItemThread::do_timeout()
 {
+    if(m_isRunning == false)
+    {
+        stop_thread();
+    }
     // std::chrono::steady_clock::time_point startTime = std::chrono::steady_clock::now();
     // SPDLOG_LOGGER_WARN(m_logger, "{} 定时器触发,开始执行定时任务", m_logBase);
     if(m_pFromMQTT == nullptr)
@@ -321,6 +314,23 @@ void CompareItemThread::do_timeout()
     m_nowTimePoint = endTime; // 更新当前时间点
 }
 
+/* 停止线程 */
+void CompareItemThread::stop_thread()
+{
+    if(m_pTimer != nullptr)
+    {
+        if(m_pTimer->isActive())
+        {
+            m_pTimer->stop();
+        }
+        disconnect(m_pTimer, &QTimer::timeout, this, &CompareItemThread::do_timeout);
+        delete m_pTimer;
+        m_pTimer = nullptr;
+    }
+    /* 停止事件循环 */
+    m_eventLoop.quit();
+}
+
 /* 初始化MQTT */
 void CompareItemThread::initMQTT()
 {
@@ -431,7 +441,7 @@ bool CompareItemThread::createCompareThreads()
         }
         m_mapCpmsistencyThreads.insert({it.key(), pThread}); // 保存线程指针
         /* 开始运行 */
-        CPPTP.add_task(&ConsistencyCompareThread::threadTask, pThread);
+        CPPTP.add_task(&ConsistencyCompareThread::thread_task, pThread);
     }
 
     return true;
@@ -449,7 +459,7 @@ void CompareItemThread::destroyCompareThreads()
     {
         if(pair.second != nullptr)
         {
-            pair.second->stopThreadBlock(); // 停止线程
+            pair.second->thread_stopBlock(); // 停止线程
             delete pair.second;             // 删除线程
             pair.second = nullptr;          // 设置为nullptr
         }
@@ -483,7 +493,7 @@ bool CompareItemThread::createCalculateDBThreads()
             SPDLOG_LOGGER_ERROR(m_logger, "{} 创建音量计算线程失败", m_logBase);
             // return false; // 获取线程失败
         }
-        CPPTP.add_task(&CalculateDBThread::threadTask, pThread);
+        CPPTP.add_task(&CalculateDBThread::thread_task, pThread);
         m_mapCalculateDBThreads.insert({road.nCompareRoadNum, pThread}); // 保存线程指针
     }
 
@@ -502,7 +512,7 @@ void CompareItemThread::destroyCalculateDBThreads()
     {
         if(pair.second != nullptr)
         {
-            pair.second->stopThreadBlock(); // 停止线程
+            pair.second->thread_stopBlock(); // 停止线程
             delete pair.second;             // 删除线程
             pair.second = nullptr;          // 设置为nullptr
         }
@@ -540,7 +550,7 @@ void CompareItemThread::createNoiseDetectThreads()
             SPDLOG_LOGGER_ERROR(m_logger, "{} 创建噪音检测线程失败", m_logBase);
             return; // 获取线程失败
         }
-        CPPTP.add_task(&NoiseDetectThread::threadTask, pThread);
+        CPPTP.add_task(&NoiseDetectThread::thread_task, pThread);
         m_mapNoiseDetectThreads.insert({road.nCompareRoadNum, pThread});
     }
 }
@@ -557,7 +567,7 @@ void CompareItemThread::destroyNoiseDetectThreads()
     {
         if(pair.second != nullptr)
         {
-            pair.second->stopThreadBlock(); // 停止线程
+            pair.second->thread_stopBlock(); // 停止线程
             delete pair.second;             // 删除线程
             pair.second = nullptr;          // 设置为nullptr
         }
@@ -705,6 +715,7 @@ bool CompareItemThread::updateResultData()
         /* 获取最新的一致性结果 */
         m_compareResult.mapRoadVolumes[pair.first].isConsistency = pThread->isConsistency();
         m_compareResult.mapRoadVolumes[pair.first].isNotConsistencyWarning = pThread->isNotConsistencyWarning();
+        m_compareResult.mapRoadVolumes[pair.first].similarity = pThread->getSimilarity();
         
         /* 判断是否开启了不一致报警条件: 在其他报警时,不一致不报警且后台不进行不一致对比 */
         if(m_isConsistencyNoWarnWhenOtherAlarm == true)

+ 6 - 3
Server/ThreadCalculate/CompareItemThread.h

@@ -50,11 +50,11 @@ public:
     ~CompareItemThread() override;
 
     /* 开启线程 */
-    void threadTask() override;
+    void thread_task() override;
 
     /* 停止线程 */
-    void stopThread() override;
-    void stopThreadBlock() override;
+    void thread_stop() override;
+    void thread_stopBlock() override;
 
     /* 设置检测时段 */
     void setDetectPeriod(const DetectPeriodConfig_t& detectPeriod);
@@ -72,6 +72,9 @@ protected:
     /* 初始化MQTT */
     void initMQTT();
 
+    /* 停止线程 */
+    void stop_thread();
+
 private slots:
     /* 定时器槽函数 */
     void do_timeout();

+ 2 - 1
Server/ThreadCalculate/ConsistencyCompareThread.cpp

@@ -265,7 +265,7 @@ void ConsistencyCompareThread::clearData()
 
     /* 设置标志位 */
     m_isRunning = false;
-    m_isStop.store(true);
+    m_isStoped.store(true);
     m_threadInfo.threadState = EThreadState::State_Stopped;
 }
 
@@ -303,6 +303,7 @@ bool ConsistencyCompareThread::compareConsistency()
 
     /* 保存结果 */
     m_consistencyResult.AddResult(result->highest_similarity / 100.0);
+    m_similarity = result->highest_similarity / 100.0; // 保存相似度
 
     delete result;
     result = nullptr;

+ 2 - 0
Server/ThreadCalculate/ConsistencyCompareThread.h

@@ -31,6 +31,8 @@ public:
     bool isConsistency() { return m_isConsistency.load(); }
     /* 获取不一致性预警 */
     bool isNotConsistencyWarning() { return m_isConsistencyWarning.load(); }
+    /* 获取相似度 */
+    double getSimilarity() const { return m_similarity; }
 
     /* 判断录音通道是否相等 */
     // bool isRoadEqual(const SoundCardRoadInfo_t& roadInfo1, const SoundCardRoadInfo_t& roadInfo2);

+ 1 - 6
Server/ThreadCalculate/NoiseDetectThread.cpp

@@ -16,12 +16,7 @@ NoiseDetectThread::NoiseDetectThread(CalculateThreadInfo_t& threadInfo)
     : BaseCalculateThread(threadInfo),
     m_leftRightData(0)
 {
-    m_logger = spdlog::get("ACAServer");
-    if(m_logger == nullptr)
-    {
-        fmt::print("NoiseDetectThread: ACAServer Logger not found.\n");
-        return;
-    }
+    
 }
 
 NoiseDetectThread::~NoiseDetectThread()

+ 2 - 2
Server/ThreadManager/ThreadCompareItemManager.cpp

@@ -22,7 +22,7 @@ void ThreadCompareItemManager::thread_compareItem(CalculateThreadInfo_t threadIn
     CompareItemManager.addCompareItemThread(pThread);
     
     /* 启动线程,就会一直阻塞在这里了 */
-    pThread->threadTask();
+    pThread->thread_task();
 }
 
 
@@ -341,7 +341,7 @@ void ThreadCompareItemManager::processDeleteCompareItemThreads(const QList<int>&
         if(deleteList.contains(compareItemID))
         {
             /* 设置线程停止标志 */
-            it->stopThread();
+            it->thread_stop();
             SPDLOG_LOGGER_INFO(m_logger, "对比项线程 {} 设置为停止状态", it->getThreadInfo().compareItemInfo.strName.toStdString());
         }
     }

+ 41 - 24
Server/ThreadManager/ThreadManager.cpp

@@ -42,7 +42,7 @@ void ThreadManager::stopAllThreads()
 bool ThreadManager::createRecordThread(const OneSoundCardPCMInfo_t& pcmInfo, int compareItemID)
 {
     /* 先查找队列中有没有该录音通道 */
-    std::lock_guard<std::mutex> lock(m_mutexRecordThreadRefCount);
+    std::unique_lock<std::mutex> lock(m_mutexRecordThreadRefCount);
     for(const auto& pair : m_mapRecordThreadRefCount)
     {
         if( pair.first == pcmInfo.pcmInfo.strPCMName)
@@ -85,7 +85,7 @@ bool ThreadManager::createRecordThread(const OneSoundCardPCMInfo_t& pcmInfo, int
         // return false; // 创建失败
     }else 
     {
-        CPPTP.add_task(&CreateWAVThread::threadTask, pCreateWAVThread);
+        CPPTP.add_task(&CreateWAVThread::thread_task, pCreateWAVThread);
         std::lock_guard<std::mutex> lock(m_mutexCreateWAVThreads);
         m_createWAVThreads.push_back(pCreateWAVThread);
     }
@@ -100,7 +100,7 @@ bool ThreadManager::createRecordThread(const OneSoundCardPCMInfo_t& pcmInfo, int
         // return false; // 创建失败
     }else 
     {
-        CPPTP.add_task(&CreateDBThread::threadTask, pCreateDBThread);
+        CPPTP.add_task(&CreateDBThread::thread_task, pCreateDBThread);
         std::lock_guard<std::mutex> lock(m_mutexCreateDBThreads);
         m_createDBThreads.push_back(pCreateDBThread);
     }
@@ -114,7 +114,7 @@ bool ThreadManager::createRecordThread(const OneSoundCardPCMInfo_t& pcmInfo, int
         // return false; // 创建失败
     }else 
     {
-        CPPTP.add_task(&CreateRecordFileThread::threadTask, pCreateLongWAVThread);
+        CPPTP.add_task(&CreateRecordFileThread::thread_task, pCreateLongWAVThread);
         std::lock_guard<std::mutex> lock(m_mutexCreateLongWAVThreads);
         m_createLongWAVThreads.push_back(pCreateLongWAVThread);
     }
@@ -143,7 +143,7 @@ bool ThreadManager::createRecordThread(const OneSoundCardPCMInfo_t& pcmInfo, int
         // return false; // 创建失败
     }else
     {
-        CPPTP.add_task(&RecordThread::threadTask, pRecordThread);
+        CPPTP.add_task(&RecordThread::thread_task, pRecordThread);
         std::lock_guard<std::mutex> lock(m_mutexRecordThreads);
         m_recordThreads.push_back(pRecordThread);
     }
@@ -157,7 +157,7 @@ bool ThreadManager::createRecordThread(const OneSoundCardPCMInfo_t& pcmInfo, int
         // return false; // 创建失败
     }else
     {
-        CPPTP.add_task(&AssignSrcDataThread::threadTask, pAssignSrcDataThread);
+        CPPTP.add_task(&AssignSrcDataThread::thread_task, pAssignSrcDataThread);
         std::lock_guard<std::mutex> lock(m_mutexAssignSrcDataThreads);
         m_assignSrcDataThreads.push_back(pAssignSrcDataThread);
     }
@@ -174,7 +174,7 @@ bool ThreadManager::createRecordThread(const OneSoundCardPCMInfo_t& pcmInfo, int
  */
 bool ThreadManager::removeRecordThread(const OneSoundCardPCMInfo_t& pcmInfo, int compareItemID)
 {
-    std::lock_guard<std::mutex> lock(m_mutexRecordThreadRefCount);
+    std::unique_lock<std::mutex> lock(m_mutexRecordThreadRefCount);
     /* 先查找这个引用计数是否存在 */
     int refCount = 0;
     for(auto& pair : m_mapRecordThreadRefCount)
@@ -211,7 +211,7 @@ bool ThreadManager::removeRecordThread(const OneSoundCardPCMInfo_t& pcmInfo, int
             const auto& threadInfo = it->getThreadInfo();
             if(threadInfo.cardRoadInfo.pcmInfo.strPCMName == pcmInfo.pcmInfo.strPCMName)
             {
-                it->stopThread();
+                it->thread_stop_block();
                 SPDLOG_LOGGER_TRACE(m_logger, "{} 录音线程已停止", logBase);
                 break;
             }
@@ -225,7 +225,7 @@ bool ThreadManager::removeRecordThread(const OneSoundCardPCMInfo_t& pcmInfo, int
             const auto& threadInfo = it->getThreadInfo();
             if(threadInfo.cardRoadInfo.pcmInfo.strPCMName == pcmInfo.pcmInfo.strPCMName)
             {
-                it->stopThread();
+                it->thread_stop_block();
                 SPDLOG_LOGGER_TRACE(m_logger, "{} 分派数据线程已停止", logBase);
                 break;
             }
@@ -239,7 +239,7 @@ bool ThreadManager::removeRecordThread(const OneSoundCardPCMInfo_t& pcmInfo, int
             const auto& threadInfo = it->getThreadInfo();
             if(threadInfo.cardRoadInfo.pcmInfo.strPCMName == pcmInfo.pcmInfo.strPCMName)
             {
-                it->stopThread();
+                it->thread_stop_block();
                 SPDLOG_LOGGER_TRACE(m_logger, "{} 生成wav小文件线程已停止", logBase);
                 break;
             }
@@ -254,7 +254,7 @@ bool ThreadManager::removeRecordThread(const OneSoundCardPCMInfo_t& pcmInfo, int
             const auto& threadInfo = it->getThreadInfo();
             if(threadInfo.cardRoadInfo.pcmInfo.strPCMName == pcmInfo.pcmInfo.strPCMName)
             {
-                it->stopThread();
+                it->thread_stop_block();
                 SPDLOG_LOGGER_TRACE(m_logger, "{} 计算音量线程已停止", logBase);
                 break;
             }
@@ -269,7 +269,7 @@ bool ThreadManager::removeRecordThread(const OneSoundCardPCMInfo_t& pcmInfo, int
             const auto& threadInfo = it->getThreadInfo();
             if(threadInfo.cardRoadInfo.pcmInfo.strPCMName == pcmInfo.pcmInfo.strPCMName)
             {
-                it->stopThread();
+                it->thread_stop_block();
                 SPDLOG_LOGGER_TRACE(m_logger, "{} 生成长文件线程已停止", logBase);
                 break;
             }
@@ -284,7 +284,7 @@ bool ThreadManager::removeRecordThread(const OneSoundCardPCMInfo_t& pcmInfo, int
             const auto& threadInfo = it->getThreadInfo();
             if(threadInfo.cardRoadInfo.pcmInfo.strPCMName == pcmInfo.pcmInfo.strPCMName)
             {
-                it->stopThread();
+                it->thread_stop_block();
                 SPDLOG_LOGGER_TRACE(m_logger, "{} 发送RTP数据线程已停止", logBase);
                 break;
             }
@@ -304,13 +304,18 @@ bool ThreadManager::removeRecordThread(const OneSoundCardPCMInfo_t& pcmInfo, int
 
     // 设置销毁录音线程标志
     m_isDestroyeRecordThread.store(true);
+    m_condVarDestroyRecord.notify_one();
 
     return true;
 }
 
 /* 销毁录音线程 */
-void ThreadManager::destroyeRecordThread()
+void ThreadManager::thread_destroyeRecordThread()
 {
+    std::unique_lock<std::mutex> m_lock(m_mutexRecordThreadRefCount);
+    m_condVarDestroyRecord.wait(m_lock, [this]() {
+        return m_isDestroyeRecordThread.load();
+    });
     if(!m_isDestroyeRecordThread.load())
     {
         /* 没有需要销毁的线程 */
@@ -324,7 +329,9 @@ void ThreadManager::destroyeRecordThread()
             BaseRecordThread* pThread = *it;
             if(pThread != nullptr)
             {
-                if(pThread->getThreadInfo().threadState == EThreadState::State_Running)
+                auto threadState = pThread->getThreadInfo().threadState;
+                if(EThreadState::State_Stopped == threadState ||
+                   EThreadState::State_Error == threadState )
                 {
                     auto pRecordThread = dynamic_cast<RecordThread*>(pThread);
                     delete pRecordThread; // 删除线程
@@ -346,7 +353,9 @@ void ThreadManager::destroyeRecordThread()
             BaseRecordThread* pThread = *it;
             if(pThread != nullptr)
             {
-                if(pThread->getThreadInfo().threadState == EThreadState::State_Running)
+                auto threadState = pThread->getThreadInfo().threadState;
+                if(EThreadState::State_Stopped == threadState ||
+                   EThreadState::State_Error == threadState )
                 {
                     auto pAssignSrcDataThread = dynamic_cast<AssignSrcDataThread*>(pThread);
                     delete pAssignSrcDataThread; // 删除线程
@@ -368,7 +377,9 @@ void ThreadManager::destroyeRecordThread()
             BaseRecordThread* pThread = *it;
             if(pThread != nullptr)
             {
-                if(pThread->getThreadInfo().threadState == EThreadState::State_Running)
+                auto threadState = pThread->getThreadInfo().threadState;
+                if(EThreadState::State_Stopped == threadState ||
+                   EThreadState::State_Error == threadState )
                 {
                     auto pCreateWAVThread = dynamic_cast<CreateWAVThread*>(pThread);
                     delete pCreateWAVThread; // 删除线程
@@ -390,7 +401,9 @@ void ThreadManager::destroyeRecordThread()
             BaseRecordThread* pThread = *it;
             if(pThread != nullptr)
             {
-                if(pThread->getThreadInfo().threadState == EThreadState::State_Running)
+                auto threadState = pThread->getThreadInfo().threadState;
+                if(EThreadState::State_Stopped == threadState ||
+                   EThreadState::State_Error == threadState )
                 {
                     auto pCreateDBThread = dynamic_cast<CreateDBThread*>(pThread);
                     delete pCreateDBThread; // 删除线程
@@ -412,7 +425,9 @@ void ThreadManager::destroyeRecordThread()
             BaseRecordThread* pThread = *it;
             if(pThread != nullptr)
             {
-                if(pThread->getThreadInfo().threadState == EThreadState::State_Running)
+                auto threadState = pThread->getThreadInfo().threadState;
+                if(EThreadState::State_Stopped == threadState ||
+                   EThreadState::State_Error == threadState )
                 {
                     auto pCreateLongWAVThread = dynamic_cast<CreateRecordFileThread*>(pThread);
                     delete pCreateLongWAVThread; // 删除线程
@@ -434,7 +449,9 @@ void ThreadManager::destroyeRecordThread()
             BaseRecordThread* pThread = *it;
             if(pThread != nullptr)
             {
-                if(pThread->getThreadInfo().threadState == EThreadState::State_Running)
+                auto threadState = pThread->getThreadInfo().threadState;
+                if(EThreadState::State_Stopped == threadState ||
+                   EThreadState::State_Error == threadState )
                 {
                     auto pRtpSendThread = dynamic_cast<RTPOneRoadThread*>(pThread);
                     delete pRtpSendThread; // 删除线程
@@ -664,7 +681,7 @@ CreateRecordFileThread* ThreadManager::getCreateRecordFileThread(std::string pcm
 //     if(m_referCountConsistencyCompare <= 0)
 //     {
 //         /* 停止线程,并一直等待其停止 */
-//         pThreadToRemove->stopThreadBlock();
+//         pThreadToRemove->thread_stopBlock();
 //         m_listConsistencyCompareThreads.remove(pThreadToRemove); // 从列表中移除
 //         delete pThreadToRemove; // 删除线程
 //         pThreadToRemove = nullptr;
@@ -799,7 +816,7 @@ CreateRecordFileThread* ThreadManager::getCreateRecordFileThread(std::string pcm
 //     {
 //         SPDLOG_LOGGER_INFO(m_logger, "{}:{} 噪音检测线程引用计数为0,准备销毁该线程",
 //                     roadInfo.strSoundCardName.toStdString(), roadInfo.roadInfo.nRoadNum);
-//         pThreadToRemove->stopThreadBlock(); // 停止线程
+//         pThreadToRemove->thread_stopBlock(); // 停止线程
 //         m_listNoiseDetectThreads.remove(pThreadToRemove); // 从列表中移除
 //         delete pThreadToRemove; // 删除线程
 //         pThreadToRemove = nullptr;
@@ -867,7 +884,7 @@ CreateRecordFileThread* ThreadManager::getCreateRecordFileThread(std::string pcm
 //     m_referCountCalculateDBPhase--; // 引用计数减一
 //     if(m_referCountCalculateDBPhase <= 0)
 //     {
-//         pThreadToRemove->stopThread(); // 停止线程
+//         pThreadToRemove->thread_stop(); // 停止线程
 //         m_listCalculateDBPhaseThreads.remove(pThreadToRemove); // 从列表中移除
 //         delete pThreadToRemove; // 删除线程
 //         pThreadToRemove = nullptr;
@@ -892,6 +909,6 @@ void ThreadManager::thread_RTPSend(RecordThreadInfo_t& threadInfo)
     ThreadMan.m_rtpSendThreads.push_back(pRtpSendThread);
     ThreadMan.m_mutexRtpSendThreads.unlock();
     
-    pRtpSendThread->threadTask();
+    pRtpSendThread->thread_task();
 }
 

+ 6 - 2
Server/ThreadManager/ThreadManager.h

@@ -59,8 +59,8 @@ public:
     bool removeRecordThread(const OneSoundCardPCMInfo_t& pcmInfo, int compareItemID);
     /* 获取是否需要销毁录音线程实例 */
     bool isDestroyeRecordThread() const { return m_isDestroyeRecordThread.load(); }
-    /* 销毁录音线程,由外部的管理线程调用 */
-    void destroyeRecordThread();
+    /* 销毁录音线程,在ACAServer初始化时创建 */
+    void thread_destroyeRecordThread();
     
 
 
@@ -109,6 +109,10 @@ private:
     
 
 private:
+    /* 销毁录音线程的条件变量 */
+    std::condition_variable m_condVarDestroyRecord;
+    
+
     /* 记录每个录音通道的引用计数,key是PCM通道名,在系统中是唯一的 */
     std::mutex m_mutexRecordThreadRefCount;
     std::map<std::string, std::list<int>> m_mapRecordThreadRefCount;

+ 16 - 1
Server/ThreadRecord/AssignSrcDataThread.cpp

@@ -47,13 +47,28 @@ AssignSrcDataThread::~AssignSrcDataThread()
 }
 
 /* 停止线程 */
-void AssignSrcDataThread::stopThread()
+void AssignSrcDataThread::thread_stop()
 {
     m_isRunning = false;
     m_condDataUpdate.notify_all(); // 通知所有等待的线程
 
 }
 
+/* 停止线程 */
+void AssignSrcDataThread::thread_stop_block()
+{
+    thread_stop();
+    while(true)
+    {
+        if(m_isStoped.load())
+        {
+            break;
+        }
+
+        std::this_thread::sleep_for(std::chrono::milliseconds(1));
+    }
+}
+
 
 
 /* 设置数据,这里不用 */

+ 3 - 1
Server/ThreadRecord/AssignSrcDataThread.h

@@ -30,7 +30,9 @@ public:
     ~AssignSrcDataThread() override;
 
     /* 停止线程 */
-    void stopThread() override;
+    void thread_stop() override;
+    /* 停止线程 */
+    void thread_stop_block() override;
 
     /* 设置数据,这里不用 */
     bool setData(const AudioSrcData& srcData) override;

+ 13 - 0
Server/ThreadRecord/AudioRecord/AudioRecord.cpp

@@ -270,6 +270,19 @@ snd_free:
 	return false;
 }
 
+
+/* 关闭录音通道 */
+void AudioRecord::closeRecordChannel()
+{
+	if(m_captureHandle != nullptr) 
+	{
+		snd_pcm_drain(m_captureHandle);
+		snd_pcm_close(m_captureHandle);
+		m_captureHandle = nullptr;
+	}
+	m_deviceName.clear();
+}
+
 /**
  * @brief 读取录音大小
  * 

+ 2 - 0
Server/ThreadRecord/AudioRecord/AudioRecord.h

@@ -94,6 +94,8 @@ public:
     void setRecordParams(int sampleRate = 44100, int bits = 16, int channels = 2);
     /* 打开录音通道,通常的格式: "hw:0:0" */
     bool openRecordChannel(const std::string &deviceName);
+    /* 关闭录音通道 */
+    void closeRecordChannel();
 
     /**
      * @brief 读取录音大小

+ 26 - 2
Server/ThreadRecord/BaseRecordThread.cpp

@@ -23,7 +23,7 @@ BaseRecordThread::~BaseRecordThread()
 
 
 /* 线程任务函数,子类需要实现 */
-void BaseRecordThread::threadTask()
+void BaseRecordThread::thread_task()
 {
     m_isRunning.store(true);
     m_logBase = fmt::format("录音通道: {}:{}", m_threadInfo.cardRoadInfo.strSoundCardName, 
@@ -31,19 +31,43 @@ void BaseRecordThread::threadTask()
 
     m_threadInfo.threadState = EThreadState::State_Running;
 
+    m_isRunning.store(true);
+    m_isStoped.store(false);
     /* 执行任务 */
     task();
 
     m_threadInfo.threadState = EThreadState::State_Stopped;
+
+    m_isRunning.store(false);
+    m_isStoped.store(true);
 }
 
 
 /* 停止线程 */
-void BaseRecordThread::stopThread()
+void BaseRecordThread::thread_stop()
 {
     m_isRunning.store(false);
 }
 
+/* 阻塞停止线程 */
+void BaseRecordThread::thread_stop_block()
+{
+    if(m_isRunning.load())
+    {
+        m_isRunning.store(false);
+    }
+
+    /* 等待退出 */
+    while(true)
+    {
+        if(m_isStoped.load())
+        {
+            break;
+        }
+        std::this_thread::sleep_for(std::chrono::milliseconds(1));
+    }
+}
+
 /* 获取频率ID */
 const RecordThreadInfo_t& BaseRecordThread::getThreadInfo()
 {

+ 5 - 2
Server/ThreadRecord/BaseRecordThread.h

@@ -15,10 +15,12 @@ public:
     virtual ~BaseRecordThread();
 
     /* 线程任务函数,子类需要实现 */
-    virtual void threadTask();
+    virtual void thread_task();
 
     /* 停止线程 */
-    virtual void stopThread();
+    virtual void thread_stop();
+    /* 阻塞停止线程 */
+    virtual void thread_stop_block();
 
     /* 获取录音通道信息 */
     const RecordThreadInfo_t& getThreadInfo();
@@ -46,6 +48,7 @@ protected:
     std::shared_ptr<spdlog::logger> m_logger = nullptr;     /* 日志记录器 */
     RecordThreadInfo_t m_threadInfo;                        /* 线程信息 */
     std::atomic_bool m_isRunning = false;                   /* 线程运行标志 */
+    std::atomic_bool m_isStoped = true;                     /* 线程停止标志 */
     std::string m_logBase;                                  /* 日志基础信息 */
 
     int32_t m_oneSecondSize = 0;                           /* 每秒钟的音频数据大小 */

+ 24 - 0
Server/ThreadRecord/CreateDBThread.cpp

@@ -17,6 +17,30 @@ CreateDBThread::~CreateDBThread()
 
 }
 
+/* 停止线程 */
+void CreateDBThread::thread_stop()
+{
+    if(m_isRunning.load())
+    {
+        m_isRunning.store(false);
+    }
+    /* 环形阻塞的线程 */
+    m_queueRealTimeData.exit();
+}
+
+void CreateDBThread::thread_stop_block()
+{
+    thread_stop();
+    while(true)
+    {
+        if(m_isStoped.load())
+        {
+            break;
+        }
+        std::this_thread::sleep_for(std::chrono::milliseconds(10));
+    }
+}
+
 
 
 /* 设置数据 */

+ 4 - 0
Server/ThreadRecord/CreateDBThread.h

@@ -22,6 +22,10 @@ public:
     CreateDBThread(RecordThreadInfo_t& threadInfo);
     ~CreateDBThread() override;
 
+    /* 停止线程 */
+    void thread_stop() override;
+    void thread_stop_block() override;
+
     /* 设置数据 */
     bool setData(const AudioSrcData& srcData) override;
     /* 设置实时数据 */

+ 4 - 8
Server/ThreadRecord/CreateRecordFileThread.cpp

@@ -17,14 +17,8 @@
 CreateRecordFileThread::CreateRecordFileThread(RecordThreadInfo_t& threadInfo)
     : BaseRecordThread(threadInfo)
 {
-    m_logger = spdlog::get("RecordAudio");
-    if(m_logger == nullptr)
-    {
-        fmt::print("RecordThread: RecordAudio Logger not found.\n");
-        return;
-    }
-    /* 初始化数据 */
-    initData();
+    
+    
 }
 
 CreateRecordFileThread::~CreateRecordFileThread()
@@ -216,6 +210,8 @@ void CreateRecordFileThread::task()
     SPDLOG_LOGGER_INFO(m_logger, "➢ {} 开启记录文件线程 ", m_logBase);
     /* 计算一小时的文件大小 */
 
+    initData();
+    
     while(m_isRunning)
     {
         /* 线程休眠100ms */

+ 3 - 7
Server/ThreadRecord/CreateWAVThread.cpp

@@ -13,12 +13,7 @@
 CreateWAVThread::CreateWAVThread(RecordThreadInfo_t& threadInfo)
     : BaseRecordThread(threadInfo)
 {
-    m_logger = spdlog::get("RecordAudio");
-    if(m_logger == nullptr)
-    {
-        fmt::print("RecordThread: RecordAudio Logger not found.\n");
-        return;
-    }
+    
 }
     
 CreateWAVThread::~CreateWAVThread()
@@ -27,6 +22,7 @@ CreateWAVThread::~CreateWAVThread()
 }
 
 
+
 /**
  * @brief 设置数据,传入的都是1秒钟的数据
  * 
@@ -126,7 +122,7 @@ void CreateWAVThread::task()
     SPDLOG_LOGGER_INFO(m_logger, "WAV文件队列容量: {}, 每个元素大小: {}", GInfo.queueElementCount(), m_oneSecondSize);
 
     m_isRunning = true;
-    while(m_isRunning)
+    while(m_isRunning.load())
     {
         /* 睡眠一定时间 */
         std::this_thread::sleep_for(std::chrono::milliseconds(100));

+ 1 - 0
Server/ThreadRecord/CreateWAVThread.h

@@ -28,6 +28,7 @@ public:
     CreateWAVThread(RecordThreadInfo_t& threadInfo);
     ~CreateWAVThread() override;
 
+
     /* 设置数据 */
     bool setData(const AudioSrcData& srcData) override;
 

+ 8 - 6
Server/ThreadRecord/RecordThread.cpp

@@ -11,12 +11,7 @@
 
 RecordThread::RecordThread(RecordThreadInfo_t& threadInfo) : BaseRecordThread(threadInfo)
 {
-    m_logger = spdlog::get("RecordAudio");
-    if(m_logger == nullptr)
-    {
-        fmt::print("RecordThread: RecordAudio Logger not found.\n");
-        return;
-    }
+    
 }
 
 RecordThread::~RecordThread()
@@ -82,6 +77,7 @@ void RecordThread::task()
     // m_testLastTime = std::chrono::steady_clock::now();
 
     m_isRunning = true;
+    m_isStoped = false;
     while(m_isRunning)
     {
         // std::chrono::steady_clock::time_point startTime = std::chrono::steady_clock::now();
@@ -120,8 +116,14 @@ void RecordThread::task()
         // SPDLOG_LOGGER_TRACE(m_logger, "{} 分派数据耗时: {}us", m_logBase, duration.count());
     }
 
+    /* 关闭录音通道 */
+    m_audioRecord.closeRecordChannel();
+
+    clearData();
+
     SPDLOG_LOGGER_WARN(m_logger, "➢ {} 结束录音", m_logBase);
     m_isRunning = false;
+    m_isStoped = true;
 }
 
 

+ 1 - 0
Server/ThreadRecord/RecordThread.h

@@ -31,6 +31,7 @@ public:
     RecordThread(RecordThreadInfo_t& threadInfo);
     ~RecordThread() override;
 
+
     /* 设置声卡描述符 */
     void setAudioRecordDesc(const std::string& desc);
     /* 设置分派线程的指针 */

+ 1 - 1
Server/main.cpp

@@ -31,7 +31,7 @@ int main(int argc, char* argv[])
         return -1;
     }
     SPDLOG_LOGGER_INFO(logger, "★  ★  ★  ★  ★  ★  ★  ☆  ACAServer  ☆  ★  ★  ★  ★  ★  ★  ★");
-    SPDLOG_LOGGER_INFO(logger, "ACServer Version: {}", "6.0.0.0");
+    SPDLOG_LOGGER_INFO(logger, "ACServer Version: {}", "6.0.0.1");
     /* 设置线程池最大线程个数 */
     CPPTP.setThreadMaxNum(1024);
 

+ 21 - 1
show1/widget.cpp

@@ -3,6 +3,9 @@
 
 #include <QLayout>
 #include <QVBoxLayout>
+#include <QFile>
+#include <QSettings>
+
 
 #include "settingAPI.h"
 #include "spdlog/spdlog.h"
@@ -28,9 +31,10 @@ Widget::Widget(QWidget *parent) :
     // 初始化数据
     stInitData initData;
     initData.strWebAddr = "http://192.1.3.133:31000/v6/";
-    
+
     initData.strDBID = "cf6b57fa3d9841e22c3c897e6b8e66b8";       /* 达梦数据库 */
     // initData.strDBID = "3b8889a0d58b8d71affc04bc27d14e42";          /* GBase */
+
     DoInit(&initData);
     // 创建窗口
     int skinType = 0; // 0: 白色风格, 1: 黑色风格
@@ -59,3 +63,19 @@ void Widget::on_pBtn_cancel_clicked()
 }
 
 
+/* 读取配置文件 */
+// void Widget::readConfigFile()
+// {
+//     QString configPath = QApplication::applicationDirPath() + "/config/BaseConfig.ini";
+//     QFile configFile(configPath);
+//     if(configFile.open(QIODevice::ReadOnly))
+//     {
+//         QByteArray configData = configFile.readAll();
+//         configFile.close();
+//     }else {
+//         return;
+//     }
+//     QSettings settings(configPath, QSettings::IniFormat);
+//     settings.beginGroup("MQTT");
+// }
+

+ 6 - 0
show1/widget.h

@@ -20,9 +20,15 @@ private slots:
     void on_pBtn_save_clicked();
     void on_pBtn_cancel_clicked();
 
+private:
+    /* 读取配置文件 */
+    void readConfigFile();
 
 private:
     Ui::Widget *ui;
+
+    QString m_webapiURL;
+    QString m_dbID;
 };
 
 #endif // WIDGET_H

+ 0 - 0
ACA服务程序设计说明.md → 文档/ACA服务程序设计说明.md


+ 30 - 0
文档/debian创建ftp服务.md

@@ -0,0 +1,30 @@
+# deepin或uos安装ftp服务器
+
+## 安装vsftpd
+- sudo apt update
+- sudo apt install vsftpd
+- 设置开机自启: sudo systemctl enable vsftpd
+
+## 修改配置文件
+- sudo vim /etc/vsftpd/vsftpd.conf
+### 需要修改的参数
+- anonymous_enable=NO:禁用匿名访问。
+- local_enable=YES:允许本地用户登录。
+- write_enable=YES:允许用户上传文件。
+- chroot_local_user=YES:将用户限制在主目录中。
+
+### 创建用户和用户文件夹
+
+#### 创建ftp用户的文件夹:
+- mkdir /home/FTPDir
+- 设置这个文件夹的所属权为root或者其他用户,并设置为其他用户可读
+    - sudo chown root:root /home/FTPDir
+    - sudo chmod 755 /home/FTPDir
+
+#### 创建ftp用户
+- sudo useradd ftpUser -d /home/FTPDir /bin/bash
+
+
+## 注意
+- 设置了`chroot_local_user=yes`参数后,ftp用户不能有ftp根目录/home/FTPDir的写权限,可以在里面创建一个文件夹`subDir`设置为777权限,在`subDir`中读写
+- 如果`FTPDir`这个根目录比较深,ftp用户需要有`FTPDir`前面所有父文件夹的读权限

+ 0 - 0
旧程序说明.md → 文档/旧程序说明.md