database.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  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. CREATE TABLE IF NOT EXISTS ai_providers (
  112. id INTEGER PRIMARY KEY AUTOINCREMENT,
  113. name TEXT NOT NULL UNIQUE,
  114. provider_type TEXT NOT NULL CHECK(provider_type IN ('OPENAI', 'OPENAI_COMPATIBLE', 'GOOGLE_GEMINI')),
  115. base_url TEXT,
  116. api_key TEXT,
  117. enabled INTEGER NOT NULL DEFAULT 1,
  118. created_at TEXT NOT NULL,
  119. updated_at TEXT NOT NULL
  120. );
  121. CREATE TABLE IF NOT EXISTS ai_models (
  122. id INTEGER PRIMARY KEY AUTOINCREMENT,
  123. provider_id INTEGER NOT NULL,
  124. name TEXT NOT NULL,
  125. display_name TEXT,
  126. is_default INTEGER NOT NULL DEFAULT 0,
  127. created_at TEXT NOT NULL,
  128. updated_at TEXT NOT NULL,
  129. UNIQUE(provider_id, name),
  130. FOREIGN KEY(provider_id) REFERENCES ai_providers(id) ON DELETE CASCADE
  131. );
  132. CREATE INDEX IF NOT EXISTS idx_ai_models_provider
  133. ON ai_models(provider_id);
  134. CREATE TABLE IF NOT EXISTS automation_screens (
  135. id INTEGER PRIMARY KEY AUTOINCREMENT,
  136. interface_name TEXT NOT NULL,
  137. description TEXT,
  138. image_path TEXT NOT NULL,
  139. width INTEGER NOT NULL,
  140. height INTEGER NOT NULL,
  141. is_windows_desktop INTEGER NOT NULL DEFAULT 0,
  142. is_browser_webpage INTEGER NOT NULL DEFAULT 0,
  143. raw_ai_json TEXT,
  144. created_at TEXT NOT NULL,
  145. updated_at TEXT NOT NULL
  146. );
  147. CREATE TABLE IF NOT EXISTS automation_screen_elements (
  148. id INTEGER PRIMARY KEY AUTOINCREMENT,
  149. screen_id INTEGER NOT NULL,
  150. element_index INTEGER NOT NULL,
  151. name TEXT NOT NULL,
  152. x_percent REAL NOT NULL,
  153. y_percent REAL NOT NULL,
  154. x INTEGER NOT NULL,
  155. y INTEGER NOT NULL,
  156. raw_json TEXT,
  157. created_at TEXT NOT NULL,
  158. FOREIGN KEY(screen_id) REFERENCES automation_screens(id) ON DELETE CASCADE
  159. );
  160. CREATE INDEX IF NOT EXISTS idx_automation_screen_elements_screen
  161. ON automation_screen_elements(screen_id, element_index);
  162. CREATE TABLE IF NOT EXISTS automation_workflows (
  163. id INTEGER PRIMARY KEY AUTOINCREMENT,
  164. name TEXT NOT NULL,
  165. description TEXT,
  166. raw_json TEXT,
  167. created_at TEXT NOT NULL,
  168. updated_at TEXT NOT NULL
  169. );
  170. CREATE TABLE IF NOT EXISTS automation_workflow_nodes (
  171. id INTEGER PRIMARY KEY AUTOINCREMENT,
  172. workflow_id INTEGER NOT NULL,
  173. node_index INTEGER NOT NULL,
  174. node_type TEXT NOT NULL,
  175. screen_id INTEGER,
  176. title TEXT,
  177. config_json TEXT NOT NULL,
  178. created_at TEXT NOT NULL,
  179. updated_at TEXT NOT NULL,
  180. FOREIGN KEY(workflow_id) REFERENCES automation_workflows(id) ON DELETE CASCADE,
  181. FOREIGN KEY(screen_id) REFERENCES automation_screens(id) ON DELETE SET NULL
  182. );
  183. CREATE INDEX IF NOT EXISTS idx_automation_workflow_nodes_workflow
  184. ON automation_workflow_nodes(workflow_id, node_index);
  185. CREATE TABLE IF NOT EXISTS automation_errors (
  186. id INTEGER PRIMARY KEY AUTOINCREMENT,
  187. workflow_id INTEGER,
  188. node_id INTEGER,
  189. screen_id INTEGER,
  190. action_type TEXT,
  191. message TEXT NOT NULL,
  192. similarity REAL,
  193. expected_image_path TEXT,
  194. actual_image_path TEXT,
  195. compare_result_json TEXT,
  196. created_at TEXT NOT NULL,
  197. FOREIGN KEY(workflow_id) REFERENCES automation_workflows(id) ON DELETE SET NULL,
  198. FOREIGN KEY(node_id) REFERENCES automation_workflow_nodes(id) ON DELETE SET NULL,
  199. FOREIGN KEY(screen_id) REFERENCES automation_screens(id) ON DELETE SET NULL
  200. );
  201. CREATE INDEX IF NOT EXISTS idx_automation_errors_created
  202. ON automation_errors(created_at DESC);
  203. """
  204. )
  205. seed_default_tags(conn)
  206. def seed_default_tags(conn: sqlite3.Connection) -> None:
  207. rows = [
  208. (
  209. "windows系统",
  210. "Windows 系统原生服务或进程。默认不可控制,避免误停止系统关键组件。",
  211. 0,
  212. 1,
  213. ),
  214. (
  215. "本系统相关",
  216. "本项目程序自身相关服务或进程。默认不可控制,避免通过监控系统停止自身。",
  217. 0,
  218. 1,
  219. ),
  220. ]
  221. now = __import__("datetime").datetime.now().astimezone().isoformat(timespec="seconds")
  222. for name, description, is_controllable, is_builtin in rows:
  223. conn.execute(
  224. """
  225. INSERT INTO tags (name, description, is_controllable, is_builtin, created_at, updated_at)
  226. VALUES (?, ?, ?, ?, ?, ?)
  227. ON CONFLICT(name) DO UPDATE SET
  228. description = COALESCE(tags.description, excluded.description),
  229. is_builtin = 1,
  230. updated_at = excluded.updated_at
  231. """,
  232. (name, description, is_controllable, is_builtin, now, now),
  233. )