vmoo-mcp-database-server 1.0.0
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 +178 -0
- package/bin/vmoo-mcp-dev.js +45 -0
- package/bin/vmoo-mcp-prod.js +45 -0
- package/index.js +38 -0
- package/package.json +58 -0
- package/shared/database-utils.js +102 -0
- package/shared/privacy-utils.js +207 -0
- package/shared/security-utils.js +219 -0
- package/shared/time-utils.js +62 -0
- package/vmoo-database-dev/config.json +25 -0
- package/vmoo-database-dev/package.json +32 -0
- package/vmoo-database-dev/server.js +294 -0
- package/vmoo-database-prod/config.json +41 -0
- package/vmoo-database-prod/package.json +33 -0
- package/vmoo-database-prod/server.js +313 -0
package/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# vmoo-mcp-database-server
|
|
2
|
+
|
|
3
|
+
VMOO数据库MCP服务器 - 支持开发和生产环境的安全数据库访问
|
|
4
|
+
|
|
5
|
+
[](https://badge.fury.io/js/vmoo-mcp-database-server)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## 🚀 快速开始
|
|
9
|
+
|
|
10
|
+
### 使用npx直接运行(推荐)
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# 开发环境
|
|
14
|
+
npx vmoo-mcp-database-server@latest --env=dev
|
|
15
|
+
|
|
16
|
+
# 生产环境
|
|
17
|
+
npx vmoo-mcp-database-server@latest --env=prod
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### 在Augment中配置
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"mcpServers": {
|
|
25
|
+
"vmoo-database-dev": {
|
|
26
|
+
"command": "npx",
|
|
27
|
+
"args": ["vmoo-mcp-database-server@latest", "--env=dev"]
|
|
28
|
+
},
|
|
29
|
+
"vmoo-database-prod": {
|
|
30
|
+
"command": "npx",
|
|
31
|
+
"args": ["vmoo-mcp-database-server@latest", "--env=prod"]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 1. 安装依赖
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 2. 测试数据库连接
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm test
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
如果看到 ✅ 连接成功,说明配置正确。
|
|
50
|
+
|
|
51
|
+
### 3. 启动MCP服务器
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm start
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## 🔧 在Augment中配置
|
|
58
|
+
|
|
59
|
+
### 找到Augment配置文件
|
|
60
|
+
|
|
61
|
+
- **Windows**: `%APPDATA%\Augment\config.json`
|
|
62
|
+
- **macOS**: `~/Library/Application Support/Augment/config.json`
|
|
63
|
+
- **Linux**: `~/.config/Augment/config.json`
|
|
64
|
+
|
|
65
|
+
### 添加MCP服务器配置
|
|
66
|
+
|
|
67
|
+
在配置文件中添加:
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"mcpServers": {
|
|
72
|
+
"vmoo-database": {
|
|
73
|
+
"command": "node",
|
|
74
|
+
"args": ["D:\\代码库\\vmoo-mcp-server\\mcp-vmoo-database-server.js"],
|
|
75
|
+
"env": {}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**注意**: 请将路径替换为你的实际路径。
|
|
82
|
+
|
|
83
|
+
### 重启Augment
|
|
84
|
+
|
|
85
|
+
保存配置文件后,重启Augment应用。
|
|
86
|
+
|
|
87
|
+
## 🛠️ 可用工具
|
|
88
|
+
|
|
89
|
+
配置成功后,你可以在Augment中直接询问:
|
|
90
|
+
|
|
91
|
+
### 1. 查询数据库
|
|
92
|
+
- "列出所有fanwe_开头的表"
|
|
93
|
+
- "查询最近注册的10个用户"
|
|
94
|
+
- "显示fanwe_menu表中价格最高的商品"
|
|
95
|
+
|
|
96
|
+
### 2. 查看表结构
|
|
97
|
+
- "fanwe_user表的结构是什么?"
|
|
98
|
+
- "描述fanwe_order表的字段"
|
|
99
|
+
|
|
100
|
+
### 3. 获取示例数据
|
|
101
|
+
- "显示fanwe_menu表的前5条记录"
|
|
102
|
+
- "fanwe_user表的示例数据"
|
|
103
|
+
|
|
104
|
+
### 4. 统计信息
|
|
105
|
+
- "fanwe_user表有多少条记录?"
|
|
106
|
+
- "数据库中有多少个表?"
|
|
107
|
+
|
|
108
|
+
## 📊 数据库信息
|
|
109
|
+
|
|
110
|
+
- **主机**: 118.25.190.11:3306
|
|
111
|
+
- **数据库**: ceshi_v_moo_com
|
|
112
|
+
- **表前缀**: fanwe_
|
|
113
|
+
- **字符集**: utf8mb4
|
|
114
|
+
|
|
115
|
+
## 🔒 安全特性
|
|
116
|
+
|
|
117
|
+
### 允许的操作
|
|
118
|
+
- **查询操作**: SELECT、SHOW、DESCRIBE、EXPLAIN
|
|
119
|
+
- **数据修改**: INSERT、UPDATE(仅限新增和修改)
|
|
120
|
+
- **表结构**: CREATE TABLE、ALTER TABLE
|
|
121
|
+
|
|
122
|
+
### 安全防护
|
|
123
|
+
- **禁止所有删除操作**: 完全阻止DELETE、DROP TABLE、DROP DATABASE等删除操作
|
|
124
|
+
- **测试环境专用**: 专为测试环境设计,确保数据安全
|
|
125
|
+
- **查询限制**: 自动添加LIMIT防止大量数据返回
|
|
126
|
+
- **SQL注入防护**: 使用参数化查询
|
|
127
|
+
- **连接池管理**: 自动管理数据库连接
|
|
128
|
+
- **错误处理**: 完善的错误捕获和提示
|
|
129
|
+
|
|
130
|
+
## 📝 使用示例
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
用户: "VMOO数据库中有哪些用户相关的表?"
|
|
134
|
+
AI: 调用list_tables工具,返回包含user的表列表
|
|
135
|
+
|
|
136
|
+
用户: "fanwe_user表的字段都有什么含义?"
|
|
137
|
+
AI: 调用describe_table工具,返回表结构详情
|
|
138
|
+
|
|
139
|
+
用户: "最近一周注册的用户有多少?"
|
|
140
|
+
AI: 调用query_database工具,执行相应SQL查询
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## 🛠️ 开发调试
|
|
144
|
+
|
|
145
|
+
### 启用调试模式
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
npm run dev
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
这将启用Node.js调试器,可以在Chrome DevTools中调试。
|
|
152
|
+
|
|
153
|
+
### 查看日志
|
|
154
|
+
|
|
155
|
+
MCP服务器的日志会输出到stderr,可以通过以下方式查看:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
node mcp-vmoo-database-server.js 2> debug.log
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## 🔧 故障排除
|
|
162
|
+
|
|
163
|
+
### 连接问题
|
|
164
|
+
- 检查网络连接
|
|
165
|
+
- 确认数据库服务器可访问
|
|
166
|
+
- 验证防火墙设置
|
|
167
|
+
|
|
168
|
+
### 权限问题
|
|
169
|
+
- 确保数据库用户有查询权限
|
|
170
|
+
- 检查表访问权限
|
|
171
|
+
|
|
172
|
+
### 配置问题
|
|
173
|
+
- 验证Augment配置文件路径正确
|
|
174
|
+
- 确保Node.js版本 >= 18.0.0
|
|
175
|
+
|
|
176
|
+
## 📄 许可证
|
|
177
|
+
|
|
178
|
+
MIT License
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// VMOO开发环境MCP服务器启动器
|
|
4
|
+
|
|
5
|
+
import { spawn } from 'child_process';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
|
|
12
|
+
// 解析命令行参数
|
|
13
|
+
const args = process.argv.slice(2);
|
|
14
|
+
const configPath = args.find(arg => arg.startsWith('--config='))?.split('=')[1];
|
|
15
|
+
|
|
16
|
+
console.log('🟡 启动VMOO开发环境MCP服务器...');
|
|
17
|
+
|
|
18
|
+
// 服务器文件路径
|
|
19
|
+
const serverPath = path.join(__dirname, '../vmoo-database-dev/server.js');
|
|
20
|
+
|
|
21
|
+
// 启动服务器
|
|
22
|
+
const serverProcess = spawn('node', [serverPath], {
|
|
23
|
+
stdio: 'inherit',
|
|
24
|
+
env: {
|
|
25
|
+
...process.env,
|
|
26
|
+
...(configPath && { CONFIG_PATH: configPath })
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// 处理进程退出
|
|
31
|
+
serverProcess.on('close', (code) => {
|
|
32
|
+
console.log(`🟡 开发环境MCP服务器已退出,退出码: ${code}`);
|
|
33
|
+
process.exit(code);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// 处理中断信号
|
|
37
|
+
process.on('SIGINT', () => {
|
|
38
|
+
console.log('\n🛑 正在关闭开发环境MCP服务器...');
|
|
39
|
+
serverProcess.kill('SIGINT');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
process.on('SIGTERM', () => {
|
|
43
|
+
console.log('\n🛑 正在关闭开发环境MCP服务器...');
|
|
44
|
+
serverProcess.kill('SIGTERM');
|
|
45
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// VMOO生产环境MCP服务器启动器
|
|
4
|
+
|
|
5
|
+
import { spawn } from 'child_process';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
|
|
12
|
+
// 解析命令行参数
|
|
13
|
+
const args = process.argv.slice(2);
|
|
14
|
+
const configPath = args.find(arg => arg.startsWith('--config='))?.split('=')[1];
|
|
15
|
+
|
|
16
|
+
console.log('🔴 启动VMOO生产环境MCP服务器...');
|
|
17
|
+
|
|
18
|
+
// 服务器文件路径
|
|
19
|
+
const serverPath = path.join(__dirname, '../vmoo-database-prod/server.js');
|
|
20
|
+
|
|
21
|
+
// 启动服务器
|
|
22
|
+
const serverProcess = spawn('node', [serverPath], {
|
|
23
|
+
stdio: 'inherit',
|
|
24
|
+
env: {
|
|
25
|
+
...process.env,
|
|
26
|
+
...(configPath && { CONFIG_PATH: configPath })
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// 处理进程退出
|
|
31
|
+
serverProcess.on('close', (code) => {
|
|
32
|
+
console.log(`🔴 生产环境MCP服务器已退出,退出码: ${code}`);
|
|
33
|
+
process.exit(code);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// 处理中断信号
|
|
37
|
+
process.on('SIGINT', () => {
|
|
38
|
+
console.log('\n🛑 正在关闭生产环境MCP服务器...');
|
|
39
|
+
serverProcess.kill('SIGINT');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
process.on('SIGTERM', () => {
|
|
43
|
+
console.log('\n🛑 正在关闭生产环境MCP服务器...');
|
|
44
|
+
serverProcess.kill('SIGTERM');
|
|
45
|
+
});
|
package/index.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// VMOO MCP数据库服务器包主入口
|
|
4
|
+
|
|
5
|
+
export { createConnectionPool, executeQuery } from './shared/database-utils.js';
|
|
6
|
+
export { SECURITY_LEVELS, performSecurityCheck } from './shared/security-utils.js';
|
|
7
|
+
export { processTimeFields } from './shared/time-utils.js';
|
|
8
|
+
export { anonymizeQueryResults } from './shared/privacy-utils.js';
|
|
9
|
+
|
|
10
|
+
// 默认导出服务器启动函数
|
|
11
|
+
export default function startServer(environment = 'dev') {
|
|
12
|
+
const { spawn } = require('child_process');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
const serverPath = environment === 'prod'
|
|
16
|
+
? path.join(__dirname, 'vmoo-database-prod/server.js')
|
|
17
|
+
: path.join(__dirname, 'vmoo-database-dev/server.js');
|
|
18
|
+
|
|
19
|
+
return spawn('node', [serverPath], { stdio: 'inherit' });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 如果直接运行此文件,显示帮助信息
|
|
23
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
24
|
+
console.log(`
|
|
25
|
+
🟡 VMOO MCP数据库服务器
|
|
26
|
+
|
|
27
|
+
使用方法:
|
|
28
|
+
npx @vmoo/mcp-database-server@latest --env=dev # 开发环境
|
|
29
|
+
npx @vmoo/mcp-database-server@latest --env=prod # 生产环境
|
|
30
|
+
|
|
31
|
+
或者直接使用:
|
|
32
|
+
npx vmoo-mcp-dev # 开发环境
|
|
33
|
+
npx vmoo-mcp-prod # 生产环境
|
|
34
|
+
|
|
35
|
+
配置文件:
|
|
36
|
+
--config=path/to/config.json # 自定义配置文件路径
|
|
37
|
+
`);
|
|
38
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vmoo-mcp-database-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "VMOO数据库MCP服务器集合 - 支持开发和生产环境的安全数据库访问",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"vmoo-mcp-dev": "./bin/vmoo-mcp-dev.js",
|
|
9
|
+
"vmoo-mcp-prod": "./bin/vmoo-mcp-prod.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"start:prod": "cd vmoo-database-prod && npm start",
|
|
13
|
+
"start:dev": "cd vmoo-database-dev && npm start",
|
|
14
|
+
"install:all": "npm install && cd vmoo-database-prod && npm install && cd ../vmoo-database-dev && npm install",
|
|
15
|
+
"test:all": "cd vmoo-database-prod && npm test && cd ../vmoo-database-dev && npm test",
|
|
16
|
+
"build": "echo '构建完成'",
|
|
17
|
+
"deploy": "./scripts/deploy.sh"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@modelcontextprotocol/sdk": "^1.17.2",
|
|
21
|
+
"mysql2": "^3.6.5"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^20.0.0"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"mcp",
|
|
28
|
+
"database",
|
|
29
|
+
"mysql",
|
|
30
|
+
"vmoo",
|
|
31
|
+
"model-context-protocol",
|
|
32
|
+
"production",
|
|
33
|
+
"development",
|
|
34
|
+
"privacy",
|
|
35
|
+
"security"
|
|
36
|
+
],
|
|
37
|
+
"author": "VMOO Team",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18.0.0"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"bin/",
|
|
44
|
+
"shared/",
|
|
45
|
+
"vmoo-database-dev/",
|
|
46
|
+
"vmoo-database-prod/",
|
|
47
|
+
"index.js",
|
|
48
|
+
"README.md"
|
|
49
|
+
],
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "git+https://github.com/jinzong8/vmoo-mcp-server.git"
|
|
53
|
+
},
|
|
54
|
+
"bugs": {
|
|
55
|
+
"url": "https://github.com/jinzong8/vmoo-mcp-server/issues"
|
|
56
|
+
},
|
|
57
|
+
"homepage": "https://github.com/jinzong8/vmoo-mcp-server#readme"
|
|
58
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// VMOO数据库工具函数
|
|
2
|
+
import mysql from 'mysql2/promise';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 创建数据库连接池
|
|
6
|
+
* @param {Object} config 数据库配置
|
|
7
|
+
* @returns {Object} 连接池
|
|
8
|
+
*/
|
|
9
|
+
function createConnectionPool(config) {
|
|
10
|
+
return mysql.createPool({
|
|
11
|
+
...config,
|
|
12
|
+
waitForConnections: true,
|
|
13
|
+
connectionLimit: 10,
|
|
14
|
+
queueLimit: 0
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 标准化表名(添加fanwe_前缀)
|
|
20
|
+
* @param {string} tableName 表名
|
|
21
|
+
* @returns {string} 标准化后的表名
|
|
22
|
+
*/
|
|
23
|
+
function normalizeTableName(tableName) {
|
|
24
|
+
if (!tableName.startsWith('fanwe_')) {
|
|
25
|
+
return `fanwe_${tableName}`;
|
|
26
|
+
}
|
|
27
|
+
return tableName;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 执行查询并处理错误
|
|
32
|
+
* @param {Object} pool 连接池
|
|
33
|
+
* @param {string} query 查询语句
|
|
34
|
+
* @returns {Array} 查询结果
|
|
35
|
+
*/
|
|
36
|
+
async function executeQuery(pool, query) {
|
|
37
|
+
try {
|
|
38
|
+
const [rows] = await pool.execute(query);
|
|
39
|
+
return rows;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
throw new Error(`数据库查询失败: ${error.message}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 获取表结构
|
|
47
|
+
* @param {Object} pool 连接池
|
|
48
|
+
* @param {string} tableName 表名
|
|
49
|
+
* @returns {Array} 表结构
|
|
50
|
+
*/
|
|
51
|
+
async function getTableStructure(pool, tableName) {
|
|
52
|
+
const normalizedName = normalizeTableName(tableName);
|
|
53
|
+
return await executeQuery(pool, `DESCRIBE ${normalizedName}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 获取表记录数
|
|
58
|
+
* @param {Object} pool 连接池
|
|
59
|
+
* @param {string} tableName 表名
|
|
60
|
+
* @returns {number} 记录数
|
|
61
|
+
*/
|
|
62
|
+
async function getTableCount(pool, tableName) {
|
|
63
|
+
const normalizedName = normalizeTableName(tableName);
|
|
64
|
+
const rows = await executeQuery(pool, `SELECT COUNT(*) as count FROM ${normalizedName}`);
|
|
65
|
+
return rows[0].count;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 获取表示例数据
|
|
70
|
+
* @param {Object} pool 连接池
|
|
71
|
+
* @param {string} tableName 表名
|
|
72
|
+
* @param {number} limit 限制数量
|
|
73
|
+
* @returns {Array} 示例数据
|
|
74
|
+
*/
|
|
75
|
+
async function getTableSampleData(pool, tableName, limit = 10) {
|
|
76
|
+
const normalizedName = normalizeTableName(tableName);
|
|
77
|
+
return await executeQuery(pool, `SELECT * FROM ${normalizedName} LIMIT ${limit}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 列出所有表
|
|
82
|
+
* @param {Object} pool 连接池
|
|
83
|
+
* @param {string} pattern 匹配模式
|
|
84
|
+
* @returns {Array} 表列表
|
|
85
|
+
*/
|
|
86
|
+
async function listTables(pool, pattern = null) {
|
|
87
|
+
let query = "SHOW TABLES";
|
|
88
|
+
if (pattern) {
|
|
89
|
+
query += ` LIKE '%${pattern}%'`;
|
|
90
|
+
}
|
|
91
|
+
return await executeQuery(pool, query);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export {
|
|
95
|
+
createConnectionPool,
|
|
96
|
+
normalizeTableName,
|
|
97
|
+
executeQuery,
|
|
98
|
+
getTableStructure,
|
|
99
|
+
getTableCount,
|
|
100
|
+
getTableSampleData,
|
|
101
|
+
listTables
|
|
102
|
+
};
|
|
@@ -0,0 +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
|
+
};
|