vmoo-mcp-database-server 1.2.0 → 1.2.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/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 +102 -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/privacy-utils.js
CHANGED
|
@@ -1,207 +1,207 @@
|
|
|
1
|
-
// 个人信息匿名化工具
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* 手机号码匿名化
|
|
5
|
-
* @param {string} phone 手机号码
|
|
6
|
-
* @returns {string} 匿名化后的手机号
|
|
7
|
-
*/
|
|
8
|
-
function anonymizePhone(phone) {
|
|
9
|
-
if (!phone || typeof phone !== 'string') return phone;
|
|
10
|
-
|
|
11
|
-
// 移除所有非数字字符
|
|
12
|
-
const cleanPhone = phone.replace(/\D/g, '');
|
|
13
|
-
|
|
14
|
-
if (cleanPhone.length === 11) {
|
|
15
|
-
// 中国手机号:138****1234
|
|
16
|
-
return `${cleanPhone.substring(0, 3)}****${cleanPhone.substring(7)}`;
|
|
17
|
-
} else if (cleanPhone.length >= 7) {
|
|
18
|
-
// 其他格式:保留前3位和后4位
|
|
19
|
-
const start = cleanPhone.substring(0, 3);
|
|
20
|
-
const end = cleanPhone.substring(cleanPhone.length - 4);
|
|
21
|
-
const stars = '*'.repeat(Math.max(4, cleanPhone.length - 7));
|
|
22
|
-
return `${start}${stars}${end}`;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return phone; // 格式不符合,返回原值
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* 身份证号匿名化
|
|
30
|
-
* @param {string} idCard 身份证号
|
|
31
|
-
* @returns {string} 匿名化后的身份证号
|
|
32
|
-
*/
|
|
33
|
-
function anonymizeIdCard(idCard) {
|
|
34
|
-
if (!idCard || typeof idCard !== 'string') return idCard;
|
|
35
|
-
|
|
36
|
-
const cleanId = idCard.replace(/\s/g, '');
|
|
37
|
-
|
|
38
|
-
if (cleanId.length === 18) {
|
|
39
|
-
// 18位身份证:320***********1234
|
|
40
|
-
return `${cleanId.substring(0, 3)}${'*'.repeat(11)}${cleanId.substring(14)}`;
|
|
41
|
-
} else if (cleanId.length === 15) {
|
|
42
|
-
// 15位身份证:320*********34
|
|
43
|
-
return `${cleanId.substring(0, 3)}${'*'.repeat(9)}${cleanId.substring(13)}`;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return idCard; // 格式不符合,返回原值
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* 邮箱地址匿名化
|
|
51
|
-
* @param {string} email 邮箱地址
|
|
52
|
-
* @returns {string} 匿名化后的邮箱
|
|
53
|
-
*/
|
|
54
|
-
function anonymizeEmail(email) {
|
|
55
|
-
if (!email || typeof email !== 'string') return email;
|
|
56
|
-
|
|
57
|
-
const emailRegex = /^([^@]+)@(.+)$/;
|
|
58
|
-
const match = email.match(emailRegex);
|
|
59
|
-
|
|
60
|
-
if (match) {
|
|
61
|
-
const [, username, domain] = match;
|
|
62
|
-
|
|
63
|
-
if (username.length <= 3) {
|
|
64
|
-
// 短用户名:u**@example.com
|
|
65
|
-
return `${username.charAt(0)}${'*'.repeat(Math.max(2, username.length - 1))}@${domain}`;
|
|
66
|
-
} else {
|
|
67
|
-
// 长用户名:user***@example.com
|
|
68
|
-
return `${username.substring(0, 4)}${'*'.repeat(Math.max(3, username.length - 4))}@${domain}`;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return email; // 格式不符合,返回原值
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* 真实姓名匿名化
|
|
77
|
-
* @param {string} name 真实姓名
|
|
78
|
-
* @returns {string} 匿名化后的姓名
|
|
79
|
-
*/
|
|
80
|
-
function anonymizeName(name) {
|
|
81
|
-
if (!name || typeof name !== 'string') return name;
|
|
82
|
-
|
|
83
|
-
const trimmedName = name.trim();
|
|
84
|
-
|
|
85
|
-
if (trimmedName.length === 1) {
|
|
86
|
-
return trimmedName; // 单字名不处理
|
|
87
|
-
} else if (trimmedName.length === 2) {
|
|
88
|
-
// 两字名:张*
|
|
89
|
-
return `${trimmedName.charAt(0)}*`;
|
|
90
|
-
} else if (trimmedName.length === 3) {
|
|
91
|
-
// 三字名:张**
|
|
92
|
-
return `${trimmedName.charAt(0)}**`;
|
|
93
|
-
} else {
|
|
94
|
-
// 长姓名:保留第一个字,其余用*
|
|
95
|
-
return `${trimmedName.charAt(0)}${'*'.repeat(Math.min(2, trimmedName.length - 1))}`;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* 地址匿名化
|
|
101
|
-
* @param {string} address 地址
|
|
102
|
-
* @returns {string} 匿名化后的地址
|
|
103
|
-
*/
|
|
104
|
-
function anonymizeAddress(address) {
|
|
105
|
-
if (!address || typeof address !== 'string') return address;
|
|
106
|
-
|
|
107
|
-
// 保留省市,隐藏详细地址
|
|
108
|
-
const addressRegex = /(.*?[省市区县])(.*)/;
|
|
109
|
-
const match = address.match(addressRegex);
|
|
110
|
-
|
|
111
|
-
if (match && match[2].length > 6) {
|
|
112
|
-
const [, region, detail] = match;
|
|
113
|
-
return `${region}***${detail.substring(detail.length - 3)}`;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return address;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* 银行卡号匿名化
|
|
121
|
-
* @param {string} bankCard 银行卡号
|
|
122
|
-
* @returns {string} 匿名化后的银行卡号
|
|
123
|
-
*/
|
|
124
|
-
function anonymizeBankCard(bankCard) {
|
|
125
|
-
if (!bankCard || typeof bankCard !== 'string') return bankCard;
|
|
126
|
-
|
|
127
|
-
const cleanCard = bankCard.replace(/\s/g, '');
|
|
128
|
-
|
|
129
|
-
if (cleanCard.length >= 12) {
|
|
130
|
-
// 保留前4位和后4位
|
|
131
|
-
const start = cleanCard.substring(0, 4);
|
|
132
|
-
const end = cleanCard.substring(cleanCard.length - 4);
|
|
133
|
-
const stars = '*'.repeat(Math.max(4, cleanCard.length - 8));
|
|
134
|
-
return `${start} ${stars} ${end}`;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return bankCard;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* 对查询结果进行数据匿名化处理
|
|
142
|
-
* @param {Array} rows 查询结果
|
|
143
|
-
* @returns {Array} 匿名化后的结果
|
|
144
|
-
*/
|
|
145
|
-
function anonymizeQueryResults(rows) {
|
|
146
|
-
if (!Array.isArray(rows)) return rows;
|
|
147
|
-
|
|
148
|
-
// 需要匿名化的字段映射
|
|
149
|
-
const fieldMappings = {
|
|
150
|
-
// 手机号字段
|
|
151
|
-
mobile: anonymizePhone,
|
|
152
|
-
phone: anonymizePhone,
|
|
153
|
-
tel: anonymizePhone,
|
|
154
|
-
telephone: anonymizePhone,
|
|
155
|
-
|
|
156
|
-
// 身份证字段
|
|
157
|
-
id_card: anonymizeIdCard,
|
|
158
|
-
identity_card: anonymizeIdCard,
|
|
159
|
-
id_number: anonymizeIdCard,
|
|
160
|
-
|
|
161
|
-
// 邮箱字段
|
|
162
|
-
email: anonymizeEmail,
|
|
163
|
-
mail: anonymizeEmail,
|
|
164
|
-
|
|
165
|
-
// 姓名字段
|
|
166
|
-
name: anonymizeName,
|
|
167
|
-
real_name: anonymizeName,
|
|
168
|
-
user_name: anonymizeName,
|
|
169
|
-
consignee: anonymizeName,
|
|
170
|
-
|
|
171
|
-
// 地址字段
|
|
172
|
-
address: anonymizeAddress,
|
|
173
|
-
delivery_address: anonymizeAddress,
|
|
174
|
-
|
|
175
|
-
// 银行卡字段
|
|
176
|
-
bank_card: anonymizeBankCard,
|
|
177
|
-
card_number: anonymizeBankCard
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
return rows.map(row => {
|
|
181
|
-
const anonymizedRow = { ...row };
|
|
182
|
-
|
|
183
|
-
Object.keys(anonymizedRow).forEach(key => {
|
|
184
|
-
const lowerKey = key.toLowerCase();
|
|
185
|
-
|
|
186
|
-
// 检查是否需要匿名化
|
|
187
|
-
Object.keys(fieldMappings).forEach(fieldPattern => {
|
|
188
|
-
if (lowerKey.includes(fieldPattern)) {
|
|
189
|
-
const anonymizeFunc = fieldMappings[fieldPattern];
|
|
190
|
-
anonymizedRow[key] = anonymizeFunc(anonymizedRow[key]);
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
return anonymizedRow;
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
export {
|
|
200
|
-
anonymizePhone,
|
|
201
|
-
anonymizeIdCard,
|
|
202
|
-
anonymizeEmail,
|
|
203
|
-
anonymizeName,
|
|
204
|
-
anonymizeAddress,
|
|
205
|
-
anonymizeBankCard,
|
|
206
|
-
anonymizeQueryResults
|
|
207
|
-
};
|
|
1
|
+
// 个人信息匿名化工具
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 手机号码匿名化
|
|
5
|
+
* @param {string} phone 手机号码
|
|
6
|
+
* @returns {string} 匿名化后的手机号
|
|
7
|
+
*/
|
|
8
|
+
function anonymizePhone(phone) {
|
|
9
|
+
if (!phone || typeof phone !== 'string') return phone;
|
|
10
|
+
|
|
11
|
+
// 移除所有非数字字符
|
|
12
|
+
const cleanPhone = phone.replace(/\D/g, '');
|
|
13
|
+
|
|
14
|
+
if (cleanPhone.length === 11) {
|
|
15
|
+
// 中国手机号:138****1234
|
|
16
|
+
return `${cleanPhone.substring(0, 3)}****${cleanPhone.substring(7)}`;
|
|
17
|
+
} else if (cleanPhone.length >= 7) {
|
|
18
|
+
// 其他格式:保留前3位和后4位
|
|
19
|
+
const start = cleanPhone.substring(0, 3);
|
|
20
|
+
const end = cleanPhone.substring(cleanPhone.length - 4);
|
|
21
|
+
const stars = '*'.repeat(Math.max(4, cleanPhone.length - 7));
|
|
22
|
+
return `${start}${stars}${end}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return phone; // 格式不符合,返回原值
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 身份证号匿名化
|
|
30
|
+
* @param {string} idCard 身份证号
|
|
31
|
+
* @returns {string} 匿名化后的身份证号
|
|
32
|
+
*/
|
|
33
|
+
function anonymizeIdCard(idCard) {
|
|
34
|
+
if (!idCard || typeof idCard !== 'string') return idCard;
|
|
35
|
+
|
|
36
|
+
const cleanId = idCard.replace(/\s/g, '');
|
|
37
|
+
|
|
38
|
+
if (cleanId.length === 18) {
|
|
39
|
+
// 18位身份证:320***********1234
|
|
40
|
+
return `${cleanId.substring(0, 3)}${'*'.repeat(11)}${cleanId.substring(14)}`;
|
|
41
|
+
} else if (cleanId.length === 15) {
|
|
42
|
+
// 15位身份证:320*********34
|
|
43
|
+
return `${cleanId.substring(0, 3)}${'*'.repeat(9)}${cleanId.substring(13)}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return idCard; // 格式不符合,返回原值
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 邮箱地址匿名化
|
|
51
|
+
* @param {string} email 邮箱地址
|
|
52
|
+
* @returns {string} 匿名化后的邮箱
|
|
53
|
+
*/
|
|
54
|
+
function anonymizeEmail(email) {
|
|
55
|
+
if (!email || typeof email !== 'string') return email;
|
|
56
|
+
|
|
57
|
+
const emailRegex = /^([^@]+)@(.+)$/;
|
|
58
|
+
const match = email.match(emailRegex);
|
|
59
|
+
|
|
60
|
+
if (match) {
|
|
61
|
+
const [, username, domain] = match;
|
|
62
|
+
|
|
63
|
+
if (username.length <= 3) {
|
|
64
|
+
// 短用户名:u**@example.com
|
|
65
|
+
return `${username.charAt(0)}${'*'.repeat(Math.max(2, username.length - 1))}@${domain}`;
|
|
66
|
+
} else {
|
|
67
|
+
// 长用户名:user***@example.com
|
|
68
|
+
return `${username.substring(0, 4)}${'*'.repeat(Math.max(3, username.length - 4))}@${domain}`;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return email; // 格式不符合,返回原值
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 真实姓名匿名化
|
|
77
|
+
* @param {string} name 真实姓名
|
|
78
|
+
* @returns {string} 匿名化后的姓名
|
|
79
|
+
*/
|
|
80
|
+
function anonymizeName(name) {
|
|
81
|
+
if (!name || typeof name !== 'string') return name;
|
|
82
|
+
|
|
83
|
+
const trimmedName = name.trim();
|
|
84
|
+
|
|
85
|
+
if (trimmedName.length === 1) {
|
|
86
|
+
return trimmedName; // 单字名不处理
|
|
87
|
+
} else if (trimmedName.length === 2) {
|
|
88
|
+
// 两字名:张*
|
|
89
|
+
return `${trimmedName.charAt(0)}*`;
|
|
90
|
+
} else if (trimmedName.length === 3) {
|
|
91
|
+
// 三字名:张**
|
|
92
|
+
return `${trimmedName.charAt(0)}**`;
|
|
93
|
+
} else {
|
|
94
|
+
// 长姓名:保留第一个字,其余用*
|
|
95
|
+
return `${trimmedName.charAt(0)}${'*'.repeat(Math.min(2, trimmedName.length - 1))}`;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 地址匿名化
|
|
101
|
+
* @param {string} address 地址
|
|
102
|
+
* @returns {string} 匿名化后的地址
|
|
103
|
+
*/
|
|
104
|
+
function anonymizeAddress(address) {
|
|
105
|
+
if (!address || typeof address !== 'string') return address;
|
|
106
|
+
|
|
107
|
+
// 保留省市,隐藏详细地址
|
|
108
|
+
const addressRegex = /(.*?[省市区县])(.*)/;
|
|
109
|
+
const match = address.match(addressRegex);
|
|
110
|
+
|
|
111
|
+
if (match && match[2].length > 6) {
|
|
112
|
+
const [, region, detail] = match;
|
|
113
|
+
return `${region}***${detail.substring(detail.length - 3)}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return address;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 银行卡号匿名化
|
|
121
|
+
* @param {string} bankCard 银行卡号
|
|
122
|
+
* @returns {string} 匿名化后的银行卡号
|
|
123
|
+
*/
|
|
124
|
+
function anonymizeBankCard(bankCard) {
|
|
125
|
+
if (!bankCard || typeof bankCard !== 'string') return bankCard;
|
|
126
|
+
|
|
127
|
+
const cleanCard = bankCard.replace(/\s/g, '');
|
|
128
|
+
|
|
129
|
+
if (cleanCard.length >= 12) {
|
|
130
|
+
// 保留前4位和后4位
|
|
131
|
+
const start = cleanCard.substring(0, 4);
|
|
132
|
+
const end = cleanCard.substring(cleanCard.length - 4);
|
|
133
|
+
const stars = '*'.repeat(Math.max(4, cleanCard.length - 8));
|
|
134
|
+
return `${start} ${stars} ${end}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return bankCard;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 对查询结果进行数据匿名化处理
|
|
142
|
+
* @param {Array} rows 查询结果
|
|
143
|
+
* @returns {Array} 匿名化后的结果
|
|
144
|
+
*/
|
|
145
|
+
function anonymizeQueryResults(rows) {
|
|
146
|
+
if (!Array.isArray(rows)) return rows;
|
|
147
|
+
|
|
148
|
+
// 需要匿名化的字段映射
|
|
149
|
+
const fieldMappings = {
|
|
150
|
+
// 手机号字段
|
|
151
|
+
mobile: anonymizePhone,
|
|
152
|
+
phone: anonymizePhone,
|
|
153
|
+
tel: anonymizePhone,
|
|
154
|
+
telephone: anonymizePhone,
|
|
155
|
+
|
|
156
|
+
// 身份证字段
|
|
157
|
+
id_card: anonymizeIdCard,
|
|
158
|
+
identity_card: anonymizeIdCard,
|
|
159
|
+
id_number: anonymizeIdCard,
|
|
160
|
+
|
|
161
|
+
// 邮箱字段
|
|
162
|
+
email: anonymizeEmail,
|
|
163
|
+
mail: anonymizeEmail,
|
|
164
|
+
|
|
165
|
+
// 姓名字段
|
|
166
|
+
name: anonymizeName,
|
|
167
|
+
real_name: anonymizeName,
|
|
168
|
+
user_name: anonymizeName,
|
|
169
|
+
consignee: anonymizeName,
|
|
170
|
+
|
|
171
|
+
// 地址字段
|
|
172
|
+
address: anonymizeAddress,
|
|
173
|
+
delivery_address: anonymizeAddress,
|
|
174
|
+
|
|
175
|
+
// 银行卡字段
|
|
176
|
+
bank_card: anonymizeBankCard,
|
|
177
|
+
card_number: anonymizeBankCard
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
return rows.map(row => {
|
|
181
|
+
const anonymizedRow = { ...row };
|
|
182
|
+
|
|
183
|
+
Object.keys(anonymizedRow).forEach(key => {
|
|
184
|
+
const lowerKey = key.toLowerCase();
|
|
185
|
+
|
|
186
|
+
// 检查是否需要匿名化
|
|
187
|
+
Object.keys(fieldMappings).forEach(fieldPattern => {
|
|
188
|
+
if (lowerKey.includes(fieldPattern)) {
|
|
189
|
+
const anonymizeFunc = fieldMappings[fieldPattern];
|
|
190
|
+
anonymizedRow[key] = anonymizeFunc(anonymizedRow[key]);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
return anonymizedRow;
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export {
|
|
200
|
+
anonymizePhone,
|
|
201
|
+
anonymizeIdCard,
|
|
202
|
+
anonymizeEmail,
|
|
203
|
+
anonymizeName,
|
|
204
|
+
anonymizeAddress,
|
|
205
|
+
anonymizeBankCard,
|
|
206
|
+
anonymizeQueryResults
|
|
207
|
+
};
|