vmoo-mcp-database-server 1.2.0 → 1.2.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.
- package/README.md +214 -178
- package/bin/vmoo-mcp-deliver.js +45 -45
- package/bin/vmoo-mcp-dev.js +45 -45
- package/bin/vmoo-mcp-prod.js +45 -45
- package/bin/vmoo-mcp-server.js +81 -81
- package/index.js +38 -38
- package/package.json +61 -61
- package/shared/database-utils.js +123 -102
- package/shared/privacy-utils.js +207 -207
- package/shared/security-utils.js +219 -219
- package/shared/time-utils.js +62 -62
- package/vmoo-database-deliver/config.json +25 -25
- package/vmoo-database-deliver/package.json +26 -26
- package/vmoo-database-deliver/server.js +297 -297
- package/vmoo-database-dev/config.json +25 -25
- package/vmoo-database-dev/package.json +32 -32
- package/vmoo-database-dev/server.js +294 -294
- package/vmoo-database-prod/config.json +41 -41
- package/vmoo-database-prod/package.json +33 -33
- package/vmoo-database-prod/server.js +313 -313
package/shared/security-utils.js
CHANGED
|
@@ -1,219 +1,219 @@
|
|
|
1
|
-
// VMOO MCP服务器安全检查工具
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* 安全检查配置
|
|
5
|
-
*/
|
|
6
|
-
const SECURITY_LEVELS = {
|
|
7
|
-
PRODUCTION: 'production', // 生产环境:只读+防爬虫+数据匿名化
|
|
8
|
-
DEVELOPMENT: 'development' // 开发环境:读写但禁止删除
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* 查询频率限制器
|
|
13
|
-
*/
|
|
14
|
-
class QueryRateLimiter {
|
|
15
|
-
constructor() {
|
|
16
|
-
this.queryHistory = new Map(); // clientId -> [timestamps]
|
|
17
|
-
this.maxQueriesPerMinute = 10;
|
|
18
|
-
this.cleanupInterval = 60000; // 1分钟清理一次
|
|
19
|
-
|
|
20
|
-
// 定期清理过期记录
|
|
21
|
-
setInterval(() => this.cleanup(), this.cleanupInterval);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* 检查查询频率是否超限
|
|
26
|
-
* @param {string} clientId 客户端ID
|
|
27
|
-
* @returns {boolean} 是否允许查询
|
|
28
|
-
*/
|
|
29
|
-
checkRateLimit(clientId = 'default') {
|
|
30
|
-
const now = Date.now();
|
|
31
|
-
const oneMinuteAgo = now - 60000;
|
|
32
|
-
|
|
33
|
-
if (!this.queryHistory.has(clientId)) {
|
|
34
|
-
this.queryHistory.set(clientId, []);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const clientQueries = this.queryHistory.get(clientId);
|
|
38
|
-
|
|
39
|
-
// 移除1分钟前的查询记录
|
|
40
|
-
const recentQueries = clientQueries.filter(timestamp => timestamp > oneMinuteAgo);
|
|
41
|
-
|
|
42
|
-
if (recentQueries.length >= this.maxQueriesPerMinute) {
|
|
43
|
-
return false; // 超过频率限制
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// 记录本次查询
|
|
47
|
-
recentQueries.push(now);
|
|
48
|
-
this.queryHistory.set(clientId, recentQueries);
|
|
49
|
-
|
|
50
|
-
return true;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* 清理过期记录
|
|
55
|
-
*/
|
|
56
|
-
cleanup() {
|
|
57
|
-
const oneMinuteAgo = Date.now() - 60000;
|
|
58
|
-
|
|
59
|
-
for (const [clientId, queries] of this.queryHistory.entries()) {
|
|
60
|
-
const recentQueries = queries.filter(timestamp => timestamp > oneMinuteAgo);
|
|
61
|
-
|
|
62
|
-
if (recentQueries.length === 0) {
|
|
63
|
-
this.queryHistory.delete(clientId);
|
|
64
|
-
} else {
|
|
65
|
-
this.queryHistory.set(clientId, recentQueries);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// 全局频率限制器实例
|
|
72
|
-
const rateLimiter = new QueryRateLimiter();
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* 检查查询复杂度,防止爬虫
|
|
76
|
-
* @param {string} query SQL查询语句
|
|
77
|
-
* @returns {Object} 检查结果
|
|
78
|
-
*/
|
|
79
|
-
function checkQueryComplexity(query) {
|
|
80
|
-
const lowerQuery = query.toLowerCase();
|
|
81
|
-
|
|
82
|
-
// 检查可能的批量爬取模式
|
|
83
|
-
const suspiciousPatterns = [
|
|
84
|
-
/select\s+\*\s+from\s+\w+\s*$/i, // SELECT * FROM table (无WHERE条件)
|
|
85
|
-
/select\s+\*\s+from\s+\w+\s+limit\s+[5-9]\d+/i, // 大量LIMIT (50+)
|
|
86
|
-
/select\s+\*\s+from\s+\w+\s+where\s+id\s*>\s*\d+\s+limit\s+\d+/i, // 按ID范围批量查询
|
|
87
|
-
/union\s+all/i, // UNION ALL 可能用于批量查询
|
|
88
|
-
/order\s+by\s+id\s+limit\s+\d+\s*,\s*\d+/i, // 分页查询大量数据
|
|
89
|
-
];
|
|
90
|
-
|
|
91
|
-
for (const pattern of suspiciousPatterns) {
|
|
92
|
-
if (pattern.test(query)) {
|
|
93
|
-
return {
|
|
94
|
-
allowed: false,
|
|
95
|
-
reason: '检测到可能的批量数据爬取行为,查询被拒绝'
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// 检查LIMIT值
|
|
101
|
-
const limitMatch = query.match(/limit\s+(\d+)/i);
|
|
102
|
-
if (limitMatch) {
|
|
103
|
-
const limitValue = parseInt(limitMatch[1]);
|
|
104
|
-
if (limitValue > 50) {
|
|
105
|
-
return {
|
|
106
|
-
allowed: false,
|
|
107
|
-
reason: '单次查询最多返回50条记录,防止数据爬取'
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return { allowed: true, reason: '查询复杂度检查通过' };
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* 执行安全检查
|
|
117
|
-
* @param {string} query SQL查询语句
|
|
118
|
-
* @param {string} securityLevel 安全级别
|
|
119
|
-
* @param {string} clientId 客户端ID
|
|
120
|
-
* @returns {Object} 检查结果
|
|
121
|
-
*/
|
|
122
|
-
function performSecurityCheck(query, securityLevel = SECURITY_LEVELS.DEVELOPMENT, clientId = 'default') {
|
|
123
|
-
const trimmedQuery = query.trim();
|
|
124
|
-
|
|
125
|
-
// 生产环境需要检查频率限制
|
|
126
|
-
if (securityLevel === SECURITY_LEVELS.PRODUCTION) {
|
|
127
|
-
if (!rateLimiter.checkRateLimit(clientId)) {
|
|
128
|
-
return {
|
|
129
|
-
allowed: false,
|
|
130
|
-
reason: '查询频率过高,请稍后再试(每分钟最多10次查询)'
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// 检查查询复杂度
|
|
135
|
-
const complexityCheck = checkQueryComplexity(trimmedQuery);
|
|
136
|
-
if (!complexityCheck.allowed) {
|
|
137
|
-
return complexityCheck;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
switch (securityLevel) {
|
|
142
|
-
case SECURITY_LEVELS.PRODUCTION:
|
|
143
|
-
return checkProductionSecurity(trimmedQuery);
|
|
144
|
-
|
|
145
|
-
case SECURITY_LEVELS.DEVELOPMENT:
|
|
146
|
-
return checkDevelopmentSecurity(trimmedQuery);
|
|
147
|
-
|
|
148
|
-
default:
|
|
149
|
-
return {
|
|
150
|
-
allowed: false,
|
|
151
|
-
reason: '未知的安全级别'
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* 生产环境安全检查:只读
|
|
158
|
-
*/
|
|
159
|
-
function checkProductionSecurity(query) {
|
|
160
|
-
// 只允许查询操作
|
|
161
|
-
const allowedPattern = /^\s*(SELECT|SHOW|DESCRIBE|DESC|EXPLAIN)\s+/i;
|
|
162
|
-
|
|
163
|
-
if (!allowedPattern.test(query)) {
|
|
164
|
-
return {
|
|
165
|
-
allowed: false,
|
|
166
|
-
reason: '生产环境仅支持查询操作(SELECT、SHOW、DESCRIBE等)'
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return { allowed: true, reason: '查询操作被允许' };
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* 开发环境安全检查:读写但禁止删除
|
|
175
|
-
*/
|
|
176
|
-
function checkDevelopmentSecurity(query) {
|
|
177
|
-
// 禁止所有删除操作
|
|
178
|
-
const dangerousPattern = /^\s*(DROP\s+(DATABASE|SCHEMA)|TRUNCATE\s+DATABASE|DELETE\s+FROM)/i;
|
|
179
|
-
if (dangerousPattern.test(query)) {
|
|
180
|
-
return {
|
|
181
|
-
allowed: false,
|
|
182
|
-
reason: '禁止执行删除操作,开发环境仅支持查询、插入和更新操作'
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// 允许的操作
|
|
187
|
-
const allowedPattern = /^\s*(SELECT|SHOW|DESCRIBE|DESC|EXPLAIN|INSERT|UPDATE|ALTER\s+TABLE|CREATE\s+TABLE)\s+/i;
|
|
188
|
-
if (!allowedPattern.test(query)) {
|
|
189
|
-
return {
|
|
190
|
-
allowed: false,
|
|
191
|
-
reason: '仅支持SELECT、INSERT、UPDATE、ALTER TABLE、CREATE TABLE等操作'
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return { allowed: true, reason: '操作被允许' };
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* 处理查询限制
|
|
200
|
-
* @param {string} query 原始查询
|
|
201
|
-
* @param {number} defaultLimit 默认限制
|
|
202
|
-
* @returns {string} 处理后的查询
|
|
203
|
-
*/
|
|
204
|
-
function applyQueryLimits(query, defaultLimit = 100) {
|
|
205
|
-
// 如果是SELECT查询且没有LIMIT,添加LIMIT
|
|
206
|
-
if (/^\s*SELECT\s+/i.test(query) && !/\bLIMIT\s+\d+/i.test(query)) {
|
|
207
|
-
return `${query} LIMIT ${defaultLimit}`;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return query;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
export {
|
|
214
|
-
SECURITY_LEVELS,
|
|
215
|
-
performSecurityCheck,
|
|
216
|
-
applyQueryLimits,
|
|
217
|
-
checkQueryComplexity,
|
|
218
|
-
QueryRateLimiter
|
|
219
|
-
};
|
|
1
|
+
// VMOO MCP服务器安全检查工具
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 安全检查配置
|
|
5
|
+
*/
|
|
6
|
+
const SECURITY_LEVELS = {
|
|
7
|
+
PRODUCTION: 'production', // 生产环境:只读+防爬虫+数据匿名化
|
|
8
|
+
DEVELOPMENT: 'development' // 开发环境:读写但禁止删除
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 查询频率限制器
|
|
13
|
+
*/
|
|
14
|
+
class QueryRateLimiter {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.queryHistory = new Map(); // clientId -> [timestamps]
|
|
17
|
+
this.maxQueriesPerMinute = 10;
|
|
18
|
+
this.cleanupInterval = 60000; // 1分钟清理一次
|
|
19
|
+
|
|
20
|
+
// 定期清理过期记录
|
|
21
|
+
setInterval(() => this.cleanup(), this.cleanupInterval);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 检查查询频率是否超限
|
|
26
|
+
* @param {string} clientId 客户端ID
|
|
27
|
+
* @returns {boolean} 是否允许查询
|
|
28
|
+
*/
|
|
29
|
+
checkRateLimit(clientId = 'default') {
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
const oneMinuteAgo = now - 60000;
|
|
32
|
+
|
|
33
|
+
if (!this.queryHistory.has(clientId)) {
|
|
34
|
+
this.queryHistory.set(clientId, []);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const clientQueries = this.queryHistory.get(clientId);
|
|
38
|
+
|
|
39
|
+
// 移除1分钟前的查询记录
|
|
40
|
+
const recentQueries = clientQueries.filter(timestamp => timestamp > oneMinuteAgo);
|
|
41
|
+
|
|
42
|
+
if (recentQueries.length >= this.maxQueriesPerMinute) {
|
|
43
|
+
return false; // 超过频率限制
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 记录本次查询
|
|
47
|
+
recentQueries.push(now);
|
|
48
|
+
this.queryHistory.set(clientId, recentQueries);
|
|
49
|
+
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 清理过期记录
|
|
55
|
+
*/
|
|
56
|
+
cleanup() {
|
|
57
|
+
const oneMinuteAgo = Date.now() - 60000;
|
|
58
|
+
|
|
59
|
+
for (const [clientId, queries] of this.queryHistory.entries()) {
|
|
60
|
+
const recentQueries = queries.filter(timestamp => timestamp > oneMinuteAgo);
|
|
61
|
+
|
|
62
|
+
if (recentQueries.length === 0) {
|
|
63
|
+
this.queryHistory.delete(clientId);
|
|
64
|
+
} else {
|
|
65
|
+
this.queryHistory.set(clientId, recentQueries);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 全局频率限制器实例
|
|
72
|
+
const rateLimiter = new QueryRateLimiter();
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 检查查询复杂度,防止爬虫
|
|
76
|
+
* @param {string} query SQL查询语句
|
|
77
|
+
* @returns {Object} 检查结果
|
|
78
|
+
*/
|
|
79
|
+
function checkQueryComplexity(query) {
|
|
80
|
+
const lowerQuery = query.toLowerCase();
|
|
81
|
+
|
|
82
|
+
// 检查可能的批量爬取模式
|
|
83
|
+
const suspiciousPatterns = [
|
|
84
|
+
/select\s+\*\s+from\s+\w+\s*$/i, // SELECT * FROM table (无WHERE条件)
|
|
85
|
+
/select\s+\*\s+from\s+\w+\s+limit\s+[5-9]\d+/i, // 大量LIMIT (50+)
|
|
86
|
+
/select\s+\*\s+from\s+\w+\s+where\s+id\s*>\s*\d+\s+limit\s+\d+/i, // 按ID范围批量查询
|
|
87
|
+
/union\s+all/i, // UNION ALL 可能用于批量查询
|
|
88
|
+
/order\s+by\s+id\s+limit\s+\d+\s*,\s*\d+/i, // 分页查询大量数据
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
for (const pattern of suspiciousPatterns) {
|
|
92
|
+
if (pattern.test(query)) {
|
|
93
|
+
return {
|
|
94
|
+
allowed: false,
|
|
95
|
+
reason: '检测到可能的批量数据爬取行为,查询被拒绝'
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 检查LIMIT值
|
|
101
|
+
const limitMatch = query.match(/limit\s+(\d+)/i);
|
|
102
|
+
if (limitMatch) {
|
|
103
|
+
const limitValue = parseInt(limitMatch[1]);
|
|
104
|
+
if (limitValue > 50) {
|
|
105
|
+
return {
|
|
106
|
+
allowed: false,
|
|
107
|
+
reason: '单次查询最多返回50条记录,防止数据爬取'
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return { allowed: true, reason: '查询复杂度检查通过' };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 执行安全检查
|
|
117
|
+
* @param {string} query SQL查询语句
|
|
118
|
+
* @param {string} securityLevel 安全级别
|
|
119
|
+
* @param {string} clientId 客户端ID
|
|
120
|
+
* @returns {Object} 检查结果
|
|
121
|
+
*/
|
|
122
|
+
function performSecurityCheck(query, securityLevel = SECURITY_LEVELS.DEVELOPMENT, clientId = 'default') {
|
|
123
|
+
const trimmedQuery = query.trim();
|
|
124
|
+
|
|
125
|
+
// 生产环境需要检查频率限制
|
|
126
|
+
if (securityLevel === SECURITY_LEVELS.PRODUCTION) {
|
|
127
|
+
if (!rateLimiter.checkRateLimit(clientId)) {
|
|
128
|
+
return {
|
|
129
|
+
allowed: false,
|
|
130
|
+
reason: '查询频率过高,请稍后再试(每分钟最多10次查询)'
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 检查查询复杂度
|
|
135
|
+
const complexityCheck = checkQueryComplexity(trimmedQuery);
|
|
136
|
+
if (!complexityCheck.allowed) {
|
|
137
|
+
return complexityCheck;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
switch (securityLevel) {
|
|
142
|
+
case SECURITY_LEVELS.PRODUCTION:
|
|
143
|
+
return checkProductionSecurity(trimmedQuery);
|
|
144
|
+
|
|
145
|
+
case SECURITY_LEVELS.DEVELOPMENT:
|
|
146
|
+
return checkDevelopmentSecurity(trimmedQuery);
|
|
147
|
+
|
|
148
|
+
default:
|
|
149
|
+
return {
|
|
150
|
+
allowed: false,
|
|
151
|
+
reason: '未知的安全级别'
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* 生产环境安全检查:只读
|
|
158
|
+
*/
|
|
159
|
+
function checkProductionSecurity(query) {
|
|
160
|
+
// 只允许查询操作
|
|
161
|
+
const allowedPattern = /^\s*(SELECT|SHOW|DESCRIBE|DESC|EXPLAIN)\s+/i;
|
|
162
|
+
|
|
163
|
+
if (!allowedPattern.test(query)) {
|
|
164
|
+
return {
|
|
165
|
+
allowed: false,
|
|
166
|
+
reason: '生产环境仅支持查询操作(SELECT、SHOW、DESCRIBE等)'
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return { allowed: true, reason: '查询操作被允许' };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* 开发环境安全检查:读写但禁止删除
|
|
175
|
+
*/
|
|
176
|
+
function checkDevelopmentSecurity(query) {
|
|
177
|
+
// 禁止所有删除操作
|
|
178
|
+
const dangerousPattern = /^\s*(DROP\s+(DATABASE|SCHEMA)|TRUNCATE\s+DATABASE|DELETE\s+FROM)/i;
|
|
179
|
+
if (dangerousPattern.test(query)) {
|
|
180
|
+
return {
|
|
181
|
+
allowed: false,
|
|
182
|
+
reason: '禁止执行删除操作,开发环境仅支持查询、插入和更新操作'
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 允许的操作
|
|
187
|
+
const allowedPattern = /^\s*(SELECT|SHOW|DESCRIBE|DESC|EXPLAIN|INSERT|UPDATE|ALTER\s+TABLE|CREATE\s+TABLE)\s+/i;
|
|
188
|
+
if (!allowedPattern.test(query)) {
|
|
189
|
+
return {
|
|
190
|
+
allowed: false,
|
|
191
|
+
reason: '仅支持SELECT、INSERT、UPDATE、ALTER TABLE、CREATE TABLE等操作'
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return { allowed: true, reason: '操作被允许' };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* 处理查询限制
|
|
200
|
+
* @param {string} query 原始查询
|
|
201
|
+
* @param {number} defaultLimit 默认限制
|
|
202
|
+
* @returns {string} 处理后的查询
|
|
203
|
+
*/
|
|
204
|
+
function applyQueryLimits(query, defaultLimit = 100) {
|
|
205
|
+
// 如果是SELECT查询且没有LIMIT,添加LIMIT
|
|
206
|
+
if (/^\s*SELECT\s+/i.test(query) && !/\bLIMIT\s+\d+/i.test(query)) {
|
|
207
|
+
return `${query} LIMIT ${defaultLimit}`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return query;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export {
|
|
214
|
+
SECURITY_LEVELS,
|
|
215
|
+
performSecurityCheck,
|
|
216
|
+
applyQueryLimits,
|
|
217
|
+
checkQueryComplexity,
|
|
218
|
+
QueryRateLimiter
|
|
219
|
+
};
|
package/shared/time-utils.js
CHANGED
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
// VMOO时区处理工具函数
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* VMOO时区处理函数 - 将UTC时间戳转换为北京时间
|
|
5
|
-
* @param {number} utcTimestamp UTC时间戳
|
|
6
|
-
* @param {string} format 格式化字符串
|
|
7
|
-
* @returns {string} 格式化后的北京时间
|
|
8
|
-
*/
|
|
9
|
-
function toVmooDate(utcTimestamp, format = 'Y-m-d H:i:s') {
|
|
10
|
-
if (!utcTimestamp) return '';
|
|
11
|
-
|
|
12
|
-
// VMOO的TIME_ZONE配置为8(北京时间)
|
|
13
|
-
const timezone = 8;
|
|
14
|
-
const beijingTime = utcTimestamp + timezone * 3600;
|
|
15
|
-
|
|
16
|
-
const date = new Date(beijingTime * 1000);
|
|
17
|
-
|
|
18
|
-
// 格式化日期
|
|
19
|
-
const year = date.getFullYear();
|
|
20
|
-
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
21
|
-
const day = String(date.getDate()).padStart(2, '0');
|
|
22
|
-
const hour = String(date.getHours()).padStart(2, '0');
|
|
23
|
-
const minute = String(date.getMinutes()).padStart(2, '0');
|
|
24
|
-
const second = String(date.getSeconds()).padStart(2, '0');
|
|
25
|
-
|
|
26
|
-
if (format === 'Y-m-d H:i:s') {
|
|
27
|
-
return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
|
|
28
|
-
} else if (format === 'Y-m-d') {
|
|
29
|
-
return `${year}-${month}-${day}`;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* 处理查询结果中的时间字段
|
|
37
|
-
* @param {Array} rows 查询结果
|
|
38
|
-
* @returns {Array} 处理后的结果
|
|
39
|
-
*/
|
|
40
|
-
function processTimeFields(rows) {
|
|
41
|
-
if (!Array.isArray(rows)) return rows;
|
|
42
|
-
|
|
43
|
-
const timeFields = ['create_time', 'update_time', 'delivery_time', 'pickup_time', 'order_time', 'pay_time'];
|
|
44
|
-
|
|
45
|
-
return rows.map(row => {
|
|
46
|
-
const processedRow = { ...row };
|
|
47
|
-
|
|
48
|
-
timeFields.forEach(field => {
|
|
49
|
-
if (processedRow[field] && typeof processedRow[field] === 'number') {
|
|
50
|
-
// 添加格式化后的时间字段
|
|
51
|
-
processedRow[`${field}_formatted`] = toVmooDate(processedRow[field]);
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
return processedRow;
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export {
|
|
60
|
-
toVmooDate,
|
|
61
|
-
processTimeFields
|
|
62
|
-
};
|
|
1
|
+
// VMOO时区处理工具函数
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* VMOO时区处理函数 - 将UTC时间戳转换为北京时间
|
|
5
|
+
* @param {number} utcTimestamp UTC时间戳
|
|
6
|
+
* @param {string} format 格式化字符串
|
|
7
|
+
* @returns {string} 格式化后的北京时间
|
|
8
|
+
*/
|
|
9
|
+
function toVmooDate(utcTimestamp, format = 'Y-m-d H:i:s') {
|
|
10
|
+
if (!utcTimestamp) return '';
|
|
11
|
+
|
|
12
|
+
// VMOO的TIME_ZONE配置为8(北京时间)
|
|
13
|
+
const timezone = 8;
|
|
14
|
+
const beijingTime = utcTimestamp + timezone * 3600;
|
|
15
|
+
|
|
16
|
+
const date = new Date(beijingTime * 1000);
|
|
17
|
+
|
|
18
|
+
// 格式化日期
|
|
19
|
+
const year = date.getFullYear();
|
|
20
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
21
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
22
|
+
const hour = String(date.getHours()).padStart(2, '0');
|
|
23
|
+
const minute = String(date.getMinutes()).padStart(2, '0');
|
|
24
|
+
const second = String(date.getSeconds()).padStart(2, '0');
|
|
25
|
+
|
|
26
|
+
if (format === 'Y-m-d H:i:s') {
|
|
27
|
+
return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
|
|
28
|
+
} else if (format === 'Y-m-d') {
|
|
29
|
+
return `${year}-${month}-${day}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 处理查询结果中的时间字段
|
|
37
|
+
* @param {Array} rows 查询结果
|
|
38
|
+
* @returns {Array} 处理后的结果
|
|
39
|
+
*/
|
|
40
|
+
function processTimeFields(rows) {
|
|
41
|
+
if (!Array.isArray(rows)) return rows;
|
|
42
|
+
|
|
43
|
+
const timeFields = ['create_time', 'update_time', 'delivery_time', 'pickup_time', 'order_time', 'pay_time'];
|
|
44
|
+
|
|
45
|
+
return rows.map(row => {
|
|
46
|
+
const processedRow = { ...row };
|
|
47
|
+
|
|
48
|
+
timeFields.forEach(field => {
|
|
49
|
+
if (processedRow[field] && typeof processedRow[field] === 'number') {
|
|
50
|
+
// 添加格式化后的时间字段
|
|
51
|
+
processedRow[`${field}_formatted`] = toVmooDate(processedRow[field]);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return processedRow;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export {
|
|
60
|
+
toVmooDate,
|
|
61
|
+
processTimeFields
|
|
62
|
+
};
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
{
|
|
2
|
-
"database": {
|
|
3
|
-
"host": "118.25.190.11",
|
|
4
|
-
"port": 3306,
|
|
5
|
-
"user": "ceshi_deliver",
|
|
6
|
-
"password": "
|
|
7
|
-
"database": "ceshi_deliver",
|
|
8
|
-
"charset": "utf8mb4"
|
|
9
|
-
},
|
|
10
|
-
"security": {
|
|
11
|
-
"level": "development",
|
|
12
|
-
"description": "配送测试站:允许查询、插入、更新,禁止删除操作",
|
|
13
|
-
"features": [
|
|
14
|
-
"SELECT、INSERT、UPDATE、ALTER TABLE、CREATE TABLE",
|
|
15
|
-
"禁止DELETE、DROP TABLE、DROP DATABASE",
|
|
16
|
-
"自动LIMIT限制",
|
|
17
|
-
"时区自动转换"
|
|
18
|
-
]
|
|
19
|
-
},
|
|
20
|
-
"server": {
|
|
21
|
-
"name": "vmoo-database-deliver-server",
|
|
22
|
-
"version": "1.0.0",
|
|
23
|
-
"description": "配送测试站数据库服务器"
|
|
24
|
-
}
|
|
25
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"database": {
|
|
3
|
+
"host": "118.25.190.11",
|
|
4
|
+
"port": 3306,
|
|
5
|
+
"user": "ceshi_deliver",
|
|
6
|
+
"password": "${DELIVER_PASSWORD}",
|
|
7
|
+
"database": "ceshi_deliver",
|
|
8
|
+
"charset": "utf8mb4"
|
|
9
|
+
},
|
|
10
|
+
"security": {
|
|
11
|
+
"level": "development",
|
|
12
|
+
"description": "配送测试站:允许查询、插入、更新,禁止删除操作",
|
|
13
|
+
"features": [
|
|
14
|
+
"SELECT、INSERT、UPDATE、ALTER TABLE、CREATE TABLE",
|
|
15
|
+
"禁止DELETE、DROP TABLE、DROP DATABASE",
|
|
16
|
+
"自动LIMIT限制",
|
|
17
|
+
"时区自动转换"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"server": {
|
|
21
|
+
"name": "vmoo-database-deliver-server",
|
|
22
|
+
"version": "1.0.0",
|
|
23
|
+
"description": "配送测试站数据库服务器"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "vmoo-database-deliver-server",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"description": "VMOO配送测试站数据库MCP服务器",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "server.js",
|
|
7
|
-
"scripts": {
|
|
8
|
-
"start": "node server.js",
|
|
9
|
-
"dev": "node server.js",
|
|
10
|
-
"test": "echo \"配送测试站MCP服务器测试通过\" && exit 0"
|
|
11
|
-
},
|
|
12
|
-
"dependencies": {
|
|
13
|
-
"@modelcontextprotocol/sdk": "^1.17.2",
|
|
14
|
-
"mysql2": "^3.6.5"
|
|
15
|
-
},
|
|
16
|
-
"keywords": [
|
|
17
|
-
"mcp",
|
|
18
|
-
"database",
|
|
19
|
-
"mysql",
|
|
20
|
-
"vmoo",
|
|
21
|
-
"deliver",
|
|
22
|
-
"配送测试站"
|
|
23
|
-
],
|
|
24
|
-
"author": "VMOO Team",
|
|
25
|
-
"license": "MIT"
|
|
26
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "vmoo-database-deliver-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "VMOO配送测试站数据库MCP服务器",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "server.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"start": "node server.js",
|
|
9
|
+
"dev": "node server.js",
|
|
10
|
+
"test": "echo \"配送测试站MCP服务器测试通过\" && exit 0"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@modelcontextprotocol/sdk": "^1.17.2",
|
|
14
|
+
"mysql2": "^3.6.5"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"database",
|
|
19
|
+
"mysql",
|
|
20
|
+
"vmoo",
|
|
21
|
+
"deliver",
|
|
22
|
+
"配送测试站"
|
|
23
|
+
],
|
|
24
|
+
"author": "VMOO Team",
|
|
25
|
+
"license": "MIT"
|
|
26
|
+
}
|