Skip to content

Commit 3b39f75

Browse files
committed
feat: mock and action config
1 parent 8e9d035 commit 3b39f75

File tree

12 files changed

+344
-151
lines changed

12 files changed

+344
-151
lines changed

README.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,23 @@
66
- 安装和启动方便
77

88
待办:
9-
- [待确认]支持版本:node>=14
10-
- 发布启动的配置信息汇总,提示产品使用文档:CLI使用说明,配置说明mock.config.json
11-
- 同时只能支持一个风格,当风格切换时,检测到有mock文件夹,则提示清空
9+
- [已完成]支持版本:node>=18
10+
- [已完成]发布启动的配置信息汇总,提示产品使用文档:CLI使用说明,配置说明mock.config.json,提示当前运行的action风格还是restful风格
11+
- [已完成]读取配置文件,全局使用处理,并确认默认文件的优先级:命令行 > 配置文件 > 默认配置
12+
- [已完成]如何remote api,网络不通,进行告警,并不再转发
1213
- TODO: restful: 读取swagger,生成json,处理中,晚上CLI的文档,用来测试
13-
- TODO: 读取配置文件,全局使用处理
14-
- TODO: .js默认加载
15-
- TODO: mock能力细节处理
16-
- 安装则生成mock.config.json
14+
- ACL文档的返回值
15+
- 上传npm的时候,指定文件
1716
- 生成单元测试
17+
- CLI使用文档
1818
- 录制演示视频
1919
- 优化打印日志,全部英文显示
20+
- 配置npm可以发布的文件
21+
- 支持自动生成mock.config.json
22+
- 同时只能支持一个风格,当风格切换时,检测到有mock文件夹,则提示清空
23+
- 当用户之前用restful风格,后面切换成了action后,在代码里面兼容,如果读取格式不对,就报错
24+
- TODO: .js默认加载
25+
- TODO: mock能力细节处理,支持不走本地的local json
2026

2127
### 二、启动一个Mock-API服务
2228

bin/config.js

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,47 @@ export const commandOptions = [
88
type: {
99
alias: 't',
1010
type: 'string',
11-
default: 'restful',
1211
describe: '选择API类型',
1312
choices: ['restful', 'action']
1413
},
1514
port: {
16-
alias: 'P',
15+
alias: 'p',
1716
type: 'number',
18-
default: 9000,
1917
describe: '选择启动的端口号'
20-
},
21-
create: {
22-
alias: 'c',
23-
type: 'boolean',
24-
default: false,
25-
describe: '如果mock目录不存在是否自动创建,默认不自动创建'
2618
}
19+
// create: {
20+
// alias: 'c',
21+
// type: 'boolean',
22+
// default: false,
23+
// describe: '如果mock目录不存在是否自动创建,默认不自动创建'
24+
// }
2725
},
2826
callback: async argv => {
2927
mock({
3028
...argv
3129
});
3230
}
31+
},
32+
{
33+
command: 'config',
34+
descriptions: '【开发中】自动在根目录下生成mock.config.json',
35+
options: {
36+
type: {
37+
alias: 't',
38+
type: 'string',
39+
describe: '选择API类型',
40+
default: 'restful',
41+
choices: ['restful', 'action']
42+
},
43+
port: {
44+
alias: 'p',
45+
type: 'number',
46+
default: 9000,
47+
describe: '选择启动的端口号'
48+
}
49+
},
50+
callback: async argv => {
51+
console.log("【开发中】自动在根目录下生成mock.config.json", argv)
52+
}
3353
}
3454
];

command/action.js

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@ import fs from 'fs';
22
import { createProxyMiddleware } from 'http-proxy-middleware';
33
import { ResponseExample } from './response.js';
44
import { getAllAction } from '../utils/index.js';
5-
import { getConfig } from '../utils/config.js';
65
import { generateApi, createAPIFile, fetchAndCreateRoutes } from './localAction.js';
76

87

9-
const action = async ({ app, filePath }) => {
8+
const action = async ({ app, filePath, config }) => {
109
// TODO: 配置信息需要统一处理,作为全局使用,type的每个优先级需要确认和逻辑开发
11-
const { proxyApiUrl, swaggerApiJSON=[] } = await getConfig();
10+
const { proxyApiUrl, swaggerApi=[] } = config;
1211

1312
// 如果 mock 文件夹不存在,自动创建它
1413
if (!fs.existsSync(filePath)) {
@@ -22,26 +21,14 @@ const action = async ({ app, filePath }) => {
2221
return;
2322
}
2423

25-
// Step 1: 中间件来拦截请求并动态修改 URL
26-
// app.use('/', (req, res, next) => {
27-
// const { Action } = req.body;
28-
// if (!Action) {
29-
// // 如果请求体中没有 Action,返回 400 错误
30-
// return res.status(400).send({ error: 'Action field is required' });
31-
// }
32-
// // 动态修改请求的 URL,将它转发到 /{Action} 路径
33-
// req.url = `/${Action}`;
34-
// next(); // 继续处理下一个中间件
35-
// });
36-
37-
// Step2: 为每个 action 注册对应的路由
24+
// Step1: 为每个 action 注册对应的路由
3825
generateApi(app, filePath, allActions);
3926

40-
// Step3: 获取swagger api json的数据,注册接口
27+
// Step2: 获取swagger api json的数据,注册接口
4128
// 调用 fetchAndCreateRoutes 函数
42-
await fetchAndCreateRoutes({app, swaggerApiJSON});
29+
await fetchAndCreateRoutes({app, swaggerApi});
4330

44-
// Step4: 获取代理配置并设置代理
31+
// Step3: 获取代理配置并设置代理
4532
if (proxyApiUrl) {
4633
app.use('*', createProxyMiddleware({
4734
target: proxyApiUrl,

command/localAction.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ export const createRoutes = ({ app, data }) => {
7575
};
7676

7777
// Step3: 获取swagger api json的数据,注册接口
78-
export const fetchAndCreateRoutes = async ({ app, swaggerApiJSON }) => {
79-
const routePromises = swaggerApiJSON.map(async (item) => {
78+
export const fetchAndCreateRoutes = async ({ app, swaggerApi }) => {
79+
const routePromises = swaggerApi.map(async (item) => {
8080
const { type, url } = item;
8181
if (type === 'action') {
8282
try {

command/middleware.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { defaultConfig } from '../utils/default.config.js';
1+
import defaultConfig from '../utils/defaultConfig.js';
22
import { setHeader } from '../utils/index.js';
33
export const cors = (req, res, next) => {
44
res.header('Access-Control-Allow-Origin', '*'); //访问控制允许来源:所有
@@ -33,7 +33,6 @@ export const headerSetting = (req, res, next) => {
3333
// action中间件的转化
3434
export const actionTransfer = (req, res, next) => {
3535
const { Action } = req.body;
36-
console.log('actionTransfer', Action);
3736
if (!Action) {
3837
// 如果请求体中没有 Action,返回 400 错误
3938
return res.status(400).send({ error: 'Action field is required' });

command/restful.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { createProxyMiddleware } from 'http-proxy-middleware';
2-
import { getConfig } from '../utils/config.js';
32
import { getAllAPIPath } from '../utils/index.js';
43
import { checkFileExist, checkFileExistsAndRespond } from './localRestful.js';
54

6-
7-
const restful = async ({ app, filePath }) => {
5+
const restful = async ({ app, filePath, config }) => {
6+
const { proxyApiUrl } = config;
87
// 如果没有文件,新建example
98
checkFileExist(filePath, true)
109
console.log(filePath)
@@ -15,7 +14,6 @@ const restful = async ({ app, filePath }) => {
1514
checkFileExistsAndRespond(url, filePath, req, res);
1615
});
1716
// 没有本地mock,则读取远程接口
18-
const { proxyApiUrl } = await getConfig();
1917
if (proxyApiUrl) {
2018
const proxyMiddleware = createProxyMiddleware({
2119
target: proxyApiUrl,

index.js

Lines changed: 9 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,11 @@
1-
import express from 'express';
2-
import path from 'path';
3-
import { fileURLToPath } from 'url';
4-
import logger from './utils/logger.js';
5-
import { cors, timeoutSetting, actionTransfer } from './command/middleware.js';
6-
import restful from './command/restful.js';
7-
import action from './command/action.js';
8-
import { ConfirmPort } from './utils/prompt.js';
9-
import { getIdlePort } from './utils/index.js';
10-
11-
// 获取当前文件路径(解决 ESM 模块路径问题)
12-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
13-
14-
// 静态资源路径
15-
const staticPath = path.join(__dirname, 'public');
16-
17-
// 表单提交处理函数
18-
const handleFormSubmission = (req, res) => {
19-
const { name, email } = req.body;
20-
console.log(`Received form submission: Name=${name}, Email=${email}`);
21-
res.send(`<h2>Thank you, ${name}! Your email is ${email}</h2>`);
22-
process.exit(0); // 停止 CLI 工具
23-
};
24-
25-
// 服务器启动日志
26-
const logServerStart = (port, type) => {
27-
const examples = {
28-
action: `curl --location --request POST 'http://localhost:${port}' --header 'Content-Type: application/json' --data-raw '{ "Action": "Query" }'`,
29-
restful: `curl --location --request GET 'http://localhost:${port}/v1/user' --header 'Content-Type: application/json'`,
30-
};
31-
logger.success(`Mock API listening on port ${port}!`);
32-
logger.success(`example: ${examples[type]}`);
33-
};
34-
35-
// 启动服务器
36-
const startServer = (app, port, type) => {
37-
app.listen(port, () => logServerStart(port, type));
38-
};
39-
40-
// 模拟服务器
41-
const mock = async ({ port, type }) => {
42-
// open port
43-
const newPort = await getIdlePort(port);
44-
let confirmPortResult = true;
45-
if (port !== newPort) {
46-
confirmPortResult = await ConfirmPort(port, newPort);
47-
if (confirmPortResult) {
48-
port = newPort;
49-
} else {
50-
logger.output.error(
51-
`The current port is occupied and the service cannot be started. Please close the current port and try again.`,
52-
);
53-
process.exit(0);
54-
}
55-
}
56-
57-
// 启动express
58-
const app = express();
59-
const filePath = path.join(process.cwd(), './mock');
60-
61-
app.use(express.urlencoded({ extended: true }));
62-
app.use(express.json({ limit: '50mb' }));
63-
app.use(express.static(staticPath));
64-
65-
// 渲染 HTML 表单页面
66-
app.get('/monto/docs', (req, res) => res.sendFile(path.join(staticPath, 'index.html')));
67-
68-
// 处理表单提交
69-
app.post('/submit', handleFormSubmission);
70-
71-
// 通用中间件
72-
app.all('*', cors);
73-
app.all('*', timeoutSetting);
74-
if (type === 'action') {
75-
app.all("*", actionTransfer)
76-
}
77-
78-
// 根据类型加载相应模块
79-
const loadModule = type === 'action' ? action : restful;
80-
loadModule({ app, filePath });
81-
82-
startServer(app, port, type);
83-
};
1+
import MockServer from './server.js';
2+
import ConfigManager from './utils/config.js';
3+
4+
const mock = async (commandArgs) => {
5+
const GlobalConfig = new ConfigManager();
6+
const config = await GlobalConfig.getConfig(commandArgs);
7+
const mockServer = new MockServer(config);
8+
mockServer.start();
9+
}
8410

8511
export default mock;

package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@
33
"version": "0.0.3",
44
"description": "a react-cli and create react app",
55
"main": "index.js",
6+
"files": [
7+
"bin",
8+
"command",
9+
"public",
10+
"utils",
11+
"index.js",
12+
"package.json",
13+
"server.js",
14+
"README.md"
15+
],
616
"type": "module",
717
"bin": {
818
"dev-mock-cli": "./bin/index.js"

server.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import express from 'express';
2+
import path from 'path';
3+
4+
import { cors, timeoutSetting, actionTransfer } from './command/middleware.js';
5+
import restful from './command/restful.js';
6+
import action from './command/action.js';
7+
import { ConfirmPort } from './utils/prompt.js';
8+
import { getIdlePort } from './utils/index.js';
9+
import { logServerStart, handleFormSubmission } from './utils/serverUtils.js';
10+
11+
class MockServer {
12+
constructor(config) {
13+
this.config = config;
14+
this.port = config.port;
15+
this.type = config.type;
16+
this.staticPath = path.join(process.cwd(), 'public');
17+
this.filePath = path.join(process.cwd(), './mock');
18+
}
19+
20+
// 启动服务器
21+
async start() {
22+
const newPort = await getIdlePort(this.port);
23+
let confirmPortResult = true;
24+
25+
if (this.port !== newPort) {
26+
confirmPortResult = await ConfirmPort(this.port, newPort);
27+
if (confirmPortResult) {
28+
this.port = newPort;
29+
} else {
30+
console.error('The current port is occupied and the service cannot be started.');
31+
process.exit(0);
32+
}
33+
}
34+
35+
const app = express();
36+
app.use(express.urlencoded({ extended: true }));
37+
app.use(express.json({ limit: '50mb' }));
38+
app.use(express.static(this.staticPath));
39+
40+
app.get('/monto/docs', (req, res) => res.sendFile(path.join(this.staticPath, 'index.html')));
41+
app.post('/submit', handleFormSubmission);
42+
43+
// 通用中间件
44+
app.all('*', cors);
45+
app.all('*', timeoutSetting);
46+
if (this.type === 'action') {
47+
app.all('*', actionTransfer);
48+
}
49+
50+
// 根据类型加载模块
51+
const loadModule = this.type === 'action' ? action : restful;
52+
loadModule({ app, filePath: this.filePath, config: this.config });
53+
54+
this.startServer(app);
55+
}
56+
57+
// 启动Express服务器并打印启动日志
58+
startServer(app) {
59+
app.listen(this.port, () => logServerStart(this.port, this.type));
60+
}
61+
}
62+
63+
export default MockServer;

0 commit comments

Comments
 (0)