@geekron/hono 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/dist/contract.d.ts +9 -1
  2. package/dist/contract.d.ts.map +1 -1
  3. package/dist/contract.js +14 -3
  4. package/dist/core/api/common.d.ts +2 -0
  5. package/dist/core/api/common.d.ts.map +1 -0
  6. package/dist/core/api/common.js +38 -0
  7. package/dist/core/api/inquiries.d.ts +2 -0
  8. package/dist/core/api/inquiries.d.ts.map +1 -0
  9. package/dist/core/api/inquiries.js +116 -0
  10. package/dist/core/api/inquiry.d.ts +2 -0
  11. package/dist/core/api/inquiry.d.ts.map +1 -0
  12. package/dist/core/api/inquiry.js +33 -0
  13. package/dist/core/api/visitor.d.ts +2 -0
  14. package/dist/core/api/visitor.d.ts.map +1 -0
  15. package/dist/core/api/visitor.js +152 -0
  16. package/dist/core/components/InquiryEmailTemplate.d.ts +30 -0
  17. package/dist/core/components/InquiryEmailTemplate.d.ts.map +1 -0
  18. package/dist/core/components/InquiryEmailTemplate.js +13 -0
  19. package/dist/core/components/Seo.d.ts +97 -0
  20. package/dist/core/components/Seo.d.ts.map +1 -0
  21. package/dist/core/components/Seo.js +365 -0
  22. package/dist/core/components/index.d.ts +3 -0
  23. package/dist/core/components/index.d.ts.map +1 -0
  24. package/dist/core/components/index.js +2 -0
  25. package/dist/core/index.d.ts +4 -0
  26. package/dist/core/index.d.ts.map +1 -0
  27. package/dist/core/index.js +3 -0
  28. package/dist/core/lib/constants/inquiry.d.ts +52 -0
  29. package/dist/core/lib/constants/inquiry.d.ts.map +1 -0
  30. package/dist/core/lib/constants/inquiry.js +51 -0
  31. package/dist/core/lib/constants/visitor.d.ts +29 -0
  32. package/dist/core/lib/constants/visitor.d.ts.map +1 -0
  33. package/dist/core/lib/constants/visitor.js +28 -0
  34. package/dist/core/lib/database/constants/database.d.ts +15 -0
  35. package/dist/core/lib/database/constants/database.d.ts.map +1 -0
  36. package/dist/core/lib/database/constants/database.js +14 -0
  37. package/dist/core/lib/database/database.service.d.ts +63 -0
  38. package/dist/core/lib/database/database.service.d.ts.map +1 -0
  39. package/dist/core/lib/database/database.service.js +108 -0
  40. package/dist/core/lib/database/index.d.ts +8 -0
  41. package/dist/core/lib/database/index.d.ts.map +1 -0
  42. package/dist/core/lib/database/index.js +7 -0
  43. package/dist/core/lib/database/managers/sqlite.d.ts +29 -0
  44. package/dist/core/lib/database/managers/sqlite.d.ts.map +1 -0
  45. package/dist/core/lib/database/managers/sqlite.js +55 -0
  46. package/dist/core/lib/database/managers/visitor-shard.d.ts +29 -0
  47. package/dist/core/lib/database/managers/visitor-shard.d.ts.map +1 -0
  48. package/dist/core/lib/database/managers/visitor-shard.js +83 -0
  49. package/dist/core/lib/database/repositories/inquiry.repository.d.ts +33 -0
  50. package/dist/core/lib/database/repositories/inquiry.repository.d.ts.map +1 -0
  51. package/dist/core/lib/database/repositories/inquiry.repository.js +173 -0
  52. package/dist/core/lib/database/repositories/visitor.repository.d.ts +51 -0
  53. package/dist/core/lib/database/repositories/visitor.repository.d.ts.map +1 -0
  54. package/dist/core/lib/database/repositories/visitor.repository.js +559 -0
  55. package/dist/core/lib/database/schemas/inquiry.schema.d.ts +323 -0
  56. package/dist/core/lib/database/schemas/inquiry.schema.d.ts.map +1 -0
  57. package/dist/core/lib/database/schemas/inquiry.schema.js +52 -0
  58. package/dist/core/lib/database/schemas/visitor.schema.d.ts +623 -0
  59. package/dist/core/lib/database/schemas/visitor.schema.d.ts.map +1 -0
  60. package/dist/core/lib/database/schemas/visitor.schema.js +162 -0
  61. package/dist/core/lib/services/captcha.service.d.ts +13 -0
  62. package/dist/core/lib/services/captcha.service.d.ts.map +1 -0
  63. package/dist/core/lib/services/captcha.service.js +40 -0
  64. package/dist/core/lib/services/geoip.service.d.ts +79 -0
  65. package/dist/core/lib/services/geoip.service.d.ts.map +1 -0
  66. package/dist/core/lib/services/geoip.service.js +144 -0
  67. package/dist/core/lib/services/index.d.ts +9 -0
  68. package/dist/core/lib/services/index.d.ts.map +1 -0
  69. package/dist/core/lib/services/index.js +8 -0
  70. package/dist/core/lib/services/inquiry.service.d.ts +56 -0
  71. package/dist/core/lib/services/inquiry.service.d.ts.map +1 -0
  72. package/dist/core/lib/services/inquiry.service.js +129 -0
  73. package/dist/core/lib/services/mailer.service.d.ts +23 -0
  74. package/dist/core/lib/services/mailer.service.d.ts.map +1 -0
  75. package/dist/core/lib/services/mailer.service.js +96 -0
  76. package/dist/core/lib/types/inquiry.d.ts +95 -0
  77. package/dist/core/lib/types/inquiry.d.ts.map +1 -0
  78. package/dist/core/lib/types/inquiry.js +1 -0
  79. package/dist/core/lib/types/visitor.d.ts +135 -0
  80. package/dist/core/lib/types/visitor.d.ts.map +1 -0
  81. package/dist/core/lib/types/visitor.js +1 -0
  82. package/dist/core/lib/utils/api.d.ts +25 -0
  83. package/dist/core/lib/utils/api.d.ts.map +1 -0
  84. package/dist/core/lib/utils/api.js +48 -0
  85. package/dist/core/lib/utils/request.d.ts +18 -0
  86. package/dist/core/lib/utils/request.d.ts.map +1 -0
  87. package/dist/core/lib/utils/request.js +25 -0
  88. package/dist/core/middlewares/api-guard.d.ts +39 -0
  89. package/dist/core/middlewares/api-guard.d.ts.map +1 -0
  90. package/dist/core/middlewares/api-guard.js +154 -0
  91. package/dist/core/middlewares/html-minifier.d.ts +55 -0
  92. package/dist/core/middlewares/html-minifier.d.ts.map +1 -0
  93. package/dist/core/middlewares/html-minifier.js +140 -0
  94. package/dist/core/middlewares/index.d.ts +5 -0
  95. package/dist/core/middlewares/index.d.ts.map +1 -0
  96. package/dist/core/middlewares/index.js +4 -0
  97. package/dist/core/middlewares/initializer.d.ts +11 -0
  98. package/dist/core/middlewares/initializer.d.ts.map +1 -0
  99. package/dist/core/middlewares/initializer.js +13 -0
  100. package/dist/core/middlewares/visitor-tracker.d.ts +85 -0
  101. package/dist/core/middlewares/visitor-tracker.d.ts.map +1 -0
  102. package/dist/core/middlewares/visitor-tracker.js +253 -0
  103. package/dist/core/template.d.ts +4 -0
  104. package/dist/core/template.d.ts.map +1 -0
  105. package/dist/core/template.js +7 -0
  106. package/dist/core/utils/formatDate.d.ts +3 -0
  107. package/dist/core/utils/formatDate.d.ts.map +1 -0
  108. package/dist/core/utils/formatDate.js +2 -0
  109. package/dist/core/utils/fullUrl.d.ts +2 -0
  110. package/dist/core/utils/fullUrl.d.ts.map +1 -0
  111. package/dist/core/utils/fullUrl.js +9 -0
  112. package/dist/core/utils/index.d.ts +6 -0
  113. package/dist/core/utils/index.d.ts.map +1 -0
  114. package/dist/core/utils/index.js +5 -0
  115. package/dist/core/utils/markdownify.d.ts +2 -0
  116. package/dist/core/utils/markdownify.d.ts.map +1 -0
  117. package/dist/core/utils/markdownify.js +4 -0
  118. package/dist/core/utils/params.d.ts +5 -0
  119. package/dist/core/utils/params.d.ts.map +1 -0
  120. package/dist/core/utils/params.js +14 -0
  121. package/dist/core/utils/routeUrl.d.ts +20 -0
  122. package/dist/core/utils/routeUrl.d.ts.map +1 -0
  123. package/dist/core/utils/routeUrl.js +111 -0
  124. package/dist/route/methods.d.ts +15 -0
  125. package/dist/route/methods.d.ts.map +1 -1
  126. package/dist/route/methods.js +45 -18
  127. package/dist/route/setup.d.ts +11 -12
  128. package/dist/route/setup.d.ts.map +1 -1
  129. package/dist/route/setup.js +26 -16
  130. package/dist/strapi/api/site.d.ts +2 -0
  131. package/dist/strapi/api/site.d.ts.map +1 -0
  132. package/dist/strapi/api/site.js +153 -0
  133. package/dist/strapi/cms/cms.js +1 -1
  134. package/dist/strapi/cms/common.d.ts.map +1 -1
  135. package/dist/strapi/cms/common.js +3 -1
  136. package/dist/strapi/cms/menu.d.ts +0 -9
  137. package/dist/strapi/cms/menu.d.ts.map +1 -1
  138. package/dist/strapi/cms/menu.js +3 -62
  139. package/dist/strapi/cms/setup.d.ts +2 -1
  140. package/dist/strapi/cms/setup.d.ts.map +1 -1
  141. package/dist/strapi/cms/setup.js +5 -0
  142. package/dist/strapi/cms/site.d.ts.map +1 -1
  143. package/dist/strapi/cms/site.js +3 -1
  144. package/dist/strapi/cms/translations.d.ts.map +1 -1
  145. package/dist/strapi/cms/translations.js +6 -2
  146. package/dist/strapi/i18n.d.ts +3 -0
  147. package/dist/strapi/i18n.d.ts.map +1 -0
  148. package/dist/strapi/i18n.js +27 -0
  149. package/dist/strapi/index.d.ts +1 -0
  150. package/dist/strapi/index.d.ts.map +1 -1
  151. package/dist/strapi/index.js +1 -0
  152. package/dist/strapi/interfaces/index.d.ts +1 -0
  153. package/dist/strapi/interfaces/index.d.ts.map +1 -1
  154. package/dist/strapi/interfaces/index.js +1 -0
  155. package/dist/strapi/interfaces/sitemap.d.ts +19 -0
  156. package/dist/strapi/interfaces/sitemap.d.ts.map +1 -0
  157. package/dist/strapi/interfaces/sitemap.js +1 -0
  158. package/dist/strapi/pages/sitemap/index.d.ts +2 -0
  159. package/dist/strapi/pages/sitemap/index.d.ts.map +1 -0
  160. package/dist/strapi/pages/sitemap/index.js +50 -0
  161. package/dist/strapi/pages/sitemap/index.xml +11 -0
  162. package/dist/strapi/pages/sitemap/list.d.ts +2 -0
  163. package/dist/strapi/pages/sitemap/list.d.ts.map +1 -0
  164. package/dist/strapi/pages/sitemap/list.js +123 -0
  165. package/dist/strapi/pages/sitemap/list.xml +28 -0
  166. package/dist/strapi/pages/sitemap/robots.d.ts +2 -0
  167. package/dist/strapi/pages/sitemap/robots.d.ts.map +1 -0
  168. package/dist/strapi/pages/sitemap/robots.js +19 -0
  169. package/dist/strapi/pages/sitemap/robots.txt +7 -0
  170. package/dist/strapi/pages/sitemap/style.d.ts +2 -0
  171. package/dist/strapi/pages/sitemap/style.d.ts.map +1 -0
  172. package/dist/strapi/pages/sitemap/style.js +17 -0
  173. package/dist/strapi/pages/sitemap/style.xsl +602 -0
  174. package/dist/strapi/utils/index.d.ts +1 -0
  175. package/dist/strapi/utils/index.d.ts.map +1 -1
  176. package/dist/strapi/utils/index.js +1 -0
  177. package/dist/strapi/utils/trans.d.ts +5 -0
  178. package/dist/strapi/utils/trans.d.ts.map +1 -0
  179. package/dist/strapi/utils/trans.js +7 -0
  180. package/package.json +20 -2
@@ -0,0 +1,108 @@
1
+ import * as fs from 'node:fs';
2
+ import { DATA_DIR } from './constants/database';
3
+ import { Sqlite } from './managers/sqlite';
4
+ import { InquiryRepository } from './repositories/inquiry.repository';
5
+ import { VisitorRepository } from './repositories/visitor.repository';
6
+ /**
7
+ * DatabaseService:聚合仓储,提供对外的业务友好 API。
8
+ */
9
+ export class DatabaseService {
10
+ constructor() {
11
+ this.initialized = false;
12
+ this.manager = new Sqlite();
13
+ this.inquiryRepo = new InquiryRepository(this.manager);
14
+ this.visitorRepo = new VisitorRepository(this.manager);
15
+ }
16
+ /** 初始化数据目录及各表结构 */
17
+ async init() {
18
+ if (this.initialized)
19
+ return;
20
+ if (!fs.existsSync(DATA_DIR))
21
+ fs.mkdirSync(DATA_DIR, { recursive: true });
22
+ await this.inquiryRepo.initSchema();
23
+ await this.visitorRepo.initShardSchemas();
24
+ this.initialized = true;
25
+ }
26
+ /** 询盘:创建 */
27
+ addInquiry(data) {
28
+ return this.inquiryRepo.create(data);
29
+ }
30
+ /** 询盘:按 ID 查询 */
31
+ getInquiryById(id) {
32
+ return this.inquiryRepo.findById(id);
33
+ }
34
+ /** 询盘:列表 */
35
+ listInquiries(params = {}) {
36
+ return this.inquiryRepo.list(params);
37
+ }
38
+ /** 询盘:计数 */
39
+ countInquiries(params = {}) {
40
+ return this.inquiryRepo.count(params);
41
+ }
42
+ /** 询盘:统计未读数量 */
43
+ countUnreadInquiries() {
44
+ return this.inquiryRepo.countUnread();
45
+ }
46
+ /** 询盘:标记已读 */
47
+ markInquiryAsRead(id, operatorId) {
48
+ return this.inquiryRepo.markAsRead(id, operatorId);
49
+ }
50
+ /** 访客:写入 */
51
+ addVisitor(data) {
52
+ return this.visitorRepo.insert(data);
53
+ }
54
+ /** 访客:查询页面浏览记录 */
55
+ queryPageViews(filter = {}) {
56
+ return this.visitorRepo.findPageViews(filter);
57
+ }
58
+ /** 访客:查询访客会话 */
59
+ queryVisitorSessions(filter = {}) {
60
+ return this.visitorRepo.findVisitorSessions(filter);
61
+ }
62
+ /** 访客:计数(页面浏览数) */
63
+ countVisitors(filter = {}) {
64
+ return this.visitorRepo.count(filter);
65
+ }
66
+ /** 访客:统计(向后兼容) */
67
+ visitorStats(by, filter = {}) {
68
+ return this.visitorRepo.stats(by, filter);
69
+ }
70
+ /**
71
+ * 统计独立访客数(UV)
72
+ */
73
+ getUVStats(filter = {}, byDate = false) {
74
+ return this.visitorRepo.getUVStats(filter, byDate);
75
+ }
76
+ /**
77
+ * 统计页面总浏览量(PV)
78
+ */
79
+ getPVStats(filter = {}, groupBy) {
80
+ return this.visitorRepo.getPVStats(filter, groupBy);
81
+ }
82
+ /**
83
+ * 统计每个页面每日的访问次数
84
+ */
85
+ getDailyPageViewStats(filter = {}) {
86
+ return this.visitorRepo.getDailyPageViewStats(filter);
87
+ }
88
+ /**
89
+ * 基于 IP 的页面访问次数统计
90
+ */
91
+ getIpPageViewStats(filter = {}) {
92
+ return this.visitorRepo.getIpPageViewStats(filter);
93
+ }
94
+ }
95
+ /**
96
+ * 全局单例数据库服务
97
+ */
98
+ let dbInstance = null;
99
+ /**
100
+ * 获取数据库服务实例(单例)
101
+ */
102
+ export async function getDatabase() {
103
+ if (!dbInstance) {
104
+ dbInstance = new DatabaseService();
105
+ await dbInstance.init();
106
+ }
107
+ return dbInstance;
108
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * 数据库模块导出
3
+ */
4
+ export * from './constants/database';
5
+ export * from './database.service';
6
+ export * from './schemas/inquiry.schema';
7
+ export * from './schemas/visitor.schema';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/core/lib/database/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,sBAAsB,CAAA;AACpC,cAAc,oBAAoB,CAAA;AAClC,cAAc,0BAA0B,CAAA;AACxC,cAAc,0BAA0B,CAAA"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 数据库模块导出
3
+ */
4
+ export * from './constants/database';
5
+ export * from './database.service';
6
+ export * from './schemas/inquiry.schema';
7
+ export * from './schemas/visitor.schema';
@@ -0,0 +1,29 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import type { BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite';
3
+ /**
4
+ * SQLite 连接管理器,负责按文件路径复用 bun:sqlite 实例并执行连接优化。
5
+ */
6
+ export declare class Sqlite {
7
+ private readonly cache;
8
+ private readonly dbCache;
9
+ /**
10
+ * 获取指定数据库文件的 Drizzle 实例(如不存在则创建)。
11
+ */
12
+ get(dbPath: string): BunSQLiteDatabase;
13
+ /**
14
+ * 获取原始 Database 实例(用于 DDL 操作)
15
+ */
16
+ getRaw(dbPath: string): Database;
17
+ /**
18
+ * 连接后执行的优化配置(WAL、外键、超时等)。
19
+ */
20
+ private afterConnect;
21
+ /**
22
+ * 当前已打开的连接列表。
23
+ */
24
+ listOpen(): {
25
+ path: string;
26
+ db: BunSQLiteDatabase;
27
+ }[];
28
+ }
29
+ //# sourceMappingURL=sqlite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../../../../src/core/lib/database/managers/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAErC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAI/D;;GAEG;AACH,qBAAa,MAAM;IAClB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuC;IAC7D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8B;IAEtD;;OAEG;IACH,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB;IAYtC;;OAEG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,GAGe,QAAQ;IAG5C;;OAEG;IACH,OAAO,CAAC,YAAY;IAOpB;;OAEG;IACH,QAAQ,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,iBAAiB,CAAA;KAAE,EAAE;CAMrD"}
@@ -0,0 +1,55 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import * as fs from 'node:fs';
3
+ import { drizzle } from 'drizzle-orm/bun-sqlite';
4
+ import { DATA_DIR } from '../../../../core/lib/database';
5
+ /**
6
+ * SQLite 连接管理器,负责按文件路径复用 bun:sqlite 实例并执行连接优化。
7
+ */
8
+ export class Sqlite {
9
+ constructor() {
10
+ this.cache = new Map();
11
+ this.dbCache = new Map();
12
+ }
13
+ /**
14
+ * 获取指定数据库文件的 Drizzle 实例(如不存在则创建)。
15
+ */
16
+ get(dbPath) {
17
+ const existing = this.cache.get(dbPath);
18
+ if (existing)
19
+ return existing;
20
+ if (!fs.existsSync(DATA_DIR))
21
+ fs.mkdirSync(DATA_DIR, { recursive: true });
22
+ const db = new Database(dbPath, { create: true });
23
+ this.afterConnect(db);
24
+ const drizzleDb = drizzle(db);
25
+ this.cache.set(dbPath, drizzleDb);
26
+ this.dbCache.set(dbPath, db);
27
+ return drizzleDb;
28
+ }
29
+ /**
30
+ * 获取原始 Database 实例(用于 DDL 操作)
31
+ */
32
+ getRaw(dbPath) {
33
+ // 确保 drizzle 实例已创建
34
+ this.get(dbPath);
35
+ return this.dbCache.get(dbPath);
36
+ }
37
+ /**
38
+ * 连接后执行的优化配置(WAL、外键、超时等)。
39
+ */
40
+ afterConnect(db) {
41
+ db.run('PRAGMA journal_mode = WAL');
42
+ db.run('PRAGMA synchronous = NORMAL');
43
+ db.run('PRAGMA foreign_keys = ON');
44
+ db.run('PRAGMA busy_timeout = 2000');
45
+ }
46
+ /**
47
+ * 当前已打开的连接列表。
48
+ */
49
+ listOpen() {
50
+ return Array.from(this.cache.entries()).map(([p, db]) => ({
51
+ path: p,
52
+ db: db,
53
+ }));
54
+ }
55
+ }
@@ -0,0 +1,29 @@
1
+ import type { BunSQLiteDatabase } from 'drizzle-orm/bun-sqlite';
2
+ import type { VisitorShardStrategy } from '../../../../core/lib/database';
3
+ import type { Sqlite } from './sqlite';
4
+ /**
5
+ * 访客分片管理器:按顺序或日期管理 SQLite 分片数据库文件,并提供对应的 Drizzle 实例。
6
+ */
7
+ export declare class VisitorShard {
8
+ private readonly manager;
9
+ private strategy;
10
+ constructor(manager: Sqlite);
11
+ /** 切换分片策略 */
12
+ setStrategy(strategy: VisitorShardStrategy): void;
13
+ /** 当前写入分片文件路径(不足容量则复用,否则生成新分片)。 */
14
+ currentShardPath(): string;
15
+ /** 列出所有分片文件名 */
16
+ listShardFiles(): string[];
17
+ /** 列出所有分片绝对路径 */
18
+ allShardPaths(): string[];
19
+ /** 获取当前分片的 Drizzle 实例 */
20
+ getCurrentDb(): BunSQLiteDatabase;
21
+ /** 获取已存在的全部分片 Drizzle 实例(若无则创建当前分片) */
22
+ getAllDbs(): {
23
+ path: string;
24
+ db: BunSQLiteDatabase;
25
+ }[];
26
+ /** 到达容量上限时触发旋转(销毁旧连接,下一次写入自动落到新分片)。 */
27
+ rotateIfNeeded(shardPath: string): void;
28
+ }
29
+ //# sourceMappingURL=visitor-shard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visitor-shard.d.ts","sourceRoot":"","sources":["../../../../../src/core/lib/database/managers/visitor-shard.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC/D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAE/D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAEtC;;GAEG;AACH,qBAAa,YAAY;IAEZ,OAAO,CAAC,QAAQ,CAAC,OAAO;IADpC,OAAO,CAAC,QAAQ,CAAmC;gBACtB,OAAO,EAAE,MAAM;IAE5C,aAAa;IACb,WAAW,CAAC,QAAQ,EAAE,oBAAoB;IAI1C,mCAAmC;IACnC,gBAAgB,IAAI,MAAM;IA+B1B,gBAAgB;IAChB,cAAc,IAAI,MAAM,EAAE;IAM1B,iBAAiB;IACjB,aAAa,IAAI,MAAM,EAAE;IAIzB,yBAAyB;IACzB,YAAY,IAAI,iBAAiB;IAKjC,uCAAuC;IACvC,SAAS,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,iBAAiB,CAAA;KAAE,EAAE;IAStD,uCAAuC;IACvC,cAAc,CAAC,SAAS,EAAE,MAAM;CAUhC"}
@@ -0,0 +1,83 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import dayjs from 'dayjs';
4
+ import { DATA_DIR, MAX_DB_SIZE_BYTES } from '../../../../core/lib/database';
5
+ /**
6
+ * 访客分片管理器:按顺序或日期管理 SQLite 分片数据库文件,并提供对应的 Drizzle 实例。
7
+ */
8
+ export class VisitorShard {
9
+ constructor(manager) {
10
+ this.manager = manager;
11
+ this.strategy = 'sequence';
12
+ }
13
+ /** 切换分片策略 */
14
+ setStrategy(strategy) {
15
+ this.strategy = strategy;
16
+ }
17
+ /** 当前写入分片文件路径(不足容量则复用,否则生成新分片)。 */
18
+ currentShardPath() {
19
+ if (this.strategy === 'date') {
20
+ const base = `visitor_${dayjs().format('YYYYMMDD')}.db`;
21
+ const p = path.join(DATA_DIR, base);
22
+ if (fs.existsSync(p)) {
23
+ const s = fs.statSync(p).size;
24
+ if (s < MAX_DB_SIZE_BYTES)
25
+ return p;
26
+ }
27
+ }
28
+ const files = this.listShardFiles();
29
+ const seqNumbers = files
30
+ .map((f) => {
31
+ const m = f.match(/^visitor_(\d{3})\.db$/);
32
+ return m ? parseInt(m[1], 10) : null;
33
+ })
34
+ .filter((n) => typeof n === 'number');
35
+ const last = seqNumbers.length ? Math.max(...seqNumbers) : 0;
36
+ const lastFile = last
37
+ ? path.join(DATA_DIR, `visitor_${String(last).padStart(3, '0')}.db`)
38
+ : null;
39
+ if (lastFile &&
40
+ fs.existsSync(lastFile) &&
41
+ fs.statSync(lastFile).size < MAX_DB_SIZE_BYTES) {
42
+ return lastFile;
43
+ }
44
+ const next = String(last + 1).padStart(3, '0');
45
+ return path.join(DATA_DIR, `visitor_${next}.db`);
46
+ }
47
+ /** 列出所有分片文件名 */
48
+ listShardFiles() {
49
+ if (!fs.existsSync(DATA_DIR))
50
+ return [];
51
+ const names = fs.readdirSync(DATA_DIR);
52
+ return names.filter((n) => n.startsWith('visitor_') && n.endsWith('.db'));
53
+ }
54
+ /** 列出所有分片绝对路径 */
55
+ allShardPaths() {
56
+ return this.listShardFiles().map((n) => path.join(DATA_DIR, n));
57
+ }
58
+ /** 获取当前分片的 Drizzle 实例 */
59
+ getCurrentDb() {
60
+ const p = this.currentShardPath();
61
+ return this.manager.get(p);
62
+ }
63
+ /** 获取已存在的全部分片 Drizzle 实例(若无则创建当前分片) */
64
+ getAllDbs() {
65
+ const paths = this.allShardPaths();
66
+ if (!paths.length) {
67
+ const db = this.getCurrentDb();
68
+ return [{ path: this.currentShardPath(), db }];
69
+ }
70
+ return paths.map((p) => ({ path: p, db: this.manager.get(p) }));
71
+ }
72
+ /** 到达容量上限时触发旋转(销毁旧连接,下一次写入自动落到新分片)。 */
73
+ rotateIfNeeded(shardPath) {
74
+ if (fs.existsSync(shardPath)) {
75
+ const size = fs.statSync(shardPath).size;
76
+ if (size >= MAX_DB_SIZE_BYTES) {
77
+ // 获取原始 Database 实例进行关闭
78
+ const db = this.manager.getRaw(shardPath);
79
+ db.close();
80
+ }
81
+ }
82
+ }
83
+ }
@@ -0,0 +1,33 @@
1
+ import type { Inquiry, InquiryCreate, InquiryListParams } from '../../../../core/lib/database';
2
+ import type { Sqlite } from '../managers/sqlite';
3
+ /**
4
+ * 询盘仓储:封装对 SQLite 的 CRUD 操作。
5
+ */
6
+ export declare class InquiryRepository {
7
+ private readonly manager;
8
+ constructor(manager: Sqlite);
9
+ /** 获取询盘数据库连接 */
10
+ private get db();
11
+ /** 获取原始 Database 实例 */
12
+ private get rawDb();
13
+ /** 初始化表结构(幂等) */
14
+ initSchema(): Promise<void>;
15
+ /** 创建询盘(事务) */
16
+ create(data: InquiryCreate): Promise<number>;
17
+ /** 根据 ID 查询询盘 */
18
+ findById(id: number): Promise<Inquiry | null>;
19
+ /** 列表查询 */
20
+ list(params?: InquiryListParams): Promise<Inquiry[]>;
21
+ count(params?: InquiryListParams): Promise<number>;
22
+ /**
23
+ * 统计未读询盘数量
24
+ */
25
+ countUnread(): Promise<number>;
26
+ /**
27
+ * 标记询盘为已读
28
+ * @param id 询盘 ID
29
+ * @param operatorId 操作人 ID
30
+ */
31
+ markAsRead(id: number, operatorId: number): Promise<boolean>;
32
+ }
33
+ //# sourceMappingURL=inquiry.repository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inquiry.repository.d.ts","sourceRoot":"","sources":["../../../../../src/core/lib/database/repositories/inquiry.repository.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACX,OAAO,EACP,aAAa,EACb,iBAAiB,EACjB,MAAM,qBAAqB,CAAA;AAM5B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAEhD;;GAEG;AACH,qBAAa,iBAAiB;IACjB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,MAAM;IAE5C,gBAAgB;IAChB,OAAO,KAAK,EAAE,GAEb;IAED,uBAAuB;IACvB,OAAO,KAAK,KAAK,GAEhB;IAED,iBAAiB;IACX,UAAU;IAIhB,eAAe;IACT,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC;IA0BlD,iBAAiB;IACX,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IA2BnD,WAAW;IACL,IAAI,CAAC,MAAM,GAAE,iBAAsB,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IA6DxD,KAAK,CAAC,MAAM,GAAE,iBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC;IAwB5D;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC;IASpC;;;;OAIG;IACG,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAalE"}
@@ -0,0 +1,173 @@
1
+ import dayjs from 'dayjs';
2
+ import { and, asc, count, desc, eq, gte, like, lt } from 'drizzle-orm';
3
+ import { ensureInquirySchema, INQUIRY_DB_PATH, inquiries, } from '../../../../core/lib/database';
4
+ /**
5
+ * 询盘仓储:封装对 SQLite 的 CRUD 操作。
6
+ */
7
+ export class InquiryRepository {
8
+ constructor(manager) {
9
+ this.manager = manager;
10
+ }
11
+ /** 获取询盘数据库连接 */
12
+ get db() {
13
+ return this.manager.get(INQUIRY_DB_PATH);
14
+ }
15
+ /** 获取原始 Database 实例 */
16
+ get rawDb() {
17
+ return this.manager.getRaw(INQUIRY_DB_PATH);
18
+ }
19
+ /** 初始化表结构(幂等) */
20
+ async initSchema() {
21
+ ensureInquirySchema(this.rawDb);
22
+ }
23
+ /** 创建询盘(事务) */
24
+ async create(data) {
25
+ const createdAt = data.createdAt
26
+ ? dayjs(data.createdAt).toISOString()
27
+ : dayjs().toISOString();
28
+ const result = this.db
29
+ .insert(inquiries)
30
+ .values({
31
+ firstName: data.firstName,
32
+ lastName: data.lastName,
33
+ email: data.email,
34
+ phone: data.phone,
35
+ message: data.message,
36
+ userAgent: data.userAgent ?? null,
37
+ ip: data.ip ?? null,
38
+ data: data.data ? JSON.stringify(data.data) : null,
39
+ referer: data.referer ?? null,
40
+ geo: data.geo ? JSON.stringify(data.geo) : null,
41
+ createdAt,
42
+ })
43
+ .returning({ id: inquiries.id });
44
+ const rows = result.all();
45
+ return rows[0]?.id ?? 0;
46
+ }
47
+ /** 根据 ID 查询询盘 */
48
+ async findById(id) {
49
+ const row = this.db
50
+ .select()
51
+ .from(inquiries)
52
+ .where(eq(inquiries.id, id))
53
+ .get();
54
+ if (!row)
55
+ return null;
56
+ return {
57
+ id: row.id,
58
+ firstName: row.firstName,
59
+ lastName: row.lastName,
60
+ email: row.email,
61
+ phone: row.phone,
62
+ message: row.message,
63
+ userAgent: row.userAgent ?? null,
64
+ ip: row.ip ?? null,
65
+ data: row.data ? JSON.parse(row.data) : null,
66
+ referer: row.referer ?? null,
67
+ geo: row.geo ? JSON.parse(row.geo) : null,
68
+ isRead: row.isRead ?? false,
69
+ operatorId: row.operatorId ?? null,
70
+ createdAt: row.createdAt,
71
+ };
72
+ }
73
+ /** 列表查询 */
74
+ async list(params = {}) {
75
+ const conditions = [];
76
+ if (params.start) {
77
+ conditions.push(gte(inquiries.createdAt, dayjs(params.start).toISOString()));
78
+ }
79
+ if (params.end) {
80
+ conditions.push(lt(inquiries.createdAt, dayjs(params.end).toISOString()));
81
+ }
82
+ if (params.keyword) {
83
+ conditions.push(like(inquiries.message, `%${params.keyword}%`));
84
+ }
85
+ let query = this.db.select().from(inquiries).$dynamic();
86
+ if (conditions.length > 0) {
87
+ query = query.where(and(...conditions));
88
+ }
89
+ // 排序
90
+ if (params.sortBy === 'email') {
91
+ query =
92
+ params.order === 'asc'
93
+ ? query.orderBy(asc(inquiries.email))
94
+ : query.orderBy(desc(inquiries.email));
95
+ }
96
+ else {
97
+ query =
98
+ params.order === 'asc'
99
+ ? query.orderBy(asc(inquiries.createdAt))
100
+ : query.orderBy(desc(inquiries.createdAt));
101
+ }
102
+ // 分页
103
+ if (params.limit) {
104
+ query = query.limit(params.limit);
105
+ }
106
+ if (params.offset) {
107
+ query = query.offset(params.offset);
108
+ }
109
+ const rows = query.all();
110
+ return rows.map((row) => ({
111
+ id: row.id,
112
+ firstName: row.firstName,
113
+ lastName: row.lastName,
114
+ email: row.email,
115
+ phone: row.phone,
116
+ message: row.message,
117
+ userAgent: row.userAgent ?? null,
118
+ ip: row.ip ?? null,
119
+ data: row.data ? JSON.parse(row.data) : null,
120
+ referer: row.referer ?? null,
121
+ geo: row.geo ? JSON.parse(row.geo) : null,
122
+ isRead: row.isRead ?? false,
123
+ operatorId: row.operatorId ?? null,
124
+ createdAt: row.createdAt,
125
+ }));
126
+ }
127
+ async count(params = {}) {
128
+ const conditions = [];
129
+ if (params.start) {
130
+ conditions.push(gte(inquiries.createdAt, dayjs(params.start).toISOString()));
131
+ }
132
+ if (params.end) {
133
+ conditions.push(lt(inquiries.createdAt, dayjs(params.end).toISOString()));
134
+ }
135
+ if (params.keyword) {
136
+ conditions.push(like(inquiries.message, `%${params.keyword}%`));
137
+ }
138
+ let query = this.db.select({ c: count() }).from(inquiries);
139
+ if (conditions.length > 0) {
140
+ query = query.where(and(...conditions));
141
+ }
142
+ const result = query.get();
143
+ return result?.c ?? 0;
144
+ }
145
+ /**
146
+ * 统计未读询盘数量
147
+ */
148
+ async countUnread() {
149
+ const result = this.db
150
+ .select({ c: count() })
151
+ .from(inquiries)
152
+ .where(eq(inquiries.isRead, false))
153
+ .get();
154
+ return result?.c ?? 0;
155
+ }
156
+ /**
157
+ * 标记询盘为已读
158
+ * @param id 询盘 ID
159
+ * @param operatorId 操作人 ID
160
+ */
161
+ async markAsRead(id, operatorId) {
162
+ const result = this.db
163
+ .update(inquiries)
164
+ .set({
165
+ isRead: true,
166
+ operatorId,
167
+ })
168
+ .where(eq(inquiries.id, id))
169
+ .returning({ id: inquiries.id });
170
+ const rows = result.all();
171
+ return rows.length > 0;
172
+ }
173
+ }
@@ -0,0 +1,51 @@
1
+ import type { DailyPageViewStats, IpPageViewStats, PageView, PVStats, UVStats, VisitorCreate, VisitorQuery, VisitorSession, VisitorStatsBy } from '../../../../core/lib/database';
2
+ import type { Sqlite } from '../managers/sqlite';
3
+ /**
4
+ * 访客仓储:封装跨分片的读写与统计。
5
+ */
6
+ export declare class VisitorRepository {
7
+ private readonly manager;
8
+ private readonly shardManager;
9
+ constructor(manager: Sqlite);
10
+ /** 初始化已有分片的表结构(幂等),若无分片则创建当前分片 */
11
+ initShardSchemas(): Promise<void>;
12
+ /**
13
+ * 写入访客记录(事务),并在必要时触发分片旋转。
14
+ */
15
+ insert(data: VisitorCreate): Promise<{
16
+ id: number;
17
+ shardPath: string;
18
+ }>;
19
+ /**
20
+ * 统计独立访客数(UV)
21
+ */
22
+ getUVStats(filter?: VisitorQuery, byDate?: boolean): Promise<UVStats>;
23
+ /**
24
+ * 统计页面总浏览量(PV)
25
+ */
26
+ getPVStats(filter?: VisitorQuery, groupBy?: 'page' | 'date'): Promise<PVStats>;
27
+ /**
28
+ * 统计每个页面每日的访问次数(从预聚合表查询)
29
+ */
30
+ getDailyPageViewStats(filter?: VisitorQuery): Promise<DailyPageViewStats[]>;
31
+ /**
32
+ * 统计基于 IP 的页面访问次数(从预聚合表查询)
33
+ */
34
+ getIpPageViewStats(filter?: VisitorQuery): Promise<IpPageViewStats[]>;
35
+ /**
36
+ * 跨分片查询页面浏览记录
37
+ */
38
+ findPageViews(filter?: VisitorQuery): Promise<PageView[]>;
39
+ /**
40
+ * 跨分片查询访客会话
41
+ */
42
+ findVisitorSessions(filter?: VisitorQuery): Promise<VisitorSession[]>;
43
+ /** 跨分片计数 */
44
+ count(filter?: VisitorQuery): Promise<number>;
45
+ /** 跨分片统计(向后兼容) */
46
+ stats(by: VisitorStatsBy, filter?: VisitorQuery): Promise<{
47
+ key: string;
48
+ count: number;
49
+ }[]>;
50
+ }
51
+ //# sourceMappingURL=visitor.repository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visitor.repository.d.ts","sourceRoot":"","sources":["../../../../../src/core/lib/database/repositories/visitor.repository.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACX,kBAAkB,EAClB,eAAe,EACf,QAAQ,EACR,OAAO,EACP,OAAO,EACP,aAAa,EACb,YAAY,EACZ,cAAc,EACd,cAAc,EACd,MAAM,qBAAqB,CAAA;AAS5B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAGhD;;GAEG;AACH,qBAAa,iBAAiB;IAGjB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAc;gBAEd,OAAO,EAAE,MAAM;IAI5C,kCAAkC;IAC5B,gBAAgB;IAQtB;;OAEG;IACG,MAAM,CACX,IAAI,EAAE,aAAa,GACjB,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAkK7C;;OAEG;IACG,UAAU,CACf,MAAM,GAAE,YAAiB,EACzB,MAAM,UAAQ,GACZ,OAAO,CAAC,OAAO,CAAC;IA0FnB;;OAEG;IACG,UAAU,CACf,MAAM,GAAE,YAAiB,EACzB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GACvB,OAAO,CAAC,OAAO,CAAC;IAqInB;;OAEG;IACG,qBAAqB,CAC1B,MAAM,GAAE,YAAiB,GACvB,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAkDhC;;OAEG;IACG,kBAAkB,CACvB,MAAM,GAAE,YAAiB,GACvB,OAAO,CAAC,eAAe,EAAE,CAAC;IA6C7B;;OAEG;IACG,aAAa,CAAC,MAAM,GAAE,YAAiB,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IA+CnE;;OAEG;IACG,mBAAmB,CACxB,MAAM,GAAE,YAAiB,GACvB,OAAO,CAAC,cAAc,EAAE,CAAC;IA6C5B,YAAY;IACN,KAAK,CAAC,MAAM,GAAE,YAAiB,GAAG,OAAO,CAAC,MAAM,CAAC;IAiCvD,kBAAkB;IACZ,KAAK,CACV,EAAE,EAAE,cAAc,EAClB,MAAM,GAAE,YAAiB,GACvB,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAwB5C"}