ops-store.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. 'use strict';
  2. /**
  3. * ops 存储门面
  4. * ----------------------------------------------------------------------------
  5. * 读 config.storage 决定驱动(默认 SQLite),把驱动实例藏起来,对外只暴露业务接口。
  6. * 关键约束:初始化失败不能拖垮主服务 —— 所有失败都收敛成 health() 的 ok:false,
  7. * 只有「店铺运营」相关接口会返回 503。
  8. */
  9. const path = require('path');
  10. const ROOT_DIR = __dirname;
  11. function pickDriver(storage) {
  12. const raw = String((storage && storage.driver) || 'sqlite').toLowerCase();
  13. return raw === 'postgres' || raw === 'postgresql' || raw === 'pg' ? 'postgres' : 'sqlite';
  14. }
  15. function createOpsStore(config) {
  16. const storage = (config && config.storage) || {};
  17. const driver = pickDriver(storage);
  18. let impl = null;
  19. let initPromise = null;
  20. let lastFailure = null;
  21. function build() {
  22. if (driver === 'postgres') {
  23. const pgCfg = storage.postgres || {};
  24. const connectionString = process.env.OPS_DATABASE_URL || pgCfg.connectionString || '';
  25. if (!String(connectionString).trim()) {
  26. const err = new Error('未配置 PostgreSQL 连接串');
  27. err.reason = 'storage-not-configured';
  28. throw err;
  29. }
  30. const { createPgStore } = require('./server/stores/ops-pg.js');
  31. return createPgStore({ ...pgCfg, connectionString });
  32. }
  33. const sqliteCfg = storage.sqlite || {};
  34. const file = sqliteCfg.file
  35. ? path.resolve(ROOT_DIR, String(sqliteCfg.file))
  36. : path.join(ROOT_DIR, 'data', 'ops.db');
  37. const { createSqliteStore } = require('./server/stores/ops-sqlite.js');
  38. return createSqliteStore({ file });
  39. }
  40. function get() {
  41. if (impl) return impl;
  42. try {
  43. impl = build();
  44. lastFailure = null;
  45. return impl;
  46. } catch (e) {
  47. lastFailure = e;
  48. throw e;
  49. }
  50. }
  51. function reset() {
  52. impl = null;
  53. initPromise = null;
  54. }
  55. async function init() {
  56. if (!initPromise) {
  57. initPromise = Promise.resolve()
  58. .then(() => get())
  59. .then((store) => store.init())
  60. .then(() => get())
  61. .catch((e) => {
  62. lastFailure = e;
  63. initPromise = null;
  64. throw e;
  65. });
  66. }
  67. return initPromise;
  68. }
  69. /** never throws:{ ok, kind, reason?, detail? } */
  70. async function health() {
  71. try {
  72. await init();
  73. const result = await get().health();
  74. return { ...result, kind: driver };
  75. } catch (e) {
  76. return {
  77. ok: false,
  78. kind: driver,
  79. reason: e.reason || (e.code === 'MODULE_NOT_FOUND' ? 'driver-missing' : 'storage-unreachable'),
  80. detail: e.message || String(e),
  81. stack: undefined,
  82. };
  83. }
  84. }
  85. /** 包装:统一先 ensure init,再调用驱动方法 */
  86. function call(name) {
  87. return async (...args) => {
  88. await init();
  89. return get()[name](...args);
  90. };
  91. }
  92. return {
  93. kind: driver,
  94. init,
  95. health,
  96. reset,
  97. close: async () => {
  98. if (impl && typeof impl.close === 'function') {
  99. try { await impl.close(); } catch { /* ignore */ }
  100. }
  101. reset();
  102. },
  103. withTransaction: async (fn) => {
  104. await init();
  105. return get().withTransaction(fn);
  106. },
  107. findUploadBySha: call('findUploadBySha'),
  108. listUploads: call('listUploads'),
  109. getUpload: call('getUpload'),
  110. getContent: call('getContent'),
  111. queryRows: call('queryRows'),
  112. queryPlatformRows: call('queryPlatformRows'),
  113. insertReport: call('insertReport'),
  114. getReportCache: call('getReportCache'),
  115. saveReportCache: call('saveReportCache'),
  116. listReports: call('listReports'),
  117. getReport: call('getReport'),
  118. deleteReport: call('deleteReport'),
  119. summary: call('summary'),
  120. get failure() {
  121. return lastFailure;
  122. },
  123. };
  124. }
  125. module.exports = { createOpsStore, pickDriver };