database.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. from __future__ import annotations
  2. import sqlite3
  3. from contextlib import contextmanager
  4. from pathlib import Path
  5. from typing import Iterator
  6. BASE_DIR = Path(__file__).resolve().parent.parent
  7. DATA_DIR = BASE_DIR / "data"
  8. DB_PATH = DATA_DIR / "win_monitor.db"
  9. def dict_factory(cursor: sqlite3.Cursor, row: sqlite3.Row) -> dict:
  10. fields = [column[0] for column in cursor.description]
  11. return {key: row[index] for index, key in enumerate(fields)}
  12. @contextmanager
  13. def get_db() -> Iterator[sqlite3.Connection]:
  14. DATA_DIR.mkdir(parents=True, exist_ok=True)
  15. conn = sqlite3.connect(DB_PATH)
  16. conn.row_factory = dict_factory
  17. conn.execute("PRAGMA foreign_keys = ON")
  18. try:
  19. yield conn
  20. conn.commit()
  21. except Exception:
  22. conn.rollback()
  23. raise
  24. finally:
  25. conn.close()
  26. def init_db() -> None:
  27. with get_db() as conn:
  28. conn.executescript(
  29. """
  30. CREATE TABLE IF NOT EXISTS scan_records (
  31. id INTEGER PRIMARY KEY AUTOINCREMENT,
  32. started_at TEXT NOT NULL,
  33. finished_at TEXT,
  34. status TEXT NOT NULL,
  35. services_found INTEGER NOT NULL DEFAULT 0,
  36. processes_found INTEGER NOT NULL DEFAULT 0,
  37. new_services INTEGER NOT NULL DEFAULT 0,
  38. new_processes INTEGER NOT NULL DEFAULT 0,
  39. error_message TEXT
  40. );
  41. CREATE TABLE IF NOT EXISTS windows_services (
  42. id INTEGER PRIMARY KEY AUTOINCREMENT,
  43. identity_key TEXT NOT NULL UNIQUE,
  44. name TEXT NOT NULL,
  45. display_name TEXT,
  46. status TEXT,
  47. start_type TEXT,
  48. username TEXT,
  49. binary_path TEXT,
  50. description TEXT,
  51. is_present_now INTEGER NOT NULL DEFAULT 1,
  52. first_seen_at TEXT NOT NULL,
  53. last_seen_at TEXT NOT NULL,
  54. confirm_status TEXT NOT NULL DEFAULT 'PENDING',
  55. user_note TEXT,
  56. ai_description TEXT,
  57. ai_reason TEXT,
  58. ai_suggestion TEXT,
  59. risk_level TEXT,
  60. updated_at TEXT NOT NULL
  61. );
  62. CREATE TABLE IF NOT EXISTS windows_processes (
  63. id INTEGER PRIMARY KEY AUTOINCREMENT,
  64. identity_key TEXT NOT NULL UNIQUE,
  65. name TEXT NOT NULL,
  66. exe_path TEXT,
  67. cmdline TEXT,
  68. username TEXT,
  69. status TEXT,
  70. cwd TEXT,
  71. last_pid INTEGER,
  72. parent_pid INTEGER,
  73. create_time TEXT,
  74. is_present_now INTEGER NOT NULL DEFAULT 1,
  75. first_seen_at TEXT NOT NULL,
  76. last_seen_at TEXT NOT NULL,
  77. confirm_status TEXT NOT NULL DEFAULT 'PENDING',
  78. user_note TEXT,
  79. ai_description TEXT,
  80. ai_reason TEXT,
  81. ai_suggestion TEXT,
  82. risk_level TEXT,
  83. updated_at TEXT NOT NULL
  84. );
  85. CREATE INDEX IF NOT EXISTS idx_services_confirm_status
  86. ON windows_services(confirm_status);
  87. CREATE INDEX IF NOT EXISTS idx_processes_confirm_status
  88. ON windows_processes(confirm_status);
  89. CREATE INDEX IF NOT EXISTS idx_scan_records_started_at
  90. ON scan_records(started_at DESC);
  91. CREATE TABLE IF NOT EXISTS tags (
  92. id INTEGER PRIMARY KEY AUTOINCREMENT,
  93. name TEXT NOT NULL UNIQUE,
  94. description TEXT,
  95. is_controllable INTEGER NOT NULL DEFAULT 1,
  96. is_builtin INTEGER NOT NULL DEFAULT 0,
  97. created_at TEXT NOT NULL,
  98. updated_at TEXT NOT NULL
  99. );
  100. CREATE TABLE IF NOT EXISTS item_tags (
  101. id INTEGER PRIMARY KEY AUTOINCREMENT,
  102. item_type TEXT NOT NULL CHECK(item_type IN ('service', 'process')),
  103. item_id INTEGER NOT NULL,
  104. tag_id INTEGER NOT NULL,
  105. created_at TEXT NOT NULL,
  106. UNIQUE(item_type, item_id, tag_id),
  107. FOREIGN KEY(tag_id) REFERENCES tags(id) ON DELETE CASCADE
  108. );
  109. CREATE INDEX IF NOT EXISTS idx_item_tags_item
  110. ON item_tags(item_type, item_id);
  111. """
  112. )
  113. seed_default_tags(conn)
  114. def seed_default_tags(conn: sqlite3.Connection) -> None:
  115. rows = [
  116. (
  117. "windows系统",
  118. "Windows 系统原生服务或进程。默认不可控制,避免误停止系统关键组件。",
  119. 0,
  120. 1,
  121. ),
  122. (
  123. "本系统相关",
  124. "本项目程序自身相关服务或进程。默认不可控制,避免通过监控系统停止自身。",
  125. 0,
  126. 1,
  127. ),
  128. ]
  129. now = __import__("datetime").datetime.now().astimezone().isoformat(timespec="seconds")
  130. for name, description, is_controllable, is_builtin in rows:
  131. conn.execute(
  132. """
  133. INSERT INTO tags (name, description, is_controllable, is_builtin, created_at, updated_at)
  134. VALUES (?, ?, ?, ?, ?, ?)
  135. ON CONFLICT(name) DO UPDATE SET
  136. description = COALESCE(tags.description, excluded.description),
  137. is_builtin = 1,
  138. updated_at = excluded.updated_at
  139. """,
  140. (name, description, is_controllable, is_builtin, now, now),
  141. )