warehouse-mock 1.0.0 → 1.1.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 +103 -135
- package/dist/index.d.ts +28 -2
- package/dist/index.js +396 -48
- package/package.json +8 -6
package/README.md
CHANGED
|
@@ -1,90 +1,39 @@
|
|
|
1
|
-
# warehouse-mock
|
|
1
|
+
# warehouse-mock-plugin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<div align="center">
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
[](https://github.com/CodeMomentYY/warehouse-mock/blob/main/LICENSE)
|
|
5
|
+
极简 Vue Mock 插件,零业务代码侵入,完美支持 RPC 风格接口
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
[](https://www.npmjs.com/package/warehouse-mock-plugin)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
- ✅ **零业务代码侵入**: 只需修改配置文件,无需改动业务逻辑
|
|
12
|
-
- ✅ **实时更新**: 修改 Mock 数据后刷新页面即可,无需重启服务
|
|
13
|
-
- ✅ **按需 Mock**: 只拦截配置了 Mock 数据的接口,其他接口不受影响
|
|
14
|
-
- ✅ **TypeScript 编写**: 类型安全,易于维护
|
|
15
|
-
- ✅ **兼容性强**: 支持 Webpack 4/5,Vue CLI 3/4/5
|
|
10
|
+
</div>
|
|
16
11
|
|
|
17
|
-
##
|
|
12
|
+
## 安装
|
|
18
13
|
|
|
19
14
|
```bash
|
|
20
|
-
npm install warehouse-mock --save-dev
|
|
15
|
+
npm install warehouse-mock-plugin --save-dev
|
|
21
16
|
```
|
|
22
17
|
|
|
23
|
-
##
|
|
18
|
+
## 快速开始
|
|
24
19
|
|
|
25
20
|
### 1. 配置 vue.config.js
|
|
26
21
|
|
|
27
22
|
```javascript
|
|
28
|
-
const WarehouseMockPlugin = require('warehouse-mock');
|
|
29
|
-
const webpack = require('webpack');
|
|
30
|
-
const path = require('path');
|
|
23
|
+
const WarehouseMockPlugin = require('warehouse-mock-plugin');
|
|
31
24
|
|
|
32
25
|
const isMock = process.env.MOCK === 'true';
|
|
33
|
-
const mockPlugin = isMock ? new WarehouseMockPlugin({
|
|
34
|
-
mockPath: path.resolve(__dirname, 'warehouseMock'),
|
|
35
|
-
}) : null;
|
|
36
26
|
|
|
37
27
|
module.exports = {
|
|
38
28
|
configureWebpack: config => {
|
|
39
|
-
if (isMock
|
|
40
|
-
config.plugins.push(
|
|
41
|
-
new webpack.DefinePlugin({
|
|
42
|
-
'process.env.VUE_APP_MOCK': JSON.stringify('true'),
|
|
43
|
-
})
|
|
44
|
-
);
|
|
45
|
-
config.plugins.push(mockPlugin);
|
|
29
|
+
if (isMock) {
|
|
30
|
+
config.plugins.push(new WarehouseMockPlugin());
|
|
46
31
|
}
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
devServer: {
|
|
50
|
-
// Vue CLI 5.x (Webpack 5)
|
|
51
|
-
setupMiddlewares: (middlewares, devServer) => {
|
|
52
|
-
if (isMock && mockPlugin) {
|
|
53
|
-
return mockPlugin.setupMiddlewares(middlewares, devServer);
|
|
54
|
-
}
|
|
55
|
-
return middlewares;
|
|
56
|
-
},
|
|
57
|
-
|
|
58
|
-
// Vue CLI 3.x/4.x (Webpack 4) 使用这个:
|
|
59
|
-
// before: isMock && mockPlugin ? (app) => mockPlugin.runBefore(app) : undefined,
|
|
60
32
|
}
|
|
61
33
|
};
|
|
62
34
|
```
|
|
63
35
|
|
|
64
|
-
### 2.
|
|
65
|
-
|
|
66
|
-
在 `src/const/config.js` 中添加 Mock 模式判断:
|
|
67
|
-
|
|
68
|
-
```javascript
|
|
69
|
-
const config = { env: process.env.VUE_APP_ENV || 'dev' };
|
|
70
|
-
|
|
71
|
-
// Mock 模式优先判断
|
|
72
|
-
if (process.env.VUE_APP_MOCK === 'true') {
|
|
73
|
-
Object.assign(config, {
|
|
74
|
-
API: '/mock-api',
|
|
75
|
-
});
|
|
76
|
-
} else if (config.env === 'dev') {
|
|
77
|
-
Object.assign(config, {
|
|
78
|
-
API: 'https://fat-api.hellobike.com/api',
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export default config;
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### 3. 添加 Mock 脚本
|
|
86
|
-
|
|
87
|
-
在 `package.json` 中:
|
|
36
|
+
### 2. 添加 Mock 脚本
|
|
88
37
|
|
|
89
38
|
```json
|
|
90
39
|
{
|
|
@@ -94,114 +43,133 @@ export default config;
|
|
|
94
43
|
}
|
|
95
44
|
```
|
|
96
45
|
|
|
97
|
-
###
|
|
46
|
+
### 3. 配置 API 切换
|
|
98
47
|
|
|
99
|
-
|
|
48
|
+
在 `src/const/config.js` 中:
|
|
100
49
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
"data": {
|
|
107
|
-
"userId": 12345,
|
|
108
|
-
"userName": "Mock 测试用户"
|
|
109
|
-
}
|
|
50
|
+
```javascript
|
|
51
|
+
if (process.env.VUE_APP_MOCK === 'true') {
|
|
52
|
+
config.API = '/mock-api';
|
|
53
|
+
} else {
|
|
54
|
+
config.API = 'https://your-api.com';
|
|
110
55
|
}
|
|
111
56
|
```
|
|
112
57
|
|
|
58
|
+
### 4. 创建 Mock 数据
|
|
59
|
+
|
|
60
|
+
在项目根目录创建 `warehouseMock/` 文件夹,添加 JSON 文件:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
warehouseMock/
|
|
64
|
+
├── user.account.getInfo.json
|
|
65
|
+
└── user.taurus.pointInfo.json
|
|
66
|
+
```
|
|
67
|
+
|
|
113
68
|
### 5. 启动
|
|
114
69
|
|
|
115
70
|
```bash
|
|
116
71
|
npm run mock
|
|
117
72
|
```
|
|
118
73
|
|
|
119
|
-
##
|
|
74
|
+
## 配置选项
|
|
120
75
|
|
|
121
76
|
```typescript
|
|
122
|
-
|
|
123
|
-
//
|
|
124
|
-
mockPath
|
|
77
|
+
interface MockPluginOptions {
|
|
78
|
+
// Mock 数据目录,默认 'warehouseMock'
|
|
79
|
+
mockPath?: string;
|
|
125
80
|
|
|
126
|
-
//
|
|
81
|
+
// 需要拦截的 API 路径前缀,默认 ['/api', '/mock-api']
|
|
127
82
|
apiPrefixes?: string[];
|
|
128
83
|
|
|
129
|
-
//
|
|
84
|
+
// 本地代理路径前缀,默认 '/mock-api'
|
|
130
85
|
localApiPrefix?: string;
|
|
131
|
-
|
|
86
|
+
|
|
87
|
+
// 是否启用,默认 true
|
|
88
|
+
enabled?: boolean;
|
|
89
|
+
|
|
90
|
+
// 是否自动注入环境变量 VUE_APP_MOCK,默认 true
|
|
91
|
+
injectEnv?: boolean;
|
|
92
|
+
|
|
93
|
+
// 响应延迟(毫秒),模拟网络延迟,默认 0
|
|
94
|
+
delay?: number;
|
|
95
|
+
|
|
96
|
+
// 代理配置:未匹配的请求转发到真实 API
|
|
97
|
+
proxy?: {
|
|
98
|
+
target: string;
|
|
99
|
+
changeOrigin?: boolean;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
132
102
|
```
|
|
133
103
|
|
|
134
|
-
##
|
|
135
|
-
|
|
136
|
-
### RPC 风格(推荐)
|
|
137
|
-
|
|
138
|
-
| 请求 URL | Mock 文件名 |
|
|
139
|
-
|---------|-----------|
|
|
140
|
-
| `/api?user.account.getInfo` | `user.account.getInfo.json` |
|
|
141
|
-
| `/api?user.taurus.pointInfo` | `user.taurus.pointInfo.json` |
|
|
142
|
-
| `/api?common.welfare.banner.query` | `common.welfare.banner.query.json` |
|
|
143
|
-
|
|
144
|
-
### RESTful 风格
|
|
104
|
+
## 使用示例
|
|
145
105
|
|
|
146
|
-
|
|
147
|
-
|---------|---------------------|
|
|
148
|
-
| `/api/user/info` | `api_user_info.json` (扁平化) |
|
|
149
|
-
| `/api/user/info` | `api/user/info.json` (嵌套) |
|
|
106
|
+
### 基础配置
|
|
150
107
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
### 直接访问 Mock 数据
|
|
158
|
-
|
|
159
|
-
访问: `http://localhost:8080/mock-api?user.account.getInfo`
|
|
108
|
+
```javascript
|
|
109
|
+
new WarehouseMockPlugin({
|
|
110
|
+
mockPath: 'warehouseMock',
|
|
111
|
+
apiPrefixes: ['/api', '/mock-api']
|
|
112
|
+
})
|
|
113
|
+
```
|
|
160
114
|
|
|
161
|
-
|
|
115
|
+
### 代理模式
|
|
162
116
|
|
|
163
|
-
|
|
117
|
+
未匹配的请求转发到真实 API:
|
|
164
118
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
-
|
|
119
|
+
```javascript
|
|
120
|
+
new WarehouseMockPlugin({
|
|
121
|
+
proxy: {
|
|
122
|
+
target: 'https://fat-api.hellobike.com',
|
|
123
|
+
changeOrigin: true
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
```
|
|
169
127
|
|
|
170
|
-
###
|
|
128
|
+
### 模拟网络延迟
|
|
171
129
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
| TypeScript | ✅ | ❌ | ❌ |
|
|
178
|
-
| Webpack 集成 | ✅ | ❌ | ❌ |
|
|
130
|
+
```javascript
|
|
131
|
+
new WarehouseMockPlugin({
|
|
132
|
+
delay: 500 // 500ms 延迟
|
|
133
|
+
})
|
|
134
|
+
```
|
|
179
135
|
|
|
180
|
-
##
|
|
136
|
+
## Mock 文件命名规则
|
|
181
137
|
|
|
182
|
-
|
|
138
|
+
### RPC 风格 (推荐)
|
|
183
139
|
|
|
184
|
-
|
|
140
|
+
| 请求 URL | Mock 文件名 |
|
|
141
|
+
|---------|-----------|
|
|
142
|
+
| `GET /api?user.account.getInfo` | `user.account.getInfo.json` |
|
|
143
|
+
| `POST /mock-api?user.taurus.pointInfo` | `user.taurus.pointInfo.json` |
|
|
185
144
|
|
|
186
|
-
|
|
145
|
+
### RESTful 风格
|
|
187
146
|
|
|
188
|
-
|
|
147
|
+
| 请求 URL | Mock 文件名 |
|
|
148
|
+
|---------|-----------|
|
|
149
|
+
| `GET /api/user/info` | `api_user_info.json` (扁平化) |
|
|
150
|
+
| `GET /api/user/info` | `api/user/info.json` (嵌套) |
|
|
189
151
|
|
|
190
|
-
|
|
152
|
+
## 调试工具
|
|
191
153
|
|
|
192
|
-
|
|
154
|
+
### 查看可用 Mock 列表
|
|
193
155
|
|
|
194
|
-
|
|
156
|
+
访问: `http://localhost:8080/__mock_list__`
|
|
195
157
|
|
|
196
|
-
|
|
158
|
+
### 直接访问 Mock 数据
|
|
197
159
|
|
|
198
|
-
|
|
160
|
+
访问: `http://localhost:8080/mock-api?user.account.getInfo`
|
|
199
161
|
|
|
200
|
-
|
|
162
|
+
## 核心特性
|
|
201
163
|
|
|
202
|
-
|
|
164
|
+
- ✅ **极简配置** - vue.config.js 只需 3 行代码
|
|
165
|
+
- ✅ **零业务侵入** - 无需修改接口调用代码
|
|
166
|
+
- ✅ **RPC 风格支持** - 原生支持 RPC 接口
|
|
167
|
+
- ✅ **实时热更新** - 修改 Mock 数据,刷新即可
|
|
168
|
+
- ✅ **按需拦截** - 只拦截配置了 Mock 文件的接口
|
|
169
|
+
- ✅ **代理模式** - 未匹配请求转发到真实 API
|
|
170
|
+
- ✅ **自动注入环境变量** - 无需手动配置 DefinePlugin
|
|
171
|
+
- ✅ **兼容性强** - 支持 Webpack 4/5,Vue CLI 3/4/5
|
|
203
172
|
|
|
204
|
-
|
|
205
|
-
- [问题反馈](https://github.com/CodeMomentYY/warehouse-mock/issues)
|
|
206
|
-
- [更新日志](https://github.com/CodeMomentYY/warehouse-mock/releases)
|
|
173
|
+
## License
|
|
207
174
|
|
|
175
|
+
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
import { Compiler } from 'webpack';
|
|
2
2
|
interface MockPluginOptions {
|
|
3
|
-
mockPath
|
|
3
|
+
mockPath?: string;
|
|
4
4
|
apiPrefixes?: string[];
|
|
5
5
|
localApiPrefix?: string;
|
|
6
|
+
enabled?: boolean;
|
|
7
|
+
proxy?: {
|
|
8
|
+
target: string;
|
|
9
|
+
changeOrigin?: boolean;
|
|
10
|
+
};
|
|
11
|
+
injectEnv?: boolean;
|
|
12
|
+
delay?: number;
|
|
13
|
+
admin?: {
|
|
14
|
+
enabled?: boolean;
|
|
15
|
+
port?: number;
|
|
16
|
+
};
|
|
6
17
|
}
|
|
7
18
|
declare class WarehouseMockPlugin {
|
|
8
19
|
private options;
|
|
9
20
|
private resolvedMockPath;
|
|
10
|
-
|
|
21
|
+
private isEnabled;
|
|
22
|
+
private adminServer;
|
|
23
|
+
constructor(options?: MockPluginOptions);
|
|
11
24
|
/**
|
|
12
25
|
* 实时扫描 mock 目录,获取所有 mock 文件名列表
|
|
13
26
|
*/
|
|
@@ -17,6 +30,14 @@ declare class WarehouseMockPlugin {
|
|
|
17
30
|
*/
|
|
18
31
|
getLocalApiPrefix(): string;
|
|
19
32
|
apply(compiler: Compiler): void;
|
|
33
|
+
/**
|
|
34
|
+
* 启动管理后台服务
|
|
35
|
+
*/
|
|
36
|
+
private startAdminServer;
|
|
37
|
+
/**
|
|
38
|
+
* 自动注入环境变量 VUE_APP_MOCK
|
|
39
|
+
*/
|
|
40
|
+
private injectEnvironmentVariable;
|
|
20
41
|
/**
|
|
21
42
|
* 公共方法:设置中间件 (Webpack 5 / Vue CLI 5+)
|
|
22
43
|
*/
|
|
@@ -28,6 +49,11 @@ declare class WarehouseMockPlugin {
|
|
|
28
49
|
private getResolvedMockPath;
|
|
29
50
|
private ensureMockDirectory;
|
|
30
51
|
private createDemoFile;
|
|
52
|
+
private readInterfaceConfig;
|
|
31
53
|
private handleRequest;
|
|
54
|
+
/**
|
|
55
|
+
* 代理请求到真实 API(改进版)
|
|
56
|
+
*/
|
|
57
|
+
private proxyRequest;
|
|
32
58
|
}
|
|
33
59
|
export = WarehouseMockPlugin;
|
package/dist/index.js
CHANGED
|
@@ -6,9 +6,32 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
6
6
|
const path_1 = __importDefault(require("path"));
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
8
|
class WarehouseMockPlugin {
|
|
9
|
-
constructor(options) {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
var _a, _b;
|
|
10
11
|
this.resolvedMockPath = '';
|
|
11
|
-
this.
|
|
12
|
+
this.isEnabled = true;
|
|
13
|
+
this.adminServer = null;
|
|
14
|
+
// 默认配置
|
|
15
|
+
this.options = {
|
|
16
|
+
mockPath: options.mockPath || 'warehouseMock',
|
|
17
|
+
apiPrefixes: options.apiPrefixes || ['/api', '/mock-api'],
|
|
18
|
+
localApiPrefix: options.localApiPrefix || '/mock-api',
|
|
19
|
+
enabled: options.enabled !== undefined ? options.enabled : true,
|
|
20
|
+
proxy: options.proxy || undefined,
|
|
21
|
+
injectEnv: options.injectEnv !== undefined ? options.injectEnv : true,
|
|
22
|
+
delay: options.delay || 0,
|
|
23
|
+
admin: {
|
|
24
|
+
enabled: ((_a = options.admin) === null || _a === void 0 ? void 0 : _a.enabled) !== undefined ? options.admin.enabled : true,
|
|
25
|
+
port: ((_b = options.admin) === null || _b === void 0 ? void 0 : _b.port) || 3100,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
// 判断是否启用(支持环境变量控制)
|
|
29
|
+
if (process.env.MOCK === 'false' || process.env.VUE_APP_MOCK === 'false') {
|
|
30
|
+
this.isEnabled = false;
|
|
31
|
+
}
|
|
32
|
+
if (this.options.enabled === false) {
|
|
33
|
+
this.isEnabled = false;
|
|
34
|
+
}
|
|
12
35
|
}
|
|
13
36
|
/**
|
|
14
37
|
* 实时扫描 mock 目录,获取所有 mock 文件名列表
|
|
@@ -33,7 +56,16 @@ class WarehouseMockPlugin {
|
|
|
33
56
|
return this.options.localApiPrefix || '/mock-api';
|
|
34
57
|
}
|
|
35
58
|
apply(compiler) {
|
|
59
|
+
// 如果未启用,直接返回
|
|
60
|
+
if (!this.isEnabled) {
|
|
61
|
+
console.log(chalk_1.default.yellow('[WarehouseMock] Mock 模式未启用'));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
36
64
|
this.resolvedMockPath = path_1.default.resolve(compiler.context, this.options.mockPath);
|
|
65
|
+
// 自动注入环境变量 VUE_APP_MOCK
|
|
66
|
+
if (this.options.injectEnv) {
|
|
67
|
+
this.injectEnvironmentVariable(compiler);
|
|
68
|
+
}
|
|
37
69
|
// 确保 mock 目录存在
|
|
38
70
|
if (!fs_1.default.existsSync(this.resolvedMockPath)) {
|
|
39
71
|
try {
|
|
@@ -46,21 +78,96 @@ class WarehouseMockPlugin {
|
|
|
46
78
|
}
|
|
47
79
|
}
|
|
48
80
|
const mockFileList = this.getMockFileList();
|
|
49
|
-
console.log(chalk_1.default.cyan(`[WarehouseMock] 已加载 ${mockFileList.length} 个 mock
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
81
|
+
console.log(chalk_1.default.cyan(`[WarehouseMock] 已加载 ${mockFileList.length} 个 mock 文件`));
|
|
82
|
+
if (mockFileList.length > 0) {
|
|
83
|
+
console.log(chalk_1.default.gray(` → ${mockFileList.join(', ')}`));
|
|
84
|
+
}
|
|
85
|
+
if (this.options.proxy) {
|
|
86
|
+
console.log(chalk_1.default.cyan(`[WarehouseMock] 代理模式: 未匹配请求 → ${this.options.proxy.target}`));
|
|
87
|
+
}
|
|
88
|
+
// 启动管理后台
|
|
89
|
+
if (this.options.admin && this.options.admin.enabled) {
|
|
90
|
+
this.startAdminServer();
|
|
91
|
+
}
|
|
92
|
+
// 注意:devServer 的配置需要在 vue.config.js 中手动配置
|
|
93
|
+
// 对于 Webpack 5 使用 setupMiddlewares
|
|
94
|
+
// 对于 Webpack 4 使用 before
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 启动管理后台服务
|
|
98
|
+
*/
|
|
99
|
+
startAdminServer() {
|
|
100
|
+
var _a;
|
|
101
|
+
try {
|
|
102
|
+
// 动态导入管理后台服务
|
|
103
|
+
let createAdminServer;
|
|
104
|
+
try {
|
|
105
|
+
// 尝试导入已安装的包
|
|
106
|
+
createAdminServer = require('warehouse-mock-admin').createAdminServer;
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
// 如果找不到,尝试相对路径(开发环境)
|
|
110
|
+
const adminPath = require('path').resolve(__dirname, '../../mock-admin/dist/server/index.js');
|
|
111
|
+
createAdminServer = require(adminPath).createAdminServer;
|
|
112
|
+
}
|
|
113
|
+
if (!createAdminServer) {
|
|
114
|
+
throw new Error('无法加载管理后台模块');
|
|
115
|
+
}
|
|
116
|
+
const adminResult = createAdminServer({
|
|
117
|
+
mockPath: this.resolvedMockPath,
|
|
118
|
+
port: ((_a = this.options.admin) === null || _a === void 0 ? void 0 : _a.port) || 3100,
|
|
119
|
+
});
|
|
120
|
+
this.adminServer = adminResult;
|
|
121
|
+
console.log(chalk_1.default.green(`\n╭────────────────────────────────────────────────────╮`));
|
|
122
|
+
console.log(chalk_1.default.green(`│ 🎨 Mock 数据管理后台已启动 │`));
|
|
123
|
+
console.log(chalk_1.default.green(`│ │`));
|
|
124
|
+
console.log(chalk_1.default.green(`│ ➜ 访问地址: ${chalk_1.default.cyan(adminResult.url).padEnd(31)} │`));
|
|
125
|
+
console.log(chalk_1.default.green(`│ │`));
|
|
126
|
+
console.log(chalk_1.default.green(`│ 在浏览器中打开上面的地址来管理 Mock 数据 │`));
|
|
127
|
+
console.log(chalk_1.default.green(`╰────────────────────────────────────────────────────╯\n`));
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
console.log(chalk_1.default.yellow('[WarehouseMock] 管理后台包未安装或加载失败,跳过启动'));
|
|
131
|
+
console.log(chalk_1.default.gray(' 提示:运行 npm install warehouse-mock-admin 来安装管理后台'));
|
|
132
|
+
if (err.message && !err.message.includes('Cannot find module')) {
|
|
133
|
+
console.log(chalk_1.default.gray(` 错误详情: ${err.message}`));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* 自动注入环境变量 VUE_APP_MOCK
|
|
139
|
+
*/
|
|
140
|
+
injectEnvironmentVariable(compiler) {
|
|
141
|
+
try {
|
|
142
|
+
// 使用 compiler 的 webpack 实例
|
|
143
|
+
const { webpack } = compiler;
|
|
144
|
+
if (webpack && webpack.DefinePlugin) {
|
|
145
|
+
const definePlugin = new webpack.DefinePlugin({
|
|
146
|
+
'process.env.VUE_APP_MOCK': JSON.stringify('true'),
|
|
147
|
+
});
|
|
148
|
+
if (!compiler.options.plugins) {
|
|
149
|
+
compiler.options.plugins = [];
|
|
150
|
+
}
|
|
151
|
+
compiler.options.plugins.push(definePlugin);
|
|
152
|
+
console.log(chalk_1.default.gray('[WarehouseMock] 已注入环境变量: VUE_APP_MOCK=true'));
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
// 备用方案:直接require webpack
|
|
156
|
+
const webpackModule = require('webpack');
|
|
157
|
+
const definePlugin = new webpackModule.DefinePlugin({
|
|
158
|
+
'process.env.VUE_APP_MOCK': JSON.stringify('true'),
|
|
159
|
+
});
|
|
160
|
+
if (!compiler.options.plugins) {
|
|
161
|
+
compiler.options.plugins = [];
|
|
162
|
+
}
|
|
163
|
+
compiler.options.plugins.push(definePlugin);
|
|
164
|
+
console.log(chalk_1.default.gray('[WarehouseMock] 已注入环境变量: VUE_APP_MOCK=true'));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
console.error(chalk_1.default.red(`[WarehouseMock] 注入环境变量失败: ${err}`));
|
|
169
|
+
console.log(chalk_1.default.yellow('[WarehouseMock] 请在 vue.config.js 中手动配置 DefinePlugin'));
|
|
170
|
+
}
|
|
64
171
|
}
|
|
65
172
|
/**
|
|
66
173
|
* 公共方法:设置中间件 (Webpack 5 / Vue CLI 5+)
|
|
@@ -130,8 +237,22 @@ class WarehouseMockPlugin {
|
|
|
130
237
|
console.error(chalk_1.default.yellow(`[WarehouseMock] 创建示例文件失败: ${e}`));
|
|
131
238
|
}
|
|
132
239
|
}
|
|
240
|
+
// 读取接口配置
|
|
241
|
+
readInterfaceConfig(apiName, mockPath) {
|
|
242
|
+
const configPath = path_1.default.join(mockPath, apiName, '.config.json');
|
|
243
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
244
|
+
try {
|
|
245
|
+
const content = fs_1.default.readFileSync(configPath, 'utf-8');
|
|
246
|
+
return JSON.parse(content);
|
|
247
|
+
}
|
|
248
|
+
catch (e) {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
133
254
|
handleRequest(req, res, next, mockPath) {
|
|
134
|
-
var _a, _b;
|
|
255
|
+
var _a, _b, _c;
|
|
135
256
|
const url = req.path || ((_a = req.url) === null || _a === void 0 ? void 0 : _a.split('?')[0]);
|
|
136
257
|
if (!url) {
|
|
137
258
|
return next();
|
|
@@ -144,6 +265,8 @@ class WarehouseMockPlugin {
|
|
|
144
265
|
res.end(JSON.stringify({
|
|
145
266
|
mockList: fileList,
|
|
146
267
|
localApiPrefix: this.getLocalApiPrefix(),
|
|
268
|
+
proxy: ((_b = this.options.proxy) === null || _b === void 0 ? void 0 : _b.target) || null,
|
|
269
|
+
enabled: this.isEnabled,
|
|
147
270
|
}));
|
|
148
271
|
return;
|
|
149
272
|
}
|
|
@@ -153,38 +276,132 @@ class WarehouseMockPlugin {
|
|
|
153
276
|
}
|
|
154
277
|
// 检查是否在允许的 API 前缀范围内
|
|
155
278
|
const { apiPrefixes } = this.options;
|
|
156
|
-
const isApiRequest = (
|
|
279
|
+
const isApiRequest = (_c = apiPrefixes === null || apiPrefixes === void 0 ? void 0 : apiPrefixes.some((prefix) => url.startsWith(prefix))) !== null && _c !== void 0 ? _c : true;
|
|
157
280
|
if (!isApiRequest) {
|
|
158
281
|
return next();
|
|
159
282
|
}
|
|
160
283
|
let filePath = '';
|
|
161
284
|
let matched = false;
|
|
162
285
|
let matchedName = '';
|
|
286
|
+
let interfaceConfig = null;
|
|
163
287
|
// 1. 优先尝试 Query String 匹配 (RPC 风格接口,如 /api?user.taurus.pointInfo)
|
|
164
288
|
if (req.url && req.url.includes('?')) {
|
|
165
289
|
const queryPart = req.url.split('?')[1];
|
|
166
290
|
if (queryPart) {
|
|
167
291
|
const params = new URLSearchParams(queryPart);
|
|
168
292
|
for (const key of params.keys()) {
|
|
169
|
-
//
|
|
170
|
-
const
|
|
171
|
-
if (fs_1.default.existsSync(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
293
|
+
// 先尝试目录模式(优先,支持多场景)
|
|
294
|
+
const dirPath = path_1.default.join(mockPath, key);
|
|
295
|
+
if (fs_1.default.existsSync(dirPath) && fs_1.default.statSync(dirPath).isDirectory()) {
|
|
296
|
+
// 读取接口配置(新格式)
|
|
297
|
+
const config = this.readInterfaceConfig(key, mockPath);
|
|
298
|
+
if (config) {
|
|
299
|
+
// 新格式:检查是否启用
|
|
300
|
+
if (config.enabled === false) {
|
|
301
|
+
console.log(chalk_1.default.yellow(`[WarehouseMock] ✗ 接口已禁用: ${key}`));
|
|
302
|
+
break; // 接口被禁用,不拦截
|
|
303
|
+
}
|
|
304
|
+
// 根据 activeScene 读取场景文件
|
|
305
|
+
const activeScene = config.activeScene || 'default';
|
|
306
|
+
const sceneFilePath = path_1.default.join(dirPath, `${activeScene}.json`);
|
|
307
|
+
if (fs_1.default.existsSync(sceneFilePath)) {
|
|
308
|
+
filePath = sceneFilePath;
|
|
309
|
+
matchedName = key;
|
|
310
|
+
matched = true;
|
|
311
|
+
interfaceConfig = config;
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
// 旧格式:查找 mock: true 的文件(向后兼容)
|
|
317
|
+
const files = fs_1.default.readdirSync(dirPath).filter(f => f.endsWith('.json') && f !== '.config.json');
|
|
318
|
+
for (const file of files) {
|
|
319
|
+
const fullPath = path_1.default.join(dirPath, file);
|
|
320
|
+
try {
|
|
321
|
+
const content = fs_1.default.readFileSync(fullPath, 'utf-8');
|
|
322
|
+
const jsonData = JSON.parse(content);
|
|
323
|
+
if (jsonData.mock === true) {
|
|
324
|
+
filePath = fullPath;
|
|
325
|
+
matchedName = key;
|
|
326
|
+
matched = true;
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
catch (e) {
|
|
331
|
+
// 文件解析失败,跳过
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (matched)
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
176
337
|
}
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
matchedName = value;
|
|
338
|
+
// 再尝试单文件模式(向后兼容)
|
|
339
|
+
if (!matched) {
|
|
340
|
+
const queryFilePath = path_1.default.join(mockPath, `${key}.json`);
|
|
341
|
+
if (fs_1.default.existsSync(queryFilePath) && fs_1.default.statSync(queryFilePath).isFile()) {
|
|
342
|
+
filePath = queryFilePath;
|
|
343
|
+
matchedName = key;
|
|
184
344
|
matched = true;
|
|
185
345
|
break;
|
|
186
346
|
}
|
|
187
347
|
}
|
|
348
|
+
// 也尝试匹配 value (例如 method=user.taurus.pointInfo)
|
|
349
|
+
const value = params.get(key);
|
|
350
|
+
if (value && !matched) {
|
|
351
|
+
const valueDirPath = path_1.default.join(mockPath, value);
|
|
352
|
+
if (fs_1.default.existsSync(valueDirPath) && fs_1.default.statSync(valueDirPath).isDirectory()) {
|
|
353
|
+
// 读取接口配置(新格式)
|
|
354
|
+
const config = this.readInterfaceConfig(value, mockPath);
|
|
355
|
+
if (config) {
|
|
356
|
+
// 新格式:检查是否启用
|
|
357
|
+
if (config.enabled === false) {
|
|
358
|
+
console.log(chalk_1.default.yellow(`[WarehouseMock] ✗ 接口已禁用: ${value}`));
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
// 根据 activeScene 读取场景文件
|
|
362
|
+
const activeScene = config.activeScene || 'default';
|
|
363
|
+
const sceneFilePath = path_1.default.join(valueDirPath, `${activeScene}.json`);
|
|
364
|
+
if (fs_1.default.existsSync(sceneFilePath)) {
|
|
365
|
+
filePath = sceneFilePath;
|
|
366
|
+
matchedName = value;
|
|
367
|
+
matched = true;
|
|
368
|
+
interfaceConfig = config;
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
// 旧格式:查找 mock: true 的文件(向后兼容)
|
|
374
|
+
const files = fs_1.default.readdirSync(valueDirPath).filter(f => f.endsWith('.json') && f !== '.config.json');
|
|
375
|
+
for (const file of files) {
|
|
376
|
+
const fullPath = path_1.default.join(valueDirPath, file);
|
|
377
|
+
try {
|
|
378
|
+
const content = fs_1.default.readFileSync(fullPath, 'utf-8');
|
|
379
|
+
const jsonData = JSON.parse(content);
|
|
380
|
+
if (jsonData.mock === true) {
|
|
381
|
+
filePath = fullPath;
|
|
382
|
+
matchedName = value;
|
|
383
|
+
matched = true;
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
catch (e) {
|
|
388
|
+
// 跳过
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (matched)
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (!matched) {
|
|
396
|
+
const valueFilePath = path_1.default.join(mockPath, `${value}.json`);
|
|
397
|
+
if (fs_1.default.existsSync(valueFilePath) && fs_1.default.statSync(valueFilePath).isFile()) {
|
|
398
|
+
filePath = valueFilePath;
|
|
399
|
+
matchedName = value;
|
|
400
|
+
matched = true;
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
188
405
|
}
|
|
189
406
|
}
|
|
190
407
|
}
|
|
@@ -216,29 +433,160 @@ class WarehouseMockPlugin {
|
|
|
216
433
|
}
|
|
217
434
|
}
|
|
218
435
|
if (matched) {
|
|
219
|
-
console.log(chalk_1.default.green(`[WarehouseMock] 拦截: ${
|
|
220
|
-
|
|
221
|
-
|
|
436
|
+
console.log(chalk_1.default.green(`[WarehouseMock] ✓ 拦截: ${req.url || url}`));
|
|
437
|
+
// 获取延时配置(优先使用接口级别的 delay)
|
|
438
|
+
const delayTime = (interfaceConfig === null || interfaceConfig === void 0 ? void 0 : interfaceConfig.delay) !== undefined
|
|
439
|
+
? interfaceConfig.delay
|
|
440
|
+
: this.options.delay || 0;
|
|
441
|
+
// 获取场景名称
|
|
442
|
+
const sceneName = (interfaceConfig === null || interfaceConfig === void 0 ? void 0 : interfaceConfig.activeScene) || path_1.default.basename(filePath, '.json');
|
|
443
|
+
console.log(chalk_1.default.gray(` → 场景: ${sceneName}`));
|
|
444
|
+
if (delayTime > 0) {
|
|
445
|
+
console.log(chalk_1.default.gray(` → 延迟: ${delayTime}ms`));
|
|
446
|
+
}
|
|
447
|
+
// 模拟网络延迟
|
|
448
|
+
const respond = () => {
|
|
222
449
|
try {
|
|
223
|
-
const
|
|
450
|
+
const fileContent = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
451
|
+
try {
|
|
452
|
+
const jsonData = JSON.parse(fileContent);
|
|
453
|
+
// 新格式:纯净的场景数据(不包含 mock、scene、delay 等元数据)
|
|
454
|
+
// 旧格式:包含 mock、scene、delay、data 字段
|
|
455
|
+
let responseData = jsonData;
|
|
456
|
+
// 兼容旧格式
|
|
457
|
+
if (jsonData.mock !== undefined && jsonData.data !== undefined) {
|
|
458
|
+
responseData = jsonData.data;
|
|
459
|
+
}
|
|
460
|
+
res.setHeader('Content-Type', 'application/json');
|
|
461
|
+
res.setHeader('X-Mock-By', 'WarehouseMock');
|
|
462
|
+
res.setHeader('X-Mock-Scene', sceneName);
|
|
463
|
+
res.end(JSON.stringify(responseData));
|
|
464
|
+
}
|
|
465
|
+
catch (jsonErr) {
|
|
466
|
+
console.warn(chalk_1.default.yellow(`[WarehouseMock] 无效的 JSON 文件: ${filePath}`));
|
|
467
|
+
res.setHeader('Content-Type', 'text/plain');
|
|
468
|
+
res.end(fileContent);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
catch (e) {
|
|
472
|
+
console.error(chalk_1.default.red(`[WarehouseMock] 读取 Mock 文件失败: ${e}`));
|
|
473
|
+
res.statusCode = 500;
|
|
474
|
+
res.end('Mock Read Error');
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
// 应用延迟
|
|
478
|
+
if (delayTime > 0) {
|
|
479
|
+
setTimeout(respond, delayTime);
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
respond();
|
|
483
|
+
}
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
// 未匹配到 Mock 文件
|
|
487
|
+
if (this.options.proxy) {
|
|
488
|
+
// 如果配置了代理,转发到真实 API
|
|
489
|
+
console.log(chalk_1.default.gray(`[WarehouseMock] ⊳ 代理: ${req.url || url} → ${this.options.proxy.target}`));
|
|
490
|
+
this.proxyRequest(req, res, next);
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
// 未配置代理,直接放行
|
|
494
|
+
next();
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* 代理请求到真实 API(改进版)
|
|
499
|
+
*/
|
|
500
|
+
proxyRequest(req, res, next) {
|
|
501
|
+
if (!this.options.proxy) {
|
|
502
|
+
return next();
|
|
503
|
+
}
|
|
504
|
+
try {
|
|
505
|
+
const http = require('http');
|
|
506
|
+
const https = require('https');
|
|
507
|
+
const url = require('url');
|
|
508
|
+
// 构建完整的目标 URL
|
|
509
|
+
// 将 /mock-api?xxx 转换为 真实API/api?xxx
|
|
510
|
+
const targetUrl = this.options.proxy.target + req.url.replace('/mock-api', '/api');
|
|
511
|
+
const parsedUrl = url.parse(targetUrl);
|
|
512
|
+
const isHttps = parsedUrl.protocol === 'https:';
|
|
513
|
+
const lib = isHttps ? https : http;
|
|
514
|
+
// 清理请求头
|
|
515
|
+
const headers = Object.assign({}, req.headers);
|
|
516
|
+
delete headers.host;
|
|
517
|
+
headers.host = parsedUrl.hostname;
|
|
518
|
+
// 创建代理请求
|
|
519
|
+
const proxyReq = lib.request({
|
|
520
|
+
hostname: parsedUrl.hostname,
|
|
521
|
+
port: parsedUrl.port || (isHttps ? 443 : 80),
|
|
522
|
+
path: parsedUrl.path,
|
|
523
|
+
method: req.method,
|
|
524
|
+
headers: headers,
|
|
525
|
+
timeout: 30000, // 30秒超时
|
|
526
|
+
}, (proxyRes) => {
|
|
527
|
+
// 转发响应头
|
|
528
|
+
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
|
529
|
+
// 转发响应体
|
|
530
|
+
proxyRes.pipe(res);
|
|
531
|
+
});
|
|
532
|
+
// 错误处理
|
|
533
|
+
proxyReq.on('error', (err) => {
|
|
534
|
+
console.error(chalk_1.default.red(`[WarehouseMock] 代理请求失败: ${err.message}`));
|
|
535
|
+
console.error(chalk_1.default.red(` 目标地址: ${targetUrl}`));
|
|
536
|
+
// 返回友好的错误信息
|
|
537
|
+
if (!res.headersSent) {
|
|
538
|
+
res.statusCode = 502;
|
|
224
539
|
res.setHeader('Content-Type', 'application/json');
|
|
225
|
-
res.end(JSON.stringify(
|
|
540
|
+
res.end(JSON.stringify({
|
|
541
|
+
code: -1,
|
|
542
|
+
msg: `代理请求失败: ${err.message}`,
|
|
543
|
+
error: 'PROXY_ERROR'
|
|
544
|
+
}));
|
|
226
545
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
546
|
+
});
|
|
547
|
+
// 超时处理
|
|
548
|
+
proxyReq.on('timeout', () => {
|
|
549
|
+
proxyReq.destroy();
|
|
550
|
+
if (!res.headersSent) {
|
|
551
|
+
res.statusCode = 504;
|
|
552
|
+
res.setHeader('Content-Type', 'application/json');
|
|
553
|
+
res.end(JSON.stringify({
|
|
554
|
+
code: -1,
|
|
555
|
+
msg: '代理请求超时',
|
|
556
|
+
error: 'PROXY_TIMEOUT'
|
|
557
|
+
}));
|
|
231
558
|
}
|
|
232
|
-
|
|
559
|
+
});
|
|
560
|
+
// 转发请求体
|
|
561
|
+
if (req.method === 'POST' || req.method === 'PUT') {
|
|
562
|
+
// 处理 POST/PUT 请求的 body
|
|
563
|
+
let body = '';
|
|
564
|
+
req.on('data', (chunk) => {
|
|
565
|
+
body += chunk.toString();
|
|
566
|
+
});
|
|
567
|
+
req.on('end', () => {
|
|
568
|
+
if (body) {
|
|
569
|
+
proxyReq.write(body);
|
|
570
|
+
}
|
|
571
|
+
proxyReq.end();
|
|
572
|
+
});
|
|
233
573
|
}
|
|
234
|
-
|
|
235
|
-
|
|
574
|
+
else {
|
|
575
|
+
proxyReq.end();
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
catch (err) {
|
|
579
|
+
console.error(chalk_1.default.red(`[WarehouseMock] 代理配置错误: ${err}`));
|
|
580
|
+
if (!res.headersSent) {
|
|
236
581
|
res.statusCode = 500;
|
|
237
|
-
res.
|
|
238
|
-
|
|
582
|
+
res.setHeader('Content-Type', 'application/json');
|
|
583
|
+
res.end(JSON.stringify({
|
|
584
|
+
code: -1,
|
|
585
|
+
msg: `代理配置错误: ${err}`,
|
|
586
|
+
error: 'PROXY_CONFIG_ERROR'
|
|
587
|
+
}));
|
|
239
588
|
}
|
|
240
589
|
}
|
|
241
|
-
next();
|
|
242
590
|
}
|
|
243
591
|
}
|
|
244
592
|
module.exports = WarehouseMockPlugin;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "warehouse-mock",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "一个专为 Vue 2 项目设计的 Webpack 插件,支持 RPC 风格接口 mock,零业务代码侵入,实时更新",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -38,13 +38,16 @@
|
|
|
38
38
|
"chalk": "^4.1.2",
|
|
39
39
|
"chokidar": "^3.5.3"
|
|
40
40
|
},
|
|
41
|
+
"optionalDependencies": {
|
|
42
|
+
"warehouse-mock-admin": "^1.0.0"
|
|
43
|
+
},
|
|
41
44
|
"devDependencies": {
|
|
42
|
-
"
|
|
43
|
-
"webpack": "^5.0.0",
|
|
44
|
-
"webpack-dev-server": "^4.0.0",
|
|
45
|
+
"@types/node": "^18.0.0",
|
|
45
46
|
"@types/webpack": "^5.0.0",
|
|
46
47
|
"@types/webpack-dev-server": "^4.0.0",
|
|
47
|
-
"
|
|
48
|
+
"typescript": "^5.0.0",
|
|
49
|
+
"webpack": "^5.0.0",
|
|
50
|
+
"webpack-dev-server": "^4.0.0"
|
|
48
51
|
},
|
|
49
52
|
"peerDependencies": {
|
|
50
53
|
"webpack": "^4.0.0 || ^5.0.0"
|
|
@@ -53,4 +56,3 @@
|
|
|
53
56
|
"node": ">=12.0.0"
|
|
54
57
|
}
|
|
55
58
|
}
|
|
56
|
-
|