From c62dbec7328a8b44e6ec61758e7b8463f2e502dd Mon Sep 17 00:00:00 2001
From: LAPTOP-SNT8I5JK\Boounion <Chenluhua@qq.com>
Date: 星期五, 12 九月 2025 11:58:15 +0800
Subject: [PATCH] Merge branch 'liuyang'

---
 SourceCode/Bond/Servo/GlassLogDb.h |  170 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 170 insertions(+), 0 deletions(-)

diff --git a/SourceCode/Bond/Servo/GlassLogDb.h b/SourceCode/Bond/Servo/GlassLogDb.h
new file mode 100644
index 0000000..2bdf9bc
--- /dev/null
+++ b/SourceCode/Bond/Servo/GlassLogDb.h
@@ -0,0 +1,170 @@
+#pragma once
+// GlassLogDb.h - 单例封装:SQLite 写入/查询/分页/统计/CSV 导出(已对接 SERVO::CGlass)
+
+#include <string>
+#include <vector>
+#include <mutex>
+#include <optional>
+#include <chrono>
+#include <cstdint>
+#include <memory>   // std::unique_ptr
+
+#include "CGlass.h"   // 需要 SERVO::CGlass
+
+// 可在编译命令或本头文件前自定义表名:#define GLASS_LOG_TABLE "your_table_name"
+#ifndef GLASS_LOG_TABLE
+#define GLASS_LOG_TABLE "glass_log"
+#endif
+
+// 前置声明,避免头文件依赖 sqlite3.h
+struct sqlite3;
+struct sqlite3_stmt;
+
+class GlassLogDb {
+public:
+    // ====== 单例接口 ======
+    // 初始化(或切换)数据库文件路径;第一次调用会创建实例
+    static void Init(const std::string& dbPath);
+    // 获取全局实例;若未初始化会抛异常
+    static GlassLogDb& Instance();
+    // 是否已初始化
+    static bool IsInitialized() noexcept;
+    // 运行中切换到另一份数据库文件(等同 Init,不存在则创建)
+    static void Reopen(const std::string& dbPath);
+    // 当前数据库文件路径(未初始化返回空串)
+    static std::string CurrentPath();
+
+    // ====== 事务控制 ======
+    void beginTransaction();
+    void commit();
+    void rollback();
+
+    // ====== 写入 ======
+    // 直接从 SERVO::CGlass 写入(注意:参数为 非 const 引用,因为你的若干 getter 非 const)
+    long long insertFromCGlass(SERVO::CGlass& g);
+
+    // 显式参数插入(tStart/tEnd 可空;均写入 UTC ISO8601 文本或 NULL)
+    long long insertExplicit(
+        int cassetteSeqNo,
+        int jobSeqNo,
+        const std::string& classId,
+        int materialType,
+        int state,
+        std::optional<std::chrono::system_clock::time_point> tStart,
+        std::optional<std::chrono::system_clock::time_point> tEnd,
+        const std::string& buddyId,
+        int aoiResult,
+        const std::string& pathDesc,
+        const std::string& paramsDesc,
+        const std::string& prettyString);
+
+    // ====== 查询返回结构 ======
+    struct Row {
+        long long id = 0;
+        int cassetteSeqNo = 0;
+        int jobSeqNo = 0;
+        std::string classId;
+        int materialType = 0;
+        int state = 0;
+        std::string tStart;   // ISO8601(库中为 NULL 则为空串)
+        std::string tEnd;     // ISO8601(库中为 NULL 则为空串)
+        std::string buddyId;
+        int aoiResult = 0;
+        std::string path;
+        std::string params;
+        std::string pretty;
+    };
+
+    // ====== 过滤条件 ======
+    struct Filters {
+        std::optional<std::string> classId;       // 精确匹配 class_id
+        std::optional<int> cassetteSeqNo;
+        std::optional<int> jobSeqNo;
+
+        // 关键字模糊:对 class_id / buddy_id / path / params / pretty 做 OR LIKE
+        std::optional<std::string> keyword;
+
+        // 时间范围:对 t_start 过滤,含边界(ISO8601 文本比较)
+        std::optional<std::chrono::system_clock::time_point> tStartFrom;
+        std::optional<std::chrono::system_clock::time_point> tStartTo;
+    };
+
+    // ====== 查询 / 统计 / 分页 ======
+    // 分页查询(limit/offset),按 id DESC
+    std::vector<Row> query(
+        const Filters& filters = {},
+        int limit = 50,
+        int offset = 0);
+
+    // 统计与 filters 相同条件的总数
+    long long count(const Filters& filters = {});
+
+    struct Page {
+        std::vector<Row> items;  // 当前页数据
+        long long total = 0;     // 符合条件的总记录数
+        int limit = 0;           // 本次查询的页容量
+        int offset = 0;          // 本次查询的起始偏移
+    };
+    // 一次取回列表 + 总数
+    Page queryPaged(
+        const Filters& filters = {},
+        int limit = 50,
+        int offset = 0);
+
+    // ====== 导出 ======
+    // 导出满足 filters 的“全部记录”(不受分页限制)为 CSV(UTF-8)
+    // 返回写出的数据行数(不含表头)
+    long long exportCsv(const std::string& csvPath, const Filters& filters = {});
+
+    // ====== 析构 / 拷贝控制 ======
+    ~GlassLogDb();
+    GlassLogDb(const GlassLogDb&) = delete;
+    GlassLogDb& operator=(const GlassLogDb&) = delete;
+
+private:
+    // 仅允许通过单例接口构造
+    explicit GlassLogDb(const std::string& dbPath);
+
+    // 内部打开/关闭/重开
+    void openDb(const std::string& dbPath);
+    void closeDb() noexcept;
+    void reopenInternal(const std::string& dbPath);
+
+    // 建表 / 预编译 / 释放
+    void ensureSchema();
+    void prepareStatements();
+    void finalizeStatements() noexcept;
+
+    // 工具:time_point -> "YYYY-MM-DDTHH:MM:SSZ"(UTC)
+    static std::string toIso8601Utc(std::chrono::system_clock::time_point tp);
+
+    // 实际插入执行(支持可空时间)
+    long long doInsert(
+        int cassetteSeqNo,
+        int jobSeqNo,
+        const std::string& classId,
+        int materialType,
+        int state,
+        std::optional<std::chrono::system_clock::time_point> tStart,
+        std::optional<std::chrono::system_clock::time_point> tEnd,
+        const std::string& buddyId,
+        int aoiResult,
+        const std::string& pathDesc,
+        const std::string& paramsDesc,
+        const std::string& prettyString);
+
+private:
+    // SQLite 句柄
+    sqlite3* db_ = nullptr;
+    sqlite3_stmt* stmtInsert_ = nullptr;
+
+    // 实例内并发保护(同一连接上的写/读串行化)
+    std::mutex mtx_;
+
+    // 当前数据库文件路径
+    std::string dbPath_;
+
+    // 单例静态对象与其互斥
+    static std::unique_ptr<GlassLogDb> s_inst;
+    static std::mutex s_instMtx;
+};

--
Gitblit v1.9.3