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