@geekron/hono 0.1.0 → 0.1.2

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 @@
1
+ {"version":3,"file":"mailer.service.d.ts","sourceRoot":"","sources":["../../../../src/core/lib/services/mailer.service.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAYH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAwC3D;;;;GAIG;AACH,wBAAsB,gBAAgB,CACrC,aAAa,EAAE,kBAAkB,GAC/B,OAAO,CAAC,OAAO,CAAC,CA+BlB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,kBAAkB,GAAG,IAAI,CAK5E;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,OAAO,CAAC,CAU/D"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * 邮件发送服务
3
+ * 使用 nodemailer 发送邮件
4
+ */
5
+ import { MAIL_FROM, MAIL_HOST, MAIL_PASS, MAIL_PORT, MAIL_SECURE, MAIL_TO, MAIL_USER, } from '../../../contract';
6
+ import { InquiryEmailTemplate } from '../../../core/components';
7
+ import { getSite } from '../../../strapi';
8
+ /**
9
+ * 邮件配置
10
+ * 从环境变量中读取配置
11
+ */
12
+ const MAIL_CONFIG = {
13
+ host: MAIL_HOST,
14
+ port: MAIL_PORT,
15
+ secure: MAIL_SECURE,
16
+ auth: {
17
+ user: MAIL_USER,
18
+ pass: MAIL_PASS,
19
+ },
20
+ };
21
+ /**
22
+ * 延迟加载 nodemailer
23
+ */
24
+ let transporter = null;
25
+ async function getTransporter() {
26
+ if (!transporter) {
27
+ const nodemailer = await import('nodemailer');
28
+ transporter = nodemailer.default.createTransport(MAIL_CONFIG);
29
+ }
30
+ return transporter;
31
+ }
32
+ /**
33
+ * 将 JSX 组件渲染为 HTML 字符串
34
+ * Hono 的 JSX 运行时会自动处理
35
+ */
36
+ function renderToString(component) {
37
+ // Hono JSX 组件可以直接转换为字符串
38
+ return String(component);
39
+ }
40
+ /**
41
+ * 发送询盘邮件
42
+ * @param inquiryValues - 询盘数据
43
+ * @returns Promise<boolean> - 发送成功返回 true,失败返回 false
44
+ */
45
+ export async function sendInquiryEmail(inquiryValues) {
46
+ try {
47
+ console.log('[Mailer] Preparing to send inquiry email, ID:', inquiryValues.id);
48
+ // 渲染邮件模板
49
+ const emailTemplate = InquiryEmailTemplate({ values: inquiryValues });
50
+ const htmlContent = renderToString(emailTemplate);
51
+ const site = getSite();
52
+ // 邮件选项
53
+ const mailOptions = {
54
+ from: MAIL_FROM,
55
+ to: site?.inquiryEmail || MAIL_TO,
56
+ subject: `[询盘通知] 新询盘 #${inquiryValues.id} - ${inquiryValues.firstName} ${inquiryValues.lastName}`,
57
+ html: htmlContent,
58
+ };
59
+ // 发送邮件
60
+ const mailer = await getTransporter();
61
+ const info = await mailer.sendMail(mailOptions);
62
+ console.log('[Mailer] Email sent successfully:', info.messageId);
63
+ return true;
64
+ }
65
+ catch (error) {
66
+ console.error('[Mailer] Failed to send email:', error);
67
+ return false;
68
+ }
69
+ }
70
+ /**
71
+ * 异步发送询盘邮件(非阻塞)
72
+ * 使用 Promise 但不等待结果,避免阻塞主流程
73
+ * @param inquiryValue - 询盘数据
74
+ */
75
+ export function sendInquiryEmailAsync(inquiryValue) {
76
+ // 异步发送,不等待结果
77
+ sendInquiryEmail(inquiryValue).catch((error) => {
78
+ console.error('[Mailer] Async email sending failed:', error);
79
+ });
80
+ }
81
+ /**
82
+ * 验证邮件服务连接
83
+ * @returns Promise<boolean> - 连接成功返回 true
84
+ */
85
+ export async function verifyMailerConnection() {
86
+ try {
87
+ const mailer = await getTransporter();
88
+ await mailer.verify();
89
+ console.log('[Mailer] Connection verified successfully');
90
+ return true;
91
+ }
92
+ catch (error) {
93
+ console.error('[Mailer] Connection verification failed:', error);
94
+ return false;
95
+ }
96
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * 询盘列表查询参数
3
+ */
4
+ export interface InquiryListQueryDto {
5
+ /** 页码 */
6
+ page?: number;
7
+ /** 每页条数 */
8
+ pageSize?: number;
9
+ /** 搜索关键词 */
10
+ keyword?: string;
11
+ /** 开始时间 */
12
+ start?: string;
13
+ /** 结束时间 */
14
+ end?: string;
15
+ /** 排序字段 */
16
+ sortBy?: 'createdAt' | 'email';
17
+ /** 排序方向 */
18
+ order?: 'asc' | 'desc';
19
+ }
20
+ /**
21
+ * 询盘提交数据
22
+ */
23
+ export interface InquiryCreateDto {
24
+ /** hCaptcha token */
25
+ hcaptcha_token: string;
26
+ /** hCaptcha sitekey */
27
+ hcaptcha_sitekey: string;
28
+ /** 邮箱 */
29
+ email: string;
30
+ /** 名 */
31
+ first_name: string;
32
+ /** 姓 */
33
+ last_name: string;
34
+ /** 消息内容 */
35
+ message: string;
36
+ /** 电话 */
37
+ phone?: string;
38
+ /** 来源页面 */
39
+ referer?: string;
40
+ /** 其他扩展数据 */
41
+ [key: string]: any;
42
+ }
43
+ /**
44
+ * API 响应格式
45
+ */
46
+ export interface ApiResponse<T = any> {
47
+ /** 业务状态码 */
48
+ code: number;
49
+ /** 响应消息 */
50
+ message: string;
51
+ /** 响应数据 */
52
+ data: T;
53
+ /** 时间戳 */
54
+ timestamp: string;
55
+ }
56
+ /**
57
+ * 分页信息
58
+ */
59
+ export interface PaginationMeta {
60
+ /** 当前页码 */
61
+ page: number;
62
+ /** 每页条数 */
63
+ pageSize: number;
64
+ /** 总记录数 */
65
+ total: number;
66
+ }
67
+ /**
68
+ * 分页列表响应数据
69
+ */
70
+ export interface PagedListData<T> {
71
+ /** 数据列表 */
72
+ items: T[];
73
+ /** 分页信息 */
74
+ pagination: PaginationMeta;
75
+ }
76
+ /**
77
+ * 询盘详情
78
+ */
79
+ export interface Inquiry {
80
+ id: number;
81
+ firstName: string;
82
+ lastName: string;
83
+ email: string;
84
+ phone: string;
85
+ message: string;
86
+ userAgent: string | null;
87
+ ip: string | null;
88
+ data: Record<string, any> | null;
89
+ referer: string | null;
90
+ geo: Record<string, any> | null;
91
+ isRead: boolean;
92
+ operatorId: number | null;
93
+ createdAt: string;
94
+ }
95
+ //# sourceMappingURL=inquiry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inquiry.d.ts","sourceRoot":"","sources":["../../../../src/core/lib/types/inquiry.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC,SAAS;IACT,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW;IACX,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,YAAY;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW;IACX,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW;IACX,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,WAAW;IACX,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAA;IAC9B,WAAW;IACX,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAAA;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC,qBAAqB;IACrB,cAAc,EAAE,MAAM,CAAA;IACtB,uBAAuB;IACvB,gBAAgB,EAAE,MAAM,CAAA;IACxB,SAAS;IACT,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ;IACR,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ;IACR,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW;IACX,OAAO,EAAE,MAAM,CAAA;IACf,SAAS;IACT,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW;IACX,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,aAAa;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,GAAG;IACnC,YAAY;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW;IACX,OAAO,EAAE,MAAM,CAAA;IACf,WAAW;IACX,IAAI,EAAE,CAAC,CAAA;IACP,UAAU;IACV,SAAS,EAAE,MAAM,CAAA;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,WAAW;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW;IACX,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW;IACX,KAAK,EAAE,MAAM,CAAA;CACb;AAED;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC;IAC/B,WAAW;IACX,KAAK,EAAE,CAAC,EAAE,CAAA;IACV,WAAW;IACX,UAAU,EAAE,cAAc,CAAA;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,OAAO;IACvB,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;IACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAA;IAChC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAA;IAC/B,MAAM,EAAE,OAAO,CAAA;IACf,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,SAAS,EAAE,MAAM,CAAA;CACjB"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,135 @@
1
+ /**
2
+ * 访客统计查询参数
3
+ */
4
+ export interface VisitorStatsQueryDto {
5
+ /** 开始时间 (ISO 8601 格式) */
6
+ start?: string;
7
+ /** 结束时间 (ISO 8601 格式) */
8
+ end?: string;
9
+ /** 按日期分组 */
10
+ byDate?: boolean;
11
+ }
12
+ /**
13
+ * 页面浏览统计查询参数
14
+ */
15
+ export interface PageViewStatsQueryDto {
16
+ /** 开始时间 (ISO 8601 格式) */
17
+ start?: string;
18
+ /** 结束时间 (ISO 8601 格式) */
19
+ end?: string;
20
+ /** 分组方式: page(按页面), date(按日期) */
21
+ groupBy?: 'page' | 'date';
22
+ /** 页面路径过滤 */
23
+ page?: string;
24
+ /** IP 过滤 */
25
+ ip?: string;
26
+ }
27
+ /**
28
+ * 每日页面访问统计查询参数
29
+ */
30
+ export interface DailyPageStatsQueryDto {
31
+ /** 开始日期 (YYYY-MM-DD) */
32
+ start?: string;
33
+ /** 结束日期 (YYYY-MM-DD) */
34
+ end?: string;
35
+ /** 页面路径过滤 */
36
+ page?: string;
37
+ }
38
+ /**
39
+ * IP 页面访问统计查询参数
40
+ */
41
+ export interface IpPageStatsQueryDto {
42
+ /** IP 地址过滤 */
43
+ ip?: string;
44
+ /** 页面路径过滤 */
45
+ page?: string;
46
+ }
47
+ /**
48
+ * 访客记录查询参数
49
+ */
50
+ export interface VisitorQueryDto {
51
+ /** 开始时间 (ISO 8601 格式) */
52
+ start?: string;
53
+ /** 结束时间 (ISO 8601 格式) */
54
+ end?: string;
55
+ /** IP 地址过滤 */
56
+ ip?: string;
57
+ /** 页面路径过滤 */
58
+ page?: string;
59
+ /** 每页条数 */
60
+ limit?: number;
61
+ /** 偏移量 */
62
+ offset?: number;
63
+ }
64
+ /**
65
+ * UV 统计响应接口
66
+ */
67
+ export interface UVStatsResponse {
68
+ /** 总独立访客数 */
69
+ total: number;
70
+ /** 按日期分组的统计(可选)*/
71
+ byDate?: Array<{
72
+ date: string;
73
+ count: number;
74
+ }>;
75
+ }
76
+ /**
77
+ * PV 统计响应接口
78
+ */
79
+ export interface PVStatsResponse {
80
+ /** 总页面浏览量 */
81
+ total: number;
82
+ /** 按页面分组的统计(可选)*/
83
+ byPage?: Array<{
84
+ page: string;
85
+ count: number;
86
+ }>;
87
+ /** 按日期分组的统计(可选)*/
88
+ byDate?: Array<{
89
+ date: string;
90
+ count: number;
91
+ }>;
92
+ }
93
+ /**
94
+ * 每日页面访问统计响应接口
95
+ */
96
+ export interface DailyPageStatsResponse {
97
+ date: string;
98
+ page: string;
99
+ viewCount: number;
100
+ uniqueVisitors: number;
101
+ }
102
+ /**
103
+ * IP 页面访问统计响应接口
104
+ */
105
+ export interface IpPageStatsResponse {
106
+ ip: string;
107
+ page: string;
108
+ viewCount: number;
109
+ firstVisitAt: string;
110
+ lastVisitAt: string;
111
+ }
112
+ /**
113
+ * 页面浏览记录接口
114
+ */
115
+ export interface PageViewResponse {
116
+ id: number;
117
+ sessionId: string;
118
+ ip: string;
119
+ page: string;
120
+ referrer: string | null;
121
+ visitedAt: string;
122
+ }
123
+ /**
124
+ * 访客会话记录接口
125
+ */
126
+ export interface VisitorSessionResponse {
127
+ id: number;
128
+ sessionId: string;
129
+ ip: string;
130
+ userAgent: string | null;
131
+ firstVisitAt: string;
132
+ lastVisitAt: string;
133
+ visitCount: number;
134
+ }
135
+ //# sourceMappingURL=visitor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visitor.d.ts","sourceRoot":"","sources":["../../../../src/core/lib/types/visitor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,25 @@
1
+ import type { ApiResponse } from '../types/inquiry';
2
+ /**
3
+ * 构建成功响应
4
+ */
5
+ export declare function successResponse<T>(data: T): ApiResponse<T>;
6
+ /**
7
+ * 构建错误响应
8
+ */
9
+ export declare function errorResponse(code: number, message: string): ApiResponse<null>;
10
+ /**
11
+ * 解析查询参数为数字
12
+ */
13
+ export declare function parseNumberParam(value: string | null | undefined, defaultValue: number): number;
14
+ /**
15
+ * 解析查询参数为布尔值
16
+ */
17
+ export declare function parseBooleanParam(value: string | null | undefined, defaultValue?: boolean): boolean;
18
+ /**
19
+ * 验证 ID 的有效性
20
+ */
21
+ export declare function validateId(id: number): {
22
+ valid: boolean;
23
+ error?: string;
24
+ };
25
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../../src/core/lib/utils/api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAEnD;;GAEG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAO1D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC5B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACb,WAAW,CAAC,IAAI,CAAC,CAOnB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC/B,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAChC,YAAY,EAAE,MAAM,GAClB,MAAM,CAIR;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAChC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAChC,YAAY,GAAE,OAAe,GAC3B,OAAO,CAGT;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAKzE"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * 构建成功响应
3
+ */
4
+ export function successResponse(data) {
5
+ return {
6
+ code: 200,
7
+ message: 'ok',
8
+ data,
9
+ timestamp: new Date().toISOString(),
10
+ };
11
+ }
12
+ /**
13
+ * 构建错误响应
14
+ */
15
+ export function errorResponse(code, message) {
16
+ return {
17
+ code,
18
+ message,
19
+ data: null,
20
+ timestamp: new Date().toISOString(),
21
+ };
22
+ }
23
+ /**
24
+ * 解析查询参数为数字
25
+ */
26
+ export function parseNumberParam(value, defaultValue) {
27
+ if (!value)
28
+ return defaultValue;
29
+ const parsed = parseInt(value, 10);
30
+ return Number.isNaN(parsed) ? defaultValue : parsed;
31
+ }
32
+ /**
33
+ * 解析查询参数为布尔值
34
+ */
35
+ export function parseBooleanParam(value, defaultValue = false) {
36
+ if (!value)
37
+ return defaultValue;
38
+ return value === 'true' || value === '1';
39
+ }
40
+ /**
41
+ * 验证 ID 的有效性
42
+ */
43
+ export function validateId(id) {
44
+ if (!Number.isFinite(id) || id <= 0) {
45
+ return { valid: false, error: 'invalid id' };
46
+ }
47
+ return { valid: true };
48
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * 请求处理工具函数
3
+ */
4
+ import type { Context } from 'hono';
5
+ /**
6
+ * 获取客户端 IP 地址
7
+ * 支持从 Request 或 clientAddress 获取
8
+ */
9
+ export declare function getClientIp(c: Context): string;
10
+ /**
11
+ * 提取请求上下文信息
12
+ */
13
+ export declare function requestInfo(c: Context): {
14
+ userAgent: string;
15
+ clientIp: string;
16
+ referer: string;
17
+ };
18
+ //# sourceMappingURL=request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../../../src/core/lib/utils/request.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAEnC;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAS9C;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG;IACxC,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;CACf,CAMA"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * 请求处理工具函数
3
+ */
4
+ /**
5
+ * 获取客户端 IP 地址
6
+ * 支持从 Request 或 clientAddress 获取
7
+ */
8
+ export function getClientIp(c) {
9
+ // fallback
10
+ return (c.req.header('x-forwarded-for')?.split(',')[0]?.trim() ||
11
+ c.req.header('x-real-ip') ||
12
+ c.req.header('cf-connecting-ip') || // Cloudflare
13
+ c.req.header('true-client-ip') || // Akamai, Cloudflare Enterprise
14
+ '127.0.0.1');
15
+ }
16
+ /**
17
+ * 提取请求上下文信息
18
+ */
19
+ export function requestInfo(c) {
20
+ return {
21
+ clientIp: getClientIp(c),
22
+ userAgent: c.req.header('User-Agent') || '',
23
+ referer: c.req.header('Referer') || '',
24
+ };
25
+ }
@@ -0,0 +1,39 @@
1
+ import type { Context, MiddlewareHandler } from 'hono';
2
+ /**
3
+ * API 守卫配置
4
+ */
5
+ interface ApiGuardConfig {
6
+ /** API Token 环境变量名 */
7
+ tokenEnvKey?: string;
8
+ /** 排除认证的路径模式列表 */
9
+ excludePatterns?: (string | RegExp)[];
10
+ /** 自定义 Token 提取函数 */
11
+ extractToken?: (c: Context) => string | null;
12
+ /** 自定义错误响应 */
13
+ onUnauthorized?: (c: Context) => Response | Promise<Response>;
14
+ /** Token 验证缓存时间(毫秒),默认 5 分钟 */
15
+ throttleInterval?: number;
16
+ }
17
+ /**
18
+ * 清空所有缓存(用于测试)
19
+ */
20
+ export declare function clearTokenCache(): void;
21
+ /**
22
+ * 创建 API 守卫中间件
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * // 基础使用
27
+ * app.use('/api/*', createApiGuard())
28
+ *
29
+ * // 自定义配置
30
+ * app.use('/api/*', createApiGuard({
31
+ * tokenEnvKey: 'MY_API_TOKEN',
32
+ * excludePatterns: ['/api/public/*', /^\/api\/health/],
33
+ * throttleInterval: 10 * 60 * 1000, // 10 分钟
34
+ * }))
35
+ * ```
36
+ */
37
+ export declare function createApiGuard(config?: ApiGuardConfig): MiddlewareHandler;
38
+ export {};
39
+ //# sourceMappingURL=api-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-guard.d.ts","sourceRoot":"","sources":["../../../src/core/middlewares/api-guard.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAQ,MAAM,MAAM,CAAA;AAG5D;;GAEG;AACH,UAAU,cAAc;IACvB,sBAAsB;IACtB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kBAAkB;IAClB,eAAe,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAA;IACrC,qBAAqB;IACrB,YAAY,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,MAAM,GAAG,IAAI,CAAA;IAC5C,cAAc;IACd,cAAc,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC7D,+BAA+B;IAC/B,gBAAgB,CAAC,EAAE,MAAM,CAAA;CACzB;AA8HD;;GAEG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,MAAM,GAAE,cAAmB,GAAG,iBAAiB,CAwC7E"}
@@ -0,0 +1,154 @@
1
+ import { strapi } from '@strapi/client';
2
+ import { CMS_URL } from '../../contract';
3
+ /**
4
+ * 默认配置
5
+ */
6
+ const DEFAULT_CONFIG = {
7
+ tokenEnvKey: 'CMS_TOKEN',
8
+ excludePatterns: [],
9
+ extractToken: (c) => {
10
+ // 从 Authorization header 提取 Bearer token
11
+ const authHeader = c.req.header('Authorization');
12
+ if (authHeader?.startsWith('Bearer ')) {
13
+ return authHeader.slice(7);
14
+ }
15
+ // 从 X-API-Token header 提取
16
+ const apiToken = c.req.header('X-API-Token');
17
+ if (apiToken) {
18
+ return apiToken;
19
+ }
20
+ // 从 query 参数提取
21
+ const queryToken = c.req.query('token');
22
+ if (queryToken) {
23
+ return queryToken;
24
+ }
25
+ return null;
26
+ },
27
+ onUnauthorized: (c) => {
28
+ return c.json({
29
+ error: 'Unauthorized',
30
+ message: 'Invalid or missing API token',
31
+ }, 401);
32
+ },
33
+ throttleInterval: 5 * 60 * 1000, // 5 分钟
34
+ };
35
+ /**
36
+ * Token 验证缓存
37
+ * key: token, value: { valid: boolean, timestamp: number }
38
+ */
39
+ const tokenCache = new Map();
40
+ /**
41
+ * 检查路径是否匹配排除模式
42
+ */
43
+ function isPathExcluded(path, patterns) {
44
+ return patterns.some((pattern) => {
45
+ if (typeof pattern === 'string') {
46
+ // 支持通配符匹配
47
+ if (pattern.includes('*')) {
48
+ const regexPattern = pattern
49
+ .replace(/[.+?^${}()|[\]\\]/g, '\\$&') // 转义特殊字符
50
+ .replace(/\*/g, '.*'); // 替换 * 为 .*
51
+ return new RegExp(`^${regexPattern}$`).test(path);
52
+ }
53
+ // 精确匹配
54
+ return path === pattern;
55
+ }
56
+ // RegExp 匹配
57
+ return pattern.test(path);
58
+ });
59
+ }
60
+ /**
61
+ * 通过 Strapi CMS 验证 Token
62
+ * @param token - 要验证的 token
63
+ * @param throttleInterval - 节流间隔时间(毫秒)
64
+ * @returns 是否有效
65
+ */
66
+ async function validateTokenWithCMS(token, throttleInterval) {
67
+ // 检查缓存
68
+ const cached = tokenCache.get(token);
69
+ const now = Date.now();
70
+ // 如果缓存有效且未过期,直接返回缓存结果
71
+ if (cached && now - cached.timestamp < throttleInterval) {
72
+ return cached.valid;
73
+ }
74
+ // 使用提供的 token 创建 CMS 客户端并验证
75
+ try {
76
+ const cms = strapi({ baseURL: `${CMS_URL}/api`, auth: token });
77
+ // 尝试请求 site 接口来验证权限
78
+ await cms
79
+ .single('site', {
80
+ plugin: { name: 'website' },
81
+ })
82
+ .find();
83
+ // 验证成功,更新缓存
84
+ tokenCache.set(token, { valid: true, timestamp: now });
85
+ return true;
86
+ }
87
+ catch (e) {
88
+ console.error(e);
89
+ // 验证失败,缓存失败结果(避免频繁请求无效 token)
90
+ tokenCache.set(token, { valid: false, timestamp: now });
91
+ return false;
92
+ }
93
+ }
94
+ /**
95
+ * 清理过期的缓存项
96
+ * @param throttleInterval - 节流间隔时间(毫秒)
97
+ */
98
+ function cleanExpiredCache(throttleInterval) {
99
+ const now = Date.now();
100
+ for (const [token, item] of Array.from(tokenCache.entries())) {
101
+ if (now - item.timestamp >= throttleInterval) {
102
+ tokenCache.delete(token);
103
+ }
104
+ }
105
+ }
106
+ /**
107
+ * 清空所有缓存(用于测试)
108
+ */
109
+ export function clearTokenCache() {
110
+ tokenCache.clear();
111
+ }
112
+ /**
113
+ * 创建 API 守卫中间件
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * // 基础使用
118
+ * app.use('/api/*', createApiGuard())
119
+ *
120
+ * // 自定义配置
121
+ * app.use('/api/*', createApiGuard({
122
+ * tokenEnvKey: 'MY_API_TOKEN',
123
+ * excludePatterns: ['/api/public/*', /^\/api\/health/],
124
+ * throttleInterval: 10 * 60 * 1000, // 10 分钟
125
+ * }))
126
+ * ```
127
+ */
128
+ export function createApiGuard(config = {}) {
129
+ const finalConfig = { ...DEFAULT_CONFIG, ...config };
130
+ // 定期清理过期缓存(每 10 分钟)
131
+ setInterval(() => {
132
+ cleanExpiredCache(finalConfig.throttleInterval);
133
+ }, 10 * 60 * 1000);
134
+ return async (c, next) => {
135
+ const path = c.req.path;
136
+ // 检查是否在排除列表中
137
+ if (isPathExcluded(path, finalConfig.excludePatterns)) {
138
+ return next();
139
+ }
140
+ // 提取请求中的 token
141
+ const providedToken = finalConfig.extractToken(c);
142
+ // 如果没有提供 token
143
+ if (!providedToken) {
144
+ return finalConfig.onUnauthorized(c);
145
+ }
146
+ // 通过 CMS 验证 token
147
+ const isValid = await validateTokenWithCMS(providedToken, finalConfig.throttleInterval);
148
+ if (!isValid) {
149
+ return finalConfig.onUnauthorized(c);
150
+ }
151
+ // Token 验证通过,继续处理请求
152
+ return next();
153
+ };
154
+ }