@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.
- package/dist/contract.d.ts +9 -1
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +14 -3
- package/dist/core/api/common.d.ts +2 -0
- package/dist/core/api/common.d.ts.map +1 -0
- package/dist/core/api/common.js +38 -0
- package/dist/core/api/inquiries.d.ts +2 -0
- package/dist/core/api/inquiries.d.ts.map +1 -0
- package/dist/core/api/inquiries.js +116 -0
- package/dist/core/api/inquiry.d.ts +2 -0
- package/dist/core/api/inquiry.d.ts.map +1 -0
- package/dist/core/api/inquiry.js +33 -0
- package/dist/core/api/visitor.d.ts +2 -0
- package/dist/core/api/visitor.d.ts.map +1 -0
- package/dist/core/api/visitor.js +152 -0
- package/dist/core/components/InquiryEmailTemplate.d.ts +30 -0
- package/dist/core/components/InquiryEmailTemplate.d.ts.map +1 -0
- package/dist/core/components/InquiryEmailTemplate.js +13 -0
- package/dist/core/components/Seo.d.ts +97 -0
- package/dist/core/components/Seo.d.ts.map +1 -0
- package/dist/core/components/Seo.js +365 -0
- package/dist/core/components/index.d.ts +3 -0
- package/dist/core/components/index.d.ts.map +1 -0
- package/dist/core/components/index.js +2 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +3 -0
- package/dist/core/lib/constants/inquiry.d.ts +52 -0
- package/dist/core/lib/constants/inquiry.d.ts.map +1 -0
- package/dist/core/lib/constants/inquiry.js +51 -0
- package/dist/core/lib/constants/visitor.d.ts +29 -0
- package/dist/core/lib/constants/visitor.d.ts.map +1 -0
- package/dist/core/lib/constants/visitor.js +28 -0
- package/dist/core/lib/database/constants/database.d.ts +15 -0
- package/dist/core/lib/database/constants/database.d.ts.map +1 -0
- package/dist/core/lib/database/constants/database.js +14 -0
- package/dist/core/lib/database/database.service.d.ts +63 -0
- package/dist/core/lib/database/database.service.d.ts.map +1 -0
- package/dist/core/lib/database/database.service.js +108 -0
- package/dist/core/lib/database/index.d.ts +8 -0
- package/dist/core/lib/database/index.d.ts.map +1 -0
- package/dist/core/lib/database/index.js +7 -0
- package/dist/core/lib/database/managers/sqlite.d.ts +29 -0
- package/dist/core/lib/database/managers/sqlite.d.ts.map +1 -0
- package/dist/core/lib/database/managers/sqlite.js +55 -0
- package/dist/core/lib/database/managers/visitor-shard.d.ts +29 -0
- package/dist/core/lib/database/managers/visitor-shard.d.ts.map +1 -0
- package/dist/core/lib/database/managers/visitor-shard.js +83 -0
- package/dist/core/lib/database/repositories/inquiry.repository.d.ts +33 -0
- package/dist/core/lib/database/repositories/inquiry.repository.d.ts.map +1 -0
- package/dist/core/lib/database/repositories/inquiry.repository.js +173 -0
- package/dist/core/lib/database/repositories/visitor.repository.d.ts +51 -0
- package/dist/core/lib/database/repositories/visitor.repository.d.ts.map +1 -0
- package/dist/core/lib/database/repositories/visitor.repository.js +559 -0
- package/dist/core/lib/database/schemas/inquiry.schema.d.ts +323 -0
- package/dist/core/lib/database/schemas/inquiry.schema.d.ts.map +1 -0
- package/dist/core/lib/database/schemas/inquiry.schema.js +52 -0
- package/dist/core/lib/database/schemas/visitor.schema.d.ts +623 -0
- package/dist/core/lib/database/schemas/visitor.schema.d.ts.map +1 -0
- package/dist/core/lib/database/schemas/visitor.schema.js +162 -0
- package/dist/core/lib/services/captcha.service.d.ts +13 -0
- package/dist/core/lib/services/captcha.service.d.ts.map +1 -0
- package/dist/core/lib/services/captcha.service.js +40 -0
- package/dist/core/lib/services/geoip.service.d.ts +79 -0
- package/dist/core/lib/services/geoip.service.d.ts.map +1 -0
- package/dist/core/lib/services/geoip.service.js +144 -0
- package/dist/core/lib/services/index.d.ts +9 -0
- package/dist/core/lib/services/index.d.ts.map +1 -0
- package/dist/core/lib/services/index.js +8 -0
- package/dist/core/lib/services/inquiry.service.d.ts +56 -0
- package/dist/core/lib/services/inquiry.service.d.ts.map +1 -0
- package/dist/core/lib/services/inquiry.service.js +129 -0
- package/dist/core/lib/services/mailer.service.d.ts +23 -0
- package/dist/core/lib/services/mailer.service.d.ts.map +1 -0
- package/dist/core/lib/services/mailer.service.js +96 -0
- package/dist/core/lib/types/inquiry.d.ts +95 -0
- package/dist/core/lib/types/inquiry.d.ts.map +1 -0
- package/dist/core/lib/types/inquiry.js +1 -0
- package/dist/core/lib/types/visitor.d.ts +135 -0
- package/dist/core/lib/types/visitor.d.ts.map +1 -0
- package/dist/core/lib/types/visitor.js +1 -0
- package/dist/core/lib/utils/api.d.ts +25 -0
- package/dist/core/lib/utils/api.d.ts.map +1 -0
- package/dist/core/lib/utils/api.js +48 -0
- package/dist/core/lib/utils/request.d.ts +18 -0
- package/dist/core/lib/utils/request.d.ts.map +1 -0
- package/dist/core/lib/utils/request.js +25 -0
- package/dist/core/middlewares/api-guard.d.ts +39 -0
- package/dist/core/middlewares/api-guard.d.ts.map +1 -0
- package/dist/core/middlewares/api-guard.js +154 -0
- package/dist/core/middlewares/html-minifier.d.ts +55 -0
- package/dist/core/middlewares/html-minifier.d.ts.map +1 -0
- package/dist/core/middlewares/html-minifier.js +140 -0
- package/dist/core/middlewares/index.d.ts +5 -0
- package/dist/core/middlewares/index.d.ts.map +1 -0
- package/dist/core/middlewares/index.js +4 -0
- package/dist/core/middlewares/initializer.d.ts +11 -0
- package/dist/core/middlewares/initializer.d.ts.map +1 -0
- package/dist/core/middlewares/initializer.js +13 -0
- package/dist/core/middlewares/visitor-tracker.d.ts +85 -0
- package/dist/core/middlewares/visitor-tracker.d.ts.map +1 -0
- package/dist/core/middlewares/visitor-tracker.js +253 -0
- package/dist/core/template.d.ts +4 -0
- package/dist/core/template.d.ts.map +1 -0
- package/dist/core/template.js +7 -0
- package/dist/core/utils/formatDate.d.ts +3 -0
- package/dist/core/utils/formatDate.d.ts.map +1 -0
- package/dist/core/utils/formatDate.js +2 -0
- package/dist/core/utils/fullUrl.d.ts +2 -0
- package/dist/core/utils/fullUrl.d.ts.map +1 -0
- package/dist/core/utils/fullUrl.js +9 -0
- package/dist/core/utils/index.d.ts +6 -0
- package/dist/core/utils/index.d.ts.map +1 -0
- package/dist/core/utils/index.js +5 -0
- package/dist/core/utils/markdownify.d.ts +2 -0
- package/dist/core/utils/markdownify.d.ts.map +1 -0
- package/dist/core/utils/markdownify.js +4 -0
- package/dist/core/utils/params.d.ts +5 -0
- package/dist/core/utils/params.d.ts.map +1 -0
- package/dist/core/utils/params.js +14 -0
- package/dist/core/utils/routeUrl.d.ts +20 -0
- package/dist/core/utils/routeUrl.d.ts.map +1 -0
- package/dist/core/utils/routeUrl.js +111 -0
- package/dist/route/methods.d.ts +15 -0
- package/dist/route/methods.d.ts.map +1 -1
- package/dist/route/methods.js +45 -18
- package/dist/route/setup.d.ts +11 -12
- package/dist/route/setup.d.ts.map +1 -1
- package/dist/route/setup.js +26 -16
- package/dist/strapi/api/site.d.ts +2 -0
- package/dist/strapi/api/site.d.ts.map +1 -0
- package/dist/strapi/api/site.js +153 -0
- package/dist/strapi/cms/cms.js +1 -1
- package/dist/strapi/cms/common.d.ts.map +1 -1
- package/dist/strapi/cms/common.js +3 -1
- package/dist/strapi/cms/menu.d.ts +0 -9
- package/dist/strapi/cms/menu.d.ts.map +1 -1
- package/dist/strapi/cms/menu.js +3 -62
- package/dist/strapi/cms/setup.d.ts +2 -1
- package/dist/strapi/cms/setup.d.ts.map +1 -1
- package/dist/strapi/cms/setup.js +5 -0
- package/dist/strapi/cms/site.d.ts.map +1 -1
- package/dist/strapi/cms/site.js +3 -1
- package/dist/strapi/cms/translations.d.ts.map +1 -1
- package/dist/strapi/cms/translations.js +6 -2
- package/dist/strapi/i18n.d.ts +3 -0
- package/dist/strapi/i18n.d.ts.map +1 -0
- package/dist/strapi/i18n.js +27 -0
- package/dist/strapi/index.d.ts +1 -0
- package/dist/strapi/index.d.ts.map +1 -1
- package/dist/strapi/index.js +1 -0
- package/dist/strapi/interfaces/index.d.ts +1 -0
- package/dist/strapi/interfaces/index.d.ts.map +1 -1
- package/dist/strapi/interfaces/index.js +1 -0
- package/dist/strapi/interfaces/sitemap.d.ts +19 -0
- package/dist/strapi/interfaces/sitemap.d.ts.map +1 -0
- package/dist/strapi/interfaces/sitemap.js +1 -0
- package/dist/strapi/pages/sitemap/index.d.ts +2 -0
- package/dist/strapi/pages/sitemap/index.d.ts.map +1 -0
- package/dist/strapi/pages/sitemap/index.js +50 -0
- package/dist/strapi/pages/sitemap/index.xml +11 -0
- package/dist/strapi/pages/sitemap/list.d.ts +2 -0
- package/dist/strapi/pages/sitemap/list.d.ts.map +1 -0
- package/dist/strapi/pages/sitemap/list.js +123 -0
- package/dist/strapi/pages/sitemap/list.xml +28 -0
- package/dist/strapi/pages/sitemap/robots.d.ts +2 -0
- package/dist/strapi/pages/sitemap/robots.d.ts.map +1 -0
- package/dist/strapi/pages/sitemap/robots.js +19 -0
- package/dist/strapi/pages/sitemap/robots.txt +7 -0
- package/dist/strapi/pages/sitemap/style.d.ts +2 -0
- package/dist/strapi/pages/sitemap/style.d.ts.map +1 -0
- package/dist/strapi/pages/sitemap/style.js +17 -0
- package/dist/strapi/pages/sitemap/style.xsl +602 -0
- package/dist/strapi/utils/index.d.ts +1 -0
- package/dist/strapi/utils/index.d.ts.map +1 -1
- package/dist/strapi/utils/index.js +1 -0
- package/dist/strapi/utils/trans.d.ts +5 -0
- package/dist/strapi/utils/trans.d.ts.map +1 -0
- package/dist/strapi/utils/trans.js +7 -0
- package/package.json +20 -2
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from 'hono';
|
|
2
|
+
import { minify } from 'html-minifier-next';
|
|
3
|
+
/**
|
|
4
|
+
* HTML Minifier 配置选项
|
|
5
|
+
*/
|
|
6
|
+
export interface HtmlMinifierOptions {
|
|
7
|
+
/**
|
|
8
|
+
* 是否启用 HTML 压缩
|
|
9
|
+
* @default true 在生产环境启用,开发环境禁用
|
|
10
|
+
*/
|
|
11
|
+
enabled?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* 需要排除的路径模式(不进行 minify)
|
|
14
|
+
* @default ['/assets/', '/api/', '.xml', '.txt', '.xsl', '.json']
|
|
15
|
+
*/
|
|
16
|
+
excludePatterns?: (string | RegExp)[];
|
|
17
|
+
/**
|
|
18
|
+
* html-minifier-next 的配置选项
|
|
19
|
+
* @see https://github.com/Daninet/html-minifier-next
|
|
20
|
+
*/
|
|
21
|
+
minifyOptions?: Parameters<typeof minify>[1];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 创建 HTML Minifier 中间件
|
|
25
|
+
*
|
|
26
|
+
* 功能:
|
|
27
|
+
* 1. 拦截 HTML 响应
|
|
28
|
+
* 2. 使用 html-minifier-next 压缩 HTML
|
|
29
|
+
* 3. 支持自定义配置和排除规则
|
|
30
|
+
*
|
|
31
|
+
* 使用场景:
|
|
32
|
+
* - 生产环境自动压缩 HTML,减少传输体积
|
|
33
|
+
* - 开发环境可选择性禁用以便于调试
|
|
34
|
+
*
|
|
35
|
+
* @param options 配置选项
|
|
36
|
+
* @returns Hono 中间件
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* // 基础使用
|
|
41
|
+
* app.use(createHtmlMinifier())
|
|
42
|
+
*
|
|
43
|
+
* // 自定义配置
|
|
44
|
+
* app.use(createHtmlMinifier({
|
|
45
|
+
* enabled: process.env.NODE_ENV === 'production',
|
|
46
|
+
* excludePatterns: ['/admin/', /^\/test\//],
|
|
47
|
+
* minifyOptions: {
|
|
48
|
+
* collapseWhitespace: true,
|
|
49
|
+
* removeComments: true,
|
|
50
|
+
* }
|
|
51
|
+
* }))
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export declare function createHtmlMinifier(options?: HtmlMinifierOptions): MiddlewareHandler;
|
|
55
|
+
//# sourceMappingURL=html-minifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-minifier.d.ts","sourceRoot":"","sources":["../../../src/core/middlewares/html-minifier.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,iBAAiB,EAAE,MAAM,MAAM,CAAA;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAE3C;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IAEjB;;;OAGG;IACH,eAAe,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAA;IAErC;;;OAGG;IACH,aAAa,CAAC,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;CAC5C;AA+DD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,kBAAkB,CACjC,OAAO,GAAE,mBAAwB,GAC/B,iBAAiB,CAkEnB"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { minify } from 'html-minifier-next';
|
|
2
|
+
/**
|
|
3
|
+
* 默认的 minify 配置
|
|
4
|
+
*/
|
|
5
|
+
const DEFAULT_MINIFY_OPTIONS = {
|
|
6
|
+
// 折叠空白符
|
|
7
|
+
collapseWhitespace: true,
|
|
8
|
+
// 折叠布尔属性
|
|
9
|
+
collapseBooleanAttributes: true,
|
|
10
|
+
// 保留换行符(保留可读性)
|
|
11
|
+
preserveLineBreaks: false,
|
|
12
|
+
// 移除注释
|
|
13
|
+
removeComments: true,
|
|
14
|
+
// 移除空属性
|
|
15
|
+
removeEmptyAttributes: true,
|
|
16
|
+
// 移除可选标签
|
|
17
|
+
removeOptionalTags: false,
|
|
18
|
+
// 移除冗余属性
|
|
19
|
+
removeRedundantAttributes: true,
|
|
20
|
+
// 移除 script 类型属性
|
|
21
|
+
removeScriptTypeAttributes: true,
|
|
22
|
+
// 移除 style 链接类型属性
|
|
23
|
+
removeStyleLinkTypeAttributes: true,
|
|
24
|
+
// 使用短的 doctype
|
|
25
|
+
useShortDoctype: true,
|
|
26
|
+
// 压缩 CSS
|
|
27
|
+
minifyCSS: true,
|
|
28
|
+
// 压缩 JS
|
|
29
|
+
minifyJS: true,
|
|
30
|
+
// 压缩 URL
|
|
31
|
+
minifyURLs: false,
|
|
32
|
+
// 排序属性
|
|
33
|
+
sortAttributes: true,
|
|
34
|
+
// 排序类名
|
|
35
|
+
sortClassName: true,
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* 默认排除的路径模式
|
|
39
|
+
*/
|
|
40
|
+
const DEFAULT_EXCLUDE_PATTERNS = [
|
|
41
|
+
'/assets/',
|
|
42
|
+
'/api/',
|
|
43
|
+
'.xml',
|
|
44
|
+
'.txt',
|
|
45
|
+
'.xsl',
|
|
46
|
+
'.json',
|
|
47
|
+
'/favicon.ico',
|
|
48
|
+
];
|
|
49
|
+
/**
|
|
50
|
+
* 检查路径是否应该被排除
|
|
51
|
+
*/
|
|
52
|
+
function shouldExclude(path, patterns) {
|
|
53
|
+
return patterns.some((pattern) => {
|
|
54
|
+
if (typeof pattern === 'string') {
|
|
55
|
+
return path.includes(pattern);
|
|
56
|
+
}
|
|
57
|
+
return pattern.test(path);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* 创建 HTML Minifier 中间件
|
|
62
|
+
*
|
|
63
|
+
* 功能:
|
|
64
|
+
* 1. 拦截 HTML 响应
|
|
65
|
+
* 2. 使用 html-minifier-next 压缩 HTML
|
|
66
|
+
* 3. 支持自定义配置和排除规则
|
|
67
|
+
*
|
|
68
|
+
* 使用场景:
|
|
69
|
+
* - 生产环境自动压缩 HTML,减少传输体积
|
|
70
|
+
* - 开发环境可选择性禁用以便于调试
|
|
71
|
+
*
|
|
72
|
+
* @param options 配置选项
|
|
73
|
+
* @returns Hono 中间件
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```typescript
|
|
77
|
+
* // 基础使用
|
|
78
|
+
* app.use(createHtmlMinifier())
|
|
79
|
+
*
|
|
80
|
+
* // 自定义配置
|
|
81
|
+
* app.use(createHtmlMinifier({
|
|
82
|
+
* enabled: process.env.NODE_ENV === 'production',
|
|
83
|
+
* excludePatterns: ['/admin/', /^\/test\//],
|
|
84
|
+
* minifyOptions: {
|
|
85
|
+
* collapseWhitespace: true,
|
|
86
|
+
* removeComments: true,
|
|
87
|
+
* }
|
|
88
|
+
* }))
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export function createHtmlMinifier(options = {}) {
|
|
92
|
+
const { enabled = process.env.NODE_ENV === 'production', excludePatterns = DEFAULT_EXCLUDE_PATTERNS, minifyOptions = DEFAULT_MINIFY_OPTIONS, } = options;
|
|
93
|
+
return async (c, next) => {
|
|
94
|
+
await next();
|
|
95
|
+
// 如果禁用,直接返回
|
|
96
|
+
if (!enabled) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// 获取响应对象
|
|
100
|
+
const response = c.res;
|
|
101
|
+
// 检查是否是 HTML 响应
|
|
102
|
+
const contentType = response.headers.get('Content-Type') || '';
|
|
103
|
+
if (!contentType.includes('text/html')) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// 检查路径是否应该被排除
|
|
107
|
+
const path = c.req.path;
|
|
108
|
+
if (shouldExclude(path, excludePatterns)) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
// 克隆响应以避免消耗原始响应体
|
|
113
|
+
const clonedResponse = response.clone();
|
|
114
|
+
// 获取原始 HTML 内容
|
|
115
|
+
const originalHtml = await clonedResponse.text();
|
|
116
|
+
// 执行 minify(注意:minify 可能返回 Promise)
|
|
117
|
+
const minifiedHtml = await minify(originalHtml, minifyOptions);
|
|
118
|
+
// 计算压缩比例(用于日志)
|
|
119
|
+
const originalSize = Buffer.byteLength(originalHtml, 'utf8');
|
|
120
|
+
const minifiedSize = Buffer.byteLength(minifiedHtml, 'utf8');
|
|
121
|
+
const ratio = (((originalSize - minifiedSize) / originalSize) *
|
|
122
|
+
100).toFixed(2);
|
|
123
|
+
// 开发环境输出压缩统计
|
|
124
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
125
|
+
console.log(`🗜️ HTML Minified [${path}]: ${originalSize}B → ${minifiedSize}B (${ratio}% reduction)`);
|
|
126
|
+
}
|
|
127
|
+
// 创建新的响应
|
|
128
|
+
c.res = new Response(minifiedHtml, {
|
|
129
|
+
status: response.status,
|
|
130
|
+
statusText: response.statusText,
|
|
131
|
+
headers: response.headers,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
// 如果 minify 失败,记录错误但不影响原始响应
|
|
136
|
+
console.error('❌ HTML Minifier Error:', error);
|
|
137
|
+
// 保持原始响应
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { clearTokenCache, createApiGuard } from './api-guard';
|
|
2
|
+
export { createHtmlMinifier } from './html-minifier';
|
|
3
|
+
export { createInitializer } from './initializer';
|
|
4
|
+
export { createVisitorTracker, getTrackerInstance, stopVisitorTracker, } from './visitor-tracker';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/middlewares/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EACN,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,GAClB,MAAM,mBAAmB,CAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from 'hono';
|
|
2
|
+
import type { Variables } from '../../route';
|
|
3
|
+
/**
|
|
4
|
+
* 创建初始化中间件
|
|
5
|
+
*
|
|
6
|
+
* @returns Hono 中间件处理函数
|
|
7
|
+
*/
|
|
8
|
+
export declare function createInitializer(): MiddlewareHandler<{
|
|
9
|
+
Variables: Variables;
|
|
10
|
+
}>;
|
|
11
|
+
//# sourceMappingURL=initializer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"initializer.d.ts","sourceRoot":"","sources":["../../../src/core/middlewares/initializer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAExC;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,iBAAiB,CAAC;IACtD,SAAS,EAAE,SAAS,CAAA;CACpB,CAAC,CAOD"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from 'hono';
|
|
2
|
+
/**
|
|
3
|
+
* 访客追踪配置
|
|
4
|
+
*/
|
|
5
|
+
interface VisitorTrackerConfig {
|
|
6
|
+
/** 批量写入的最大批次大小 */
|
|
7
|
+
batchSize?: number;
|
|
8
|
+
/** 批量写入的最大等待时间(毫秒) */
|
|
9
|
+
flushInterval?: number;
|
|
10
|
+
/** 短时间内重复访问的去重时间窗口(毫秒) */
|
|
11
|
+
dedupeWindow?: number;
|
|
12
|
+
/** 是否记录静态资源访问 */
|
|
13
|
+
trackStaticAssets?: boolean;
|
|
14
|
+
/** 排除的路径模式 */
|
|
15
|
+
excludePatterns?: RegExp[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 访客追踪中间件类
|
|
19
|
+
*/
|
|
20
|
+
declare class VisitorTracker {
|
|
21
|
+
private queue;
|
|
22
|
+
private dedupeCache;
|
|
23
|
+
private flushTimer;
|
|
24
|
+
private isProcessing;
|
|
25
|
+
private readonly config;
|
|
26
|
+
constructor(config?: VisitorTrackerConfig);
|
|
27
|
+
/**
|
|
28
|
+
* 生成去重键
|
|
29
|
+
*/
|
|
30
|
+
private generateDedupeKey;
|
|
31
|
+
/**
|
|
32
|
+
* 检查是否应该跳过记录
|
|
33
|
+
*/
|
|
34
|
+
private shouldSkip;
|
|
35
|
+
/**
|
|
36
|
+
* 检查是否为重复访问
|
|
37
|
+
*/
|
|
38
|
+
private isDuplicate;
|
|
39
|
+
/**
|
|
40
|
+
* 记录去重信息
|
|
41
|
+
*/
|
|
42
|
+
private recordDedupe;
|
|
43
|
+
/**
|
|
44
|
+
* 添加访客记录到队列
|
|
45
|
+
*/
|
|
46
|
+
private enqueue;
|
|
47
|
+
/**
|
|
48
|
+
* 刷新队列到数据库
|
|
49
|
+
*/
|
|
50
|
+
private flush;
|
|
51
|
+
/**
|
|
52
|
+
* 启动定时刷新任务
|
|
53
|
+
*/
|
|
54
|
+
private startFlushTimer;
|
|
55
|
+
/**
|
|
56
|
+
* 停止追踪器并刷新所有待处理记录
|
|
57
|
+
*/
|
|
58
|
+
stop(): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* 获取客户端 IP 地址
|
|
61
|
+
*/
|
|
62
|
+
private getClientIp;
|
|
63
|
+
/**
|
|
64
|
+
* 生成会话 ID(简化版)
|
|
65
|
+
*/
|
|
66
|
+
private generateSessionId;
|
|
67
|
+
/**
|
|
68
|
+
* 创建中间件处理函数
|
|
69
|
+
*/
|
|
70
|
+
middleware(): MiddlewareHandler;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 创建访客追踪中间件
|
|
74
|
+
*/
|
|
75
|
+
export declare function createVisitorTracker(config?: VisitorTrackerConfig): MiddlewareHandler;
|
|
76
|
+
/**
|
|
77
|
+
* 获取访客追踪器实例(用于测试或手动控制)
|
|
78
|
+
*/
|
|
79
|
+
export declare function getTrackerInstance(): VisitorTracker | null;
|
|
80
|
+
/**
|
|
81
|
+
* 停止访客追踪器
|
|
82
|
+
*/
|
|
83
|
+
export declare function stopVisitorTracker(): Promise<void>;
|
|
84
|
+
export {};
|
|
85
|
+
//# sourceMappingURL=visitor-tracker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visitor-tracker.d.ts","sourceRoot":"","sources":["../../../src/core/middlewares/visitor-tracker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAItD;;GAEG;AACH,UAAU,oBAAoB;IAC7B,kBAAkB;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,sBAAsB;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,0BAA0B;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iBAAiB;IACjB,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,cAAc;IACd,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;CAC1B;AAmBD;;GAEG;AACH,cAAM,cAAc;IACnB,OAAO,CAAC,KAAK,CAAyB;IACtC,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,UAAU,CAAqB;IACvC,OAAO,CAAC,YAAY,CAAQ;IAE5B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgC;gBAE3C,MAAM,GAAE,oBAAyB;IAiB7C;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAIzB;;OAEG;IACH,OAAO,CAAC,UAAU;IAUlB;;OAEG;IACH,OAAO,CAAC,WAAW;IAcnB;;OAEG;IACH,OAAO,CAAC,YAAY;IAqBpB;;OAEG;IACH,OAAO,CAAC,OAAO;IAuBf;;OAEG;YACW,KAAK;IAmCnB;;OAEG;IACH,OAAO,CAAC,eAAe;IAUvB;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B;;OAEG;IACH,OAAO,CAAC,WAAW;IAuBnB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAOzB;;OAEG;IACH,UAAU,IAAI,iBAAiB;CA2C/B;AAOD;;GAEG;AACH,wBAAgB,oBAAoB,CACnC,MAAM,CAAC,EAAE,oBAAoB,GAC3B,iBAAiB,CAKnB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,cAAc,GAAG,IAAI,CAE1D;AAED;;GAEG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAKxD"}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { getDatabase } from '../../core/lib/database';
|
|
2
|
+
/**
|
|
3
|
+
* 访客追踪中间件类
|
|
4
|
+
*/
|
|
5
|
+
class VisitorTracker {
|
|
6
|
+
constructor(config = {}) {
|
|
7
|
+
this.queue = [];
|
|
8
|
+
this.dedupeCache = new Map();
|
|
9
|
+
this.flushTimer = null;
|
|
10
|
+
this.isProcessing = false;
|
|
11
|
+
this.config = {
|
|
12
|
+
batchSize: config.batchSize ?? 50,
|
|
13
|
+
flushInterval: config.flushInterval ?? 5000,
|
|
14
|
+
dedupeWindow: config.dedupeWindow ?? 30000, // 30秒内相同访客访问同一页面视为重复
|
|
15
|
+
trackStaticAssets: config.trackStaticAssets ?? false,
|
|
16
|
+
excludePatterns: config.excludePatterns ?? [
|
|
17
|
+
/^\/api\//, // 排除 API 路径
|
|
18
|
+
/^\/assets\//, // 排除静态资源目录
|
|
19
|
+
/\.[^.]+$(?<!\.html$)/i, // 排除所有带后缀的文件,除了 .html
|
|
20
|
+
],
|
|
21
|
+
};
|
|
22
|
+
// 启动定时刷新任务
|
|
23
|
+
this.startFlushTimer();
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 生成去重键
|
|
27
|
+
*/
|
|
28
|
+
generateDedupeKey(dedupeKey) {
|
|
29
|
+
return `${dedupeKey.ip}::${dedupeKey.page}::${dedupeKey.sessionId}`;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 检查是否应该跳过记录
|
|
33
|
+
*/
|
|
34
|
+
shouldSkip(path) {
|
|
35
|
+
// 检查排除模式
|
|
36
|
+
for (const pattern of this.config.excludePatterns) {
|
|
37
|
+
if (pattern.test(path)) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 检查是否为重复访问
|
|
45
|
+
*/
|
|
46
|
+
isDuplicate(key) {
|
|
47
|
+
const lastTime = this.dedupeCache.get(key);
|
|
48
|
+
if (!lastTime) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
const now = Date.now();
|
|
52
|
+
if (now - lastTime < this.config.dedupeWindow) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 记录去重信息
|
|
59
|
+
*/
|
|
60
|
+
recordDedupe(key) {
|
|
61
|
+
this.dedupeCache.set(key, Date.now());
|
|
62
|
+
// 定期清理过期的去重记录
|
|
63
|
+
if (this.dedupeCache.size > 10000) {
|
|
64
|
+
const now = Date.now();
|
|
65
|
+
const expiredKeys = [];
|
|
66
|
+
const entries = Array.from(this.dedupeCache.entries());
|
|
67
|
+
for (const [k, timestamp] of entries) {
|
|
68
|
+
if (now - timestamp > this.config.dedupeWindow * 2) {
|
|
69
|
+
expiredKeys.push(k);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
for (const k of expiredKeys) {
|
|
73
|
+
this.dedupeCache.delete(k);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 添加访客记录到队列
|
|
79
|
+
*/
|
|
80
|
+
enqueue(data, dedupeKey) {
|
|
81
|
+
const key = this.generateDedupeKey(dedupeKey);
|
|
82
|
+
// 检查去重
|
|
83
|
+
if (this.isDuplicate(key)) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
// 添加到队列
|
|
87
|
+
this.queue.push({
|
|
88
|
+
data,
|
|
89
|
+
timestamp: Date.now(),
|
|
90
|
+
});
|
|
91
|
+
// 记录去重信息
|
|
92
|
+
this.recordDedupe(key);
|
|
93
|
+
// 如果队列达到批次大小,立即刷新
|
|
94
|
+
if (this.queue.length >= this.config.batchSize) {
|
|
95
|
+
this.flush();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 刷新队列到数据库
|
|
100
|
+
*/
|
|
101
|
+
async flush() {
|
|
102
|
+
if (this.isProcessing || this.queue.length === 0) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
this.isProcessing = true;
|
|
106
|
+
// 获取当前队列
|
|
107
|
+
const batch = this.queue.splice(0, this.config.batchSize);
|
|
108
|
+
try {
|
|
109
|
+
const db = await getDatabase();
|
|
110
|
+
// 并发写入访客记录
|
|
111
|
+
await Promise.all(batch.map((item) => db.addVisitor(item.data).catch((error) => {
|
|
112
|
+
// 单条记录失败不影响其他记录
|
|
113
|
+
console.error('Failed to save visitor record:', error);
|
|
114
|
+
})));
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
console.error('Failed to flush visitor records:', error);
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
this.isProcessing = false;
|
|
121
|
+
// 如果还有剩余记录,继续处理
|
|
122
|
+
if (this.queue.length > 0) {
|
|
123
|
+
// 使用 setImmediate 让出控制权
|
|
124
|
+
setImmediate(() => this.flush());
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* 启动定时刷新任务
|
|
130
|
+
*/
|
|
131
|
+
startFlushTimer() {
|
|
132
|
+
if (this.flushTimer) {
|
|
133
|
+
clearInterval(this.flushTimer);
|
|
134
|
+
}
|
|
135
|
+
this.flushTimer = setInterval(() => {
|
|
136
|
+
this.flush();
|
|
137
|
+
}, this.config.flushInterval);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* 停止追踪器并刷新所有待处理记录
|
|
141
|
+
*/
|
|
142
|
+
async stop() {
|
|
143
|
+
if (this.flushTimer) {
|
|
144
|
+
clearInterval(this.flushTimer);
|
|
145
|
+
this.flushTimer = null;
|
|
146
|
+
}
|
|
147
|
+
// 刷新所有待处理记录
|
|
148
|
+
while (this.queue.length > 0) {
|
|
149
|
+
await this.flush();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* 获取客户端 IP 地址
|
|
154
|
+
*/
|
|
155
|
+
getClientIp(c) {
|
|
156
|
+
// 尝试从各种 header 中获取真实 IP
|
|
157
|
+
const forwarded = c.req.header('x-forwarded-for');
|
|
158
|
+
if (forwarded) {
|
|
159
|
+
const ips = forwarded.split(',').map((ip) => ip.trim());
|
|
160
|
+
return ips[0];
|
|
161
|
+
}
|
|
162
|
+
const realIp = c.req.header('x-real-ip');
|
|
163
|
+
if (realIp) {
|
|
164
|
+
return realIp;
|
|
165
|
+
}
|
|
166
|
+
// Cloudflare
|
|
167
|
+
const cfConnectingIp = c.req.header('cf-connecting-ip');
|
|
168
|
+
if (cfConnectingIp) {
|
|
169
|
+
return cfConnectingIp;
|
|
170
|
+
}
|
|
171
|
+
// 默认使用连接的远程地址
|
|
172
|
+
return c.req.header('remote-addr') || 'unknown';
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* 生成会话 ID(简化版)
|
|
176
|
+
*/
|
|
177
|
+
generateSessionId(ip, userAgent) {
|
|
178
|
+
const crypto = require('node:crypto');
|
|
179
|
+
const hash = crypto.createHash('sha256');
|
|
180
|
+
hash.update(`${ip}::${userAgent}`);
|
|
181
|
+
return hash.digest('hex').substring(0, 32);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* 创建中间件处理函数
|
|
185
|
+
*/
|
|
186
|
+
middleware() {
|
|
187
|
+
return async (c, next) => {
|
|
188
|
+
// 先执行下一个中间件/处理器
|
|
189
|
+
await next();
|
|
190
|
+
// 异步记录访客信息(不阻塞响应)
|
|
191
|
+
setImmediate(() => {
|
|
192
|
+
try {
|
|
193
|
+
const path = c.req.path;
|
|
194
|
+
// 检查是否应该跳过
|
|
195
|
+
if (this.shouldSkip(path)) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
// 提取访客信息
|
|
199
|
+
const ip = this.getClientIp(c);
|
|
200
|
+
const userAgent = c.req.header('user-agent') || '';
|
|
201
|
+
const referrer = c.req.header('referer') || c.req.header('referrer');
|
|
202
|
+
const sessionId = this.generateSessionId(ip, userAgent);
|
|
203
|
+
// 构造访客数据
|
|
204
|
+
const visitorData = {
|
|
205
|
+
ip,
|
|
206
|
+
page: path,
|
|
207
|
+
userAgent: userAgent || undefined,
|
|
208
|
+
referrer: referrer || undefined,
|
|
209
|
+
visitedAt: new Date(),
|
|
210
|
+
};
|
|
211
|
+
// 添加到队列
|
|
212
|
+
this.enqueue(visitorData, {
|
|
213
|
+
ip,
|
|
214
|
+
page: path,
|
|
215
|
+
sessionId,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
// 记录错误但不抛出,避免影响请求
|
|
220
|
+
console.error('Error in visitor tracker middleware:', error);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* 全局访客追踪器实例
|
|
228
|
+
*/
|
|
229
|
+
let trackerInstance = null;
|
|
230
|
+
/**
|
|
231
|
+
* 创建访客追踪中间件
|
|
232
|
+
*/
|
|
233
|
+
export function createVisitorTracker(config) {
|
|
234
|
+
if (!trackerInstance) {
|
|
235
|
+
trackerInstance = new VisitorTracker(config);
|
|
236
|
+
}
|
|
237
|
+
return trackerInstance.middleware();
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* 获取访客追踪器实例(用于测试或手动控制)
|
|
241
|
+
*/
|
|
242
|
+
export function getTrackerInstance() {
|
|
243
|
+
return trackerInstance;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* 停止访客追踪器
|
|
247
|
+
*/
|
|
248
|
+
export async function stopVisitorTracker() {
|
|
249
|
+
if (trackerInstance) {
|
|
250
|
+
await trackerInstance.stop();
|
|
251
|
+
trackerInstance = null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/core/template.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAEjC,eAAO,MAAM,MAAM,QAAe,CAAA;AAElC,eAAO,MAAM,gBAAgB,GAAI,GAAG,OAAO,MAAM,EAAE,WAElD,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { Liquid } from 'liquidjs';
|
|
4
|
+
export const engine = new Liquid();
|
|
5
|
+
export const readTemplateSync = (...paths) => {
|
|
6
|
+
return readFileSync(resolve(...paths), 'utf8').toString();
|
|
7
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatDate.d.ts","sourceRoot":"","sources":["../../../src/core/utils/formatDate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,eAAO,MAAM,UAAU,cAAQ,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fullUrl.d.ts","sourceRoot":"","sources":["../../../src/core/utils/fullUrl.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,GAAI,SAAS,MAAM,EAAE,YAAQ,WAOhD,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,UAAU,CAAA;AACxB,cAAc,YAAY,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdownify.d.ts","sourceRoot":"","sources":["../../../src/core/utils/markdownify.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,WAAW,GAAI,UAAU,MAAM,6BAE3C,CAAA"}
|